mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-25 22:43:03 +00:00
Compare commits
271 Commits
newRendere
...
v2.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f754ecd6c3 | ||
|
|
62a827ce49 | ||
|
|
e971da2b59 | ||
|
|
2d092cb290 | ||
|
|
5b66ecb06f | ||
|
|
22a9799674 | ||
|
|
7c813be13a | ||
|
|
e94148b2f0 | ||
|
|
ec4f6e4327 | ||
|
|
fcbd117784 | ||
|
|
dab716a9e0 | ||
|
|
9265e25c73 | ||
|
|
4ad63d5bce | ||
|
|
f89c897488 | ||
|
|
521ff5e7e3 | ||
|
|
89d8cb3b0a | ||
|
|
a0e92b54d0 | ||
|
|
62f549f038 | ||
|
|
e8f3b0c8d0 | ||
|
|
587ce78f4e | ||
|
|
7ca1dd3c68 | ||
|
|
58543f0b4d | ||
|
|
e88253f364 | ||
|
|
bdf37d8fe7 | ||
|
|
22908207a3 | ||
|
|
7239b89108 | ||
|
|
0ea80bd758 | ||
|
|
f6d623ace3 | ||
|
|
63ad8b3411 | ||
|
|
50cc757a5c | ||
|
|
70430f84e1 | ||
|
|
80db261c88 | ||
|
|
5631ef7be7 | ||
|
|
2745a4d6c1 | ||
|
|
33190b5c89 | ||
|
|
354a5832e4 | ||
|
|
f57c0f0886 | ||
|
|
954a393fce | ||
|
|
01dbac78ce | ||
|
|
594ea8ab59 | ||
|
|
033493a31b | ||
|
|
e79b099633 | ||
|
|
5bc948ab0a | ||
|
|
28c5d7d84a | ||
|
|
825702ee1d | ||
|
|
61b58032ca | ||
|
|
546cc13c1c | ||
|
|
44649d7f51 | ||
|
|
5f9aaba262 | ||
|
|
5d5c0b9773 | ||
|
|
b681edba23 | ||
|
|
f0c412527b | ||
|
|
fcf6b8d764 | ||
|
|
8607b9dba8 | ||
|
|
ef4fa89d9a | ||
|
|
6d38a633ef | ||
|
|
b3376435b9 | ||
|
|
b9cfc2e6af | ||
|
|
3259836964 | ||
|
|
b3387c363f | ||
|
|
f0d9fcf942 | ||
|
|
928b553b19 | ||
|
|
508f87f117 | ||
|
|
03b389761b | ||
|
|
36d0f15960 | ||
|
|
b56d4fb773 | ||
|
|
272b336cd8 | ||
|
|
ac58833adf | ||
|
|
c3432a9263 | ||
|
|
f8f1c99266 | ||
|
|
0e9b50d4e8 | ||
|
|
e6e995d7f4 | ||
|
|
c9d18be0cb | ||
|
|
b75063f936 | ||
|
|
6ef88e0f1f | ||
|
|
59e0118d8b | ||
|
|
09e6766e0d | ||
|
|
ce8cfde211 | ||
|
|
7fbb51b3f4 | ||
|
|
1e9b8e679d | ||
|
|
53a1c4f85d | ||
|
|
b1c252495b | ||
|
|
5fce35edd7 | ||
|
|
debe58ff0b | ||
|
|
0018627f82 | ||
|
|
e50f0a1f3b | ||
|
|
e862f65166 | ||
|
|
347575b0ec | ||
|
|
4ac922482e | ||
|
|
27e7af870a | ||
|
|
474b2552fd | ||
|
|
a06b29c6f5 | ||
|
|
4128670a9f | ||
|
|
94d090277f | ||
|
|
0b8889d0b8 | ||
|
|
2efb24d692 | ||
|
|
bc81e09686 | ||
|
|
aeffec1763 | ||
|
|
462a5608d2 | ||
|
|
09ae750eec | ||
|
|
e8dcb042f8 | ||
|
|
ecd25ca49f | ||
|
|
3e8551bad6 | ||
|
|
ef325e2617 | ||
|
|
81c361bfb8 | ||
|
|
37eb0d0889 | ||
|
|
8adc04a565 | ||
|
|
486841084f | ||
|
|
399a6d82f6 | ||
|
|
becc6b8df0 | ||
|
|
1ff3f96f6c | ||
|
|
b289cb1003 | ||
|
|
3ea3d273a5 | ||
|
|
a007c5f85f | ||
|
|
06d970e61a | ||
|
|
f28ed3d52e | ||
|
|
c5867dab91 | ||
|
|
df4bacf890 | ||
|
|
6a57542216 | ||
|
|
72db7fedfb | ||
|
|
892a5f9f1e | ||
|
|
2839846ec0 | ||
|
|
502ef6ad7c | ||
|
|
5b8aa5bb19 | ||
|
|
a26e828f00 | ||
|
|
f572e671cf | ||
|
|
d18bd500b1 | ||
|
|
14f721d209 | ||
|
|
1219f64cb3 | ||
|
|
b82aac4a5a | ||
|
|
b53b5ccf43 | ||
|
|
9e7981f05c | ||
|
|
18a238786e | ||
|
|
507f8e0852 | ||
|
|
0c70162a78 | ||
|
|
077511dfa7 | ||
|
|
69b25eb03a | ||
|
|
a56a999920 | ||
|
|
c1d8796807 | ||
|
|
71af97e489 | ||
|
|
facbc5f6dc | ||
|
|
63a1ff454f | ||
|
|
c634192289 | ||
|
|
bf21c3d351 | ||
|
|
52c0462a4f | ||
|
|
1184fe86a5 | ||
|
|
7656e53606 | ||
|
|
4241052952 | ||
|
|
21b83ead88 | ||
|
|
448ea5cf5c | ||
|
|
20b719d0de | ||
|
|
8b04cc9269 | ||
|
|
96466211c7 | ||
|
|
34741291c7 | ||
|
|
fcb0cd8ee7 | ||
|
|
aafac16af2 | ||
|
|
911d1d4f9c | ||
|
|
2cd7b44e12 | ||
|
|
bfccd1d9e4 | ||
|
|
bf1bf6c191 | ||
|
|
cc5cb677a1 | ||
|
|
2dcd7101f3 | ||
|
|
f4e7e46a04 | ||
|
|
8b3b7cb5aa | ||
|
|
77081b39b4 | ||
|
|
a06236e3ff | ||
|
|
c8585775be | ||
|
|
439cdfa1ea | ||
|
|
4c36f254c4 | ||
|
|
b6815593fd | ||
|
|
84054d1aae | ||
|
|
6cd6b3c1c6 | ||
|
|
907dbe4416 | ||
|
|
47429d804a | ||
|
|
1d54ab49e6 | ||
|
|
f634c451ff | ||
|
|
778c0ca0b1 | ||
|
|
f4985b68ca | ||
|
|
c8875cff94 | ||
|
|
40f1d4c790 | ||
|
|
7818b6db46 | ||
|
|
4163e95938 | ||
|
|
fed0536673 | ||
|
|
a4b92af351 | ||
|
|
4584b83c60 | ||
|
|
c57ca0b27f | ||
|
|
c04cd23213 | ||
|
|
140f064786 | ||
|
|
643f6f933d | ||
|
|
24aec1c649 | ||
|
|
8caae18a12 | ||
|
|
a1140f75d8 | ||
|
|
3532d75365 | ||
|
|
ea81bb5ebc | ||
|
|
8d217f8785 | ||
|
|
d35db1f702 | ||
|
|
f6b058f3c9 | ||
|
|
5ab7c29b9d | ||
|
|
c5ecb9d57d | ||
|
|
d0c473878a | ||
|
|
796df9a1ac | ||
|
|
9e8e403195 | ||
|
|
a369871a06 | ||
|
|
f2f45f3657 | ||
|
|
e86ce5cf06 | ||
|
|
8d73ff6833 | ||
|
|
c4397d34f8 | ||
|
|
66c0c96a4f | ||
|
|
838b64c589 | ||
|
|
730dde730c | ||
|
|
d867aa7ce1 | ||
|
|
761597e71f | ||
|
|
4bde5fcbf8 | ||
|
|
ff0aa56ddc | ||
|
|
74f92f3e44 | ||
|
|
84285f7359 | ||
|
|
ec0de7a408 | ||
|
|
9ea99236ff | ||
|
|
f806328e75 | ||
|
|
15ac397b63 | ||
|
|
b8105eb147 | ||
|
|
31cbd9ef40 | ||
|
|
eaab6de691 | ||
|
|
d417c76c56 | ||
|
|
2f15cc5611 | ||
|
|
eb08172fb1 | ||
|
|
0cc87a4f0f | ||
|
|
004dc79eb2 | ||
|
|
a8a70c2d70 | ||
|
|
825c259fba | ||
|
|
cbbb7292d9 | ||
|
|
9519a0b4e4 | ||
|
|
9dd16b6dd5 | ||
|
|
d693301c37 | ||
|
|
fd5d142c16 | ||
|
|
ee63d2d857 | ||
|
|
7c3946fb03 | ||
|
|
3e69e8c1aa | ||
|
|
1a0bc1952c | ||
|
|
7e2c3381ae | ||
|
|
4f4ef908e0 | ||
|
|
3fe2360d92 | ||
|
|
13a2a7efd2 | ||
|
|
073fb73bde | ||
|
|
847615ef8e | ||
|
|
3583c2e776 | ||
|
|
2a753ccc7c | ||
|
|
55c529473a | ||
|
|
164f646e08 | ||
|
|
6f4962926c | ||
|
|
f18a181e2e | ||
|
|
c4bff6afa0 | ||
|
|
63e8a0d9b7 | ||
|
|
ce3fda683b | ||
|
|
9594b73b0d | ||
|
|
78ad4bea09 | ||
|
|
ed1b5252be | ||
|
|
bf27250990 | ||
|
|
bab11d692e | ||
|
|
3350b04f64 | ||
|
|
5ebba25183 | ||
|
|
8f77ac9e56 | ||
|
|
3f728e7993 | ||
|
|
3f22572f98 | ||
|
|
a52f628fdf | ||
|
|
fe63133d7c | ||
|
|
3247cab214 | ||
|
|
b68c6a4ad2 | ||
|
|
6e0f042b42 | ||
|
|
c47d492ed3 | ||
|
|
18852932e8 |
7
.bithoundrc
Normal file
7
.bithoundrc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"unused-ignores": [
|
||||||
|
"react-dom"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
32
.circleci/config.yml
Normal file
32
.circleci/config.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Javascript Node CircleCI 2.0 configuration file
|
||||||
|
#
|
||||||
|
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
||||||
|
#
|
||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:12.16.3
|
||||||
|
- image: circleci/mongo:3.4-jessie
|
||||||
|
|
||||||
|
working_directory: ~/repo
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
|
||||||
|
# Download and cache dependencies
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- v1-dependencies-{{ checksum "package.json" }}
|
||||||
|
# fallback to using the latest cache if no exact match is found
|
||||||
|
- v1-dependencies-
|
||||||
|
|
||||||
|
- run: npm install
|
||||||
|
|
||||||
|
- save_cache:
|
||||||
|
paths:
|
||||||
|
- node_modules
|
||||||
|
key: v1-dependencies-{{ checksum "package.json" }}
|
||||||
|
|
||||||
|
# run tests!
|
||||||
|
- run: npm run circleci
|
||||||
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.git
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
tests
|
||||||
78
.eslintrc.js
Normal file
78
.eslintrc.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
module.exports = {
|
||||||
|
root : true,
|
||||||
|
parserOptions : {
|
||||||
|
ecmaVersion : 9,
|
||||||
|
sourceType : 'module',
|
||||||
|
ecmaFeatures : {
|
||||||
|
jsx : true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
env : {
|
||||||
|
browser : true,
|
||||||
|
node : true
|
||||||
|
},
|
||||||
|
plugins : ['react'],
|
||||||
|
rules : {
|
||||||
|
/** Errors **/
|
||||||
|
'camelcase' : ['error', { properties: 'never' }],
|
||||||
|
'func-style' : ['error', 'expression', { allowArrowFunctions: true }],
|
||||||
|
'no-array-constructor' : 'error',
|
||||||
|
'no-iterator' : 'error',
|
||||||
|
'no-nested-ternary' : 'error',
|
||||||
|
'no-new-object' : 'error',
|
||||||
|
'no-proto' : 'error',
|
||||||
|
'react/jsx-no-bind' : ['error', { allowArrowFunctions: true }],
|
||||||
|
'react/jsx-uses-react' : 'error',
|
||||||
|
'react/prefer-es6-class' : ['error', 'never'],
|
||||||
|
|
||||||
|
/** Warnings **/
|
||||||
|
'max-lines' : ['warn', {
|
||||||
|
max : 200,
|
||||||
|
skipComments : true,
|
||||||
|
skipBlankLines : true,
|
||||||
|
}],
|
||||||
|
'max-depth' : ['warn', { max: 4 }],
|
||||||
|
'max-params' : ['warn', { max: 4 }],
|
||||||
|
'no-restricted-syntax' : ['warn', 'ClassDeclaration', 'SwitchStatement'],
|
||||||
|
'no-unused-vars' : ['warn', {
|
||||||
|
vars : 'all',
|
||||||
|
args : 'none',
|
||||||
|
varsIgnorePattern : 'config|_|cx|createClass'
|
||||||
|
}],
|
||||||
|
'react/jsx-uses-vars' : 'warn',
|
||||||
|
|
||||||
|
/** Fixable **/
|
||||||
|
'arrow-parens' : ['warn', 'always'],
|
||||||
|
'brace-style' : ['warn', '1tbs', { allowSingleLine: true }],
|
||||||
|
'jsx-quotes' : ['warn', 'prefer-single'],
|
||||||
|
'no-var' : 'warn',
|
||||||
|
'prefer-const' : 'warn',
|
||||||
|
'prefer-template' : 'warn',
|
||||||
|
'quotes' : ['warn', 'single', { 'allowTemplateLiterals': true }],
|
||||||
|
'semi' : ['warn', 'always'],
|
||||||
|
|
||||||
|
/** Whitespace **/
|
||||||
|
'array-bracket-spacing' : ['warn', 'never'],
|
||||||
|
'arrow-spacing' : ['warn', { before: false, after: false }],
|
||||||
|
'comma-spacing' : ['warn', { before: false, after: true }],
|
||||||
|
'indent' : ['warn', 'tab'],
|
||||||
|
'keyword-spacing' : ['warn', {
|
||||||
|
before : true,
|
||||||
|
after : true,
|
||||||
|
overrides : {
|
||||||
|
if : { 'before': false, 'after': false }
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
'key-spacing' : ['warn', {
|
||||||
|
multiLine : { beforeColon: true, afterColon: true, align: 'colon' },
|
||||||
|
singleLine : { beforeColon: false, afterColon: true }
|
||||||
|
}],
|
||||||
|
'linebreak-style' : 'off',
|
||||||
|
'no-trailing-spaces' : 'warn',
|
||||||
|
'no-whitespace-before-property' : 'warn',
|
||||||
|
'object-curly-spacing' : ['warn', 'always'],
|
||||||
|
'react/jsx-indent-props' : ['warn', 'tab'],
|
||||||
|
'space-in-parens' : ['warn', 'never'],
|
||||||
|
'template-curly-spacing' : ['warn', 'never'],
|
||||||
|
}
|
||||||
|
};
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package-lock.json binary
|
||||||
14
.github/issue_template.md
vendored
14
.github/issue_template.md
vendored
@@ -1,9 +1,21 @@
|
|||||||
|
<!-- CLICK "Preview" FOR INSTRUCTIONS IN A MORE READABLE FORMAT -->
|
||||||
|
|
||||||
|
## Before you submit
|
||||||
|
|
||||||
|
- Support questions are better asked on the subreddit [r/homebrewery](https://www.reddit.com/r/homebrewery/)
|
||||||
|
- Read the [contributing guidelines](https://github.com/stolksdorf/homebrewery/blob/master/contributing.md).
|
||||||
|
- If it's an issue, please make sure it's reproducible
|
||||||
|
- Ensure the issue isn't already reported.
|
||||||
|
|
||||||
|
|
||||||
|
*Delete the above section and the instructions in the sections below before submitting*
|
||||||
|
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
### Additional Details
|
If this is a *feature request*, explain why it should be added. Specific use-cases are best.
|
||||||
|
|
||||||
|
For *bug reports*, please provide as much *relevant* info as possible.
|
||||||
|
|
||||||
**Share Link** :
|
**Share Link** :
|
||||||
|
|
||||||
|
|||||||
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1,16 +1,12 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
|
|
||||||
#Ignore our built files
|
|
||||||
build/*
|
|
||||||
|
|
||||||
# Ignore sensitive stuff
|
|
||||||
config/local.json
|
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
storage
|
storage
|
||||||
.idea
|
.idea
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
todo.md
|
*.log
|
||||||
|
build/*
|
||||||
|
config/local.*
|
||||||
|
|
||||||
|
todo.md
|
||||||
|
startDB.bat
|
||||||
|
startMViewer.bat
|
||||||
|
|||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM node:8
|
||||||
|
|
||||||
|
ENV NODE_ENV=docker
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy package.json into the image, then run yarn install
|
||||||
|
# This improves caching so we don't have to download the dependencies every time the code changes
|
||||||
|
COPY package.json ./
|
||||||
|
# --ignore-scripts tells yarn not to run postbuild. We run it explicitly later
|
||||||
|
RUN yarn install --ignore-scripts
|
||||||
|
|
||||||
|
# Bundle app source and build application
|
||||||
|
COPY . .
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD [ "yarn", "start" ]
|
||||||
12
README.DOCKER.md
Normal file
12
README.DOCKER.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Running Homebrewery via Docker
|
||||||
|
|
||||||
|
The repo includes a Dockerfile and a docker-compose.yml file.
|
||||||
|
|
||||||
|
To run the application via docker-compose.yml:
|
||||||
|
`docker-compose up -d`
|
||||||
|
|
||||||
|
To stop the application:
|
||||||
|
`docker-compose down`
|
||||||
|
|
||||||
|
To stop the application and remove all data:
|
||||||
|
`docker-compose down -v`
|
||||||
53
README.md
53
README.md
@@ -1,33 +1,52 @@
|
|||||||
# The Homebrewery
|
# The Homebrewery
|
||||||
The Homebrewery is a tool for making authnetic looking [D&D content](http://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook) using only [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). Check it out [here](http://homebrewery.naturalcrit.com).
|
The Homebrewery is a tool for making authentic looking [D&D content](https://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook) using [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). It is distributed under the terms of the [MIT License](./license).
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
The easiest way to get started using the Homebrewery is to use it [on our website](https://homebrewery.naturalcrit.com). The code is open source, so feel free to clone it, tinker with it. If you want to make changes to the code, you can run your own local version for testing by following the installation instructions below.
|
||||||
|
|
||||||
### issues, suggestions, bugs
|
### Installation
|
||||||
If you run into any issues using The Homebrewery, please submit an issues [here](/issues)
|
First, install two programs that the Homebrewery requires to run.
|
||||||
|
|
||||||
|
|
||||||
### local dev
|
|
||||||
Homebrewery is open source, so feel free to clone it, tinker with it, or run your own local version.
|
|
||||||
|
|
||||||
#### pre-reqs
|
|
||||||
1. install [node](https://nodejs.org/en/)
|
1. install [node](https://nodejs.org/en/)
|
||||||
1. install [mongodb](https://www.mongodb.com/)
|
1. install [mongodb](https://www.mongodb.com/)
|
||||||
|
|
||||||
#### getting started
|
Second, download a copy of the repository. If you have git you can do so with
|
||||||
1. clone it
|
```
|
||||||
|
git clone https://github.com/naturalcrit/homebrewery.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Third, you will need to add the environment variable `NODE_ENV = local` to allow the project to run locally.
|
||||||
|
|
||||||
|
You can set this temporarily in your shell of choice:
|
||||||
|
* Windows Powershell: `$env:NODE_ENV="local"`
|
||||||
|
* Windows CMD: `set NODE_ENV=local`
|
||||||
|
* Linux / OSX: `export NODE_ENV=local`
|
||||||
|
|
||||||
|
Fourth, you will need to install the program and run it using the two commands:
|
||||||
|
|
||||||
1. `npm install`
|
1. `npm install`
|
||||||
1. `npm build`
|
|
||||||
1. `npm start`
|
1. `npm start`
|
||||||
|
|
||||||
#### standalone PHB stylesheet
|
You should now be able to go to [http://localhost:8000](http://localhost:8000) in your browser and use the Homebrewery offline.
|
||||||
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)
|
|
||||||
|
### Running the application via Docker
|
||||||
|
|
||||||
|
Please see the docs here: [README.DOCKER.md](./README.DOCKER.md)
|
||||||
|
|
||||||
|
### Standalone PHB Stylesheet
|
||||||
|
If you just want the stylesheet that is generated to make pages look like they are from the Player's Handbook, you will find it in the [phb.standalone.css](./phb.standalone.css) file.
|
||||||
|
|
||||||
If you are developing locally and would like to generate your own, follow the above steps and then run `npm run phb`.
|
If you are developing locally and would like to generate your own, follow the above steps and then run `npm run phb`.
|
||||||
|
|
||||||
### changelog
|
## Issues, Suggestions, and Bugs
|
||||||
|
If you run into any issues using The Homebrewery or have suggestions for improvement, please submit an issue [on GitHub](/issues). You can also get help for issues on the subreddit [r/homebrewery](https://www.reddit.com/r/homebrewery)
|
||||||
|
|
||||||
You can check out the changelog [here](https://github.com/stolksdorf/homebrewery/blob/master/changelog.md)
|
## Changelog
|
||||||
|
|
||||||
### license
|
You can check out the [changelog](./changelog.md).
|
||||||
|
|
||||||
This project is licensed under [MIT](./license)
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT license](./license). Which means you are free to use The Homebrewery in any way that you want, except for claiming that you made it yourself.
|
||||||
|
|
||||||
|
If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||||
|
|||||||
33
changelog.md
33
changelog.md
@@ -1,5 +1,38 @@
|
|||||||
# changelog
|
# changelog
|
||||||
|
|
||||||
|
### Wednesday, 11/03/2020 - v2.8.2
|
||||||
|
- Fixed delete button removing everyone's copy for brews with multiple authors
|
||||||
|
- Compressed homebrew text in database
|
||||||
|
|
||||||
|
### Monday, 26/11/2018 - v2.8.1
|
||||||
|
- Fixed some SSL issues with images in the example page so they appear now
|
||||||
|
- Fixed duplicate scrollbars in Edit Page
|
||||||
|
- Fixed issue of being unable to change brew metadata
|
||||||
|
- Sanitized script tags-javascript typed into the editor was crashing brews
|
||||||
|
|
||||||
|
### Sunday, 08/04/2018 - v2.8.0
|
||||||
|
- Re-enabled box shadows for PDF output
|
||||||
|
- Added a "contributing guide" for the GitHub
|
||||||
|
- "Report Issue" navbar button now links to the subreddit
|
||||||
|
- Refactored background code
|
||||||
|
|
||||||
|
### Sunday, 04/06/2017 - v2.7.5
|
||||||
|
- Fixed the class feature snippet duplicating the entire brew
|
||||||
|
- Fixed headers in tables being duplicated
|
||||||
|
- Fixed border-image being scrambled on class tables and descriptive text boxes
|
||||||
|
- Fixed pages going out of sync in large brews, causing them to be rendered off-page
|
||||||
|
- Improved performance in the preview window when scrolling through large brews
|
||||||
|
- Text in the "view source" page now wraps
|
||||||
|
|
||||||
|
### Saturday, 22/04/2017 - v2.7.4
|
||||||
|
- Give ability to hide the render warning notification
|
||||||
|
|
||||||
|
### Friday, 03/03/2017 - v2.7.3
|
||||||
|
- Increasing the range on the Partial Page Rendering for a quick-fix for it getting out of sync on long brews.
|
||||||
|
|
||||||
|
### Saturday, 18/02/2017 - v2.7.2
|
||||||
|
- Adding ability to delete a brew from the user page, incase the user creates a brew that makes the edit page unrender-able. (re:309)
|
||||||
|
|
||||||
## BIG NEWS
|
## BIG NEWS
|
||||||
With the next major release of Homebrewery, v3.0.0, this tool *will no longer support raw HTML input for brew code*. Most issues and errors users are having are because of this feature and it's become too taxing to help and fix these issues.
|
With the next major release of Homebrewery, v3.0.0, this tool *will no longer support raw HTML input for brew code*. Most issues and errors users are having are because of this feature and it's become too taxing to help and fix these issues.
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,38 @@
|
|||||||
var React = require('react');
|
require('./admin.less');
|
||||||
var _ = require('lodash');
|
const React = require('react');
|
||||||
var cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
|
||||||
var HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
|
|
||||||
|
const BrewCleanup = require('./brewCleanup/brewCleanup.jsx');
|
||||||
var Admin = React.createClass({
|
const BrewLookup = require('./brewLookup/brewLookup.jsx');
|
||||||
getDefaultProps: function() {
|
const BrewCompress = require ('./brewCompress/brewCompress.jsx');
|
||||||
return {
|
const Stats = require('./stats/stats.jsx');
|
||||||
url : "",
|
|
||||||
admin_key : "",
|
const Admin = createClass({
|
||||||
homebrews : [],
|
getDefaultProps : function() {
|
||||||
};
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
var self = this;
|
return <div className='admin'>
|
||||||
return(
|
|
||||||
<div className='admin'>
|
<header>
|
||||||
|
<div className='container'>
|
||||||
<header>
|
<i className='fa fa-rocket' />
|
||||||
<div className='container'>
|
homebrewery admin
|
||||||
<i className='fa fa-rocket' />
|
</div>
|
||||||
naturalcrit admin
|
</header>
|
||||||
</div>
|
<div className='container'>
|
||||||
</header>
|
<Stats />
|
||||||
|
<hr />
|
||||||
<div className='container'>
|
<BrewLookup />
|
||||||
|
<hr />
|
||||||
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
<BrewCleanup />
|
||||||
</div>
|
<hr />
|
||||||
|
<BrewCompress />
|
||||||
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
module.exports = Admin;
|
||||||
module.exports = Admin;
|
|
||||||
|
|||||||
@@ -36,4 +36,9 @@ body{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
hr{
|
||||||
|
margin : 30px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
75
client/admin/brewCleanup/brewCleanup.jsx
Normal file
75
client/admin/brewCleanup/brewCleanup.jsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
require('./brewCleanup.less');
|
||||||
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
|
||||||
|
|
||||||
|
const BrewCleanup = createClass({
|
||||||
|
displayName : 'BrewCleanup',
|
||||||
|
getDefaultProps(){
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
count : 0,
|
||||||
|
|
||||||
|
pending : false,
|
||||||
|
primed : false,
|
||||||
|
err : null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
prime(){
|
||||||
|
this.setState({ pending: true });
|
||||||
|
|
||||||
|
request.get('/admin/cleanup')
|
||||||
|
.then((res)=>this.setState({ count: res.body.count, primed: true }))
|
||||||
|
.catch((err)=>this.setState({ error: err }))
|
||||||
|
.finally(()=>this.setState({ pending: false }));
|
||||||
|
},
|
||||||
|
cleanup(){
|
||||||
|
this.setState({ pending: true });
|
||||||
|
|
||||||
|
request.post('/admin/cleanup')
|
||||||
|
.then((res)=>this.setState({ count: res.body.count }))
|
||||||
|
.catch((err)=>this.setState({ error: err }))
|
||||||
|
.finally(()=>this.setState({ pending: false, primed: false }));
|
||||||
|
},
|
||||||
|
renderPrimed(){
|
||||||
|
if(!this.state.primed) return;
|
||||||
|
|
||||||
|
if(!this.state.count){
|
||||||
|
return <div className='removeBox'>No Matching Brews found.</div>;
|
||||||
|
}
|
||||||
|
return <div className='removeBox'>
|
||||||
|
<button onClick={this.cleanup} className='remove'>
|
||||||
|
{this.state.pending
|
||||||
|
? <i className='fa fa-spin fa-spinner' />
|
||||||
|
: <span><i className='fa fa-times' /> Remove</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
<span>Found {this.state.count} Brews that could be removed. </span>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
render(){
|
||||||
|
return <div className='BrewCleanup'>
|
||||||
|
<h2> Brew Cleanup </h2>
|
||||||
|
<p>Removes very short brews to tidy up the database</p>
|
||||||
|
|
||||||
|
<button onClick={this.prime} className='query'>
|
||||||
|
{this.state.pending
|
||||||
|
? <i className='fa fa-spin fa-spinner' />
|
||||||
|
: 'Query Brews'
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
{this.renderPrimed()}
|
||||||
|
|
||||||
|
{this.state.error
|
||||||
|
&& <div className='error'>{this.state.error.toString()}</div>
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewCleanup;
|
||||||
10
client/admin/brewCleanup/brewCleanup.less
Normal file
10
client/admin/brewCleanup/brewCleanup.less
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.BrewCleanup{
|
||||||
|
.removeBox{
|
||||||
|
margin-top: 20px;
|
||||||
|
button{
|
||||||
|
background-color: @red;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
92
client/admin/brewCompress/brewCompress.jsx
Normal file
92
client/admin/brewCompress/brewCompress.jsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
require('./brewCompress.less');
|
||||||
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
|
||||||
|
|
||||||
|
const BrewCompress = createClass({
|
||||||
|
displayName : 'BrewCompress',
|
||||||
|
getDefaultProps(){
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
count : 0,
|
||||||
|
batchRange : 0,
|
||||||
|
|
||||||
|
pending : false,
|
||||||
|
primed : false,
|
||||||
|
err : null,
|
||||||
|
ids : null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
prime(){
|
||||||
|
this.setState({ pending: true });
|
||||||
|
|
||||||
|
request.get('/admin/finduncompressed')
|
||||||
|
.then((res)=>this.setState({ count: res.body.count, primed: true, ids: res.body.ids }))
|
||||||
|
.catch((err)=>this.setState({ error: err }))
|
||||||
|
.finally(()=>this.setState({ pending: false }));
|
||||||
|
},
|
||||||
|
cleanup(){
|
||||||
|
const brews = this.state.ids;
|
||||||
|
const compressBatches = ()=>{
|
||||||
|
if(brews.length == 0){
|
||||||
|
this.setState({ pending: false, primed: false });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const batch = brews.splice(0, 1000); // Process brews in batches of 1000
|
||||||
|
this.setState({ batchRange: this.state.count - brews.length });
|
||||||
|
batch.forEach((id, idx)=>{
|
||||||
|
request.put(`/admin/compress/${id}`)
|
||||||
|
.catch((err)=>this.setState({ error: err }));
|
||||||
|
});
|
||||||
|
setTimeout(compressBatches, 10000); //Wait 10 seconds between batches
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState({ pending: true });
|
||||||
|
|
||||||
|
compressBatches();
|
||||||
|
},
|
||||||
|
renderPrimed(){
|
||||||
|
if(!this.state.primed) return;
|
||||||
|
|
||||||
|
if(!this.state.count){
|
||||||
|
return <div className='removeBox'>No Matching Brews found.</div>;
|
||||||
|
}
|
||||||
|
return <div className='removeBox'>
|
||||||
|
<button onClick={this.cleanup} className='remove'>
|
||||||
|
{this.state.pending
|
||||||
|
? <i className='fa fa-spin fa-spinner' />
|
||||||
|
: <span><i className='fa fa-compress' /> compress </span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
{this.state.pending
|
||||||
|
? <span>Compressing {this.state.batchRange} brews. </span>
|
||||||
|
: <span>Found {this.state.count} Brews that could be compressed. </span>
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
render(){
|
||||||
|
return <div className='BrewCompress'>
|
||||||
|
<h2> Brew Compression </h2>
|
||||||
|
<p>Compresses the text in brews to binary</p>
|
||||||
|
|
||||||
|
<button onClick={this.prime} className='query'>
|
||||||
|
{this.state.pending
|
||||||
|
? <i className='fa fa-spin fa-spinner' />
|
||||||
|
: 'Query Brews'
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
{this.renderPrimed()}
|
||||||
|
|
||||||
|
{this.state.error
|
||||||
|
&& <div className='error'>{this.state.error.toString()}</div>
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewCompress;
|
||||||
10
client/admin/brewCompress/brewCompress.less
Normal file
10
client/admin/brewCompress/brewCompress.less
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.BrewCompress{
|
||||||
|
.removeBox{
|
||||||
|
margin-top: 20px;
|
||||||
|
button{
|
||||||
|
background-color: @red;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
82
client/admin/brewLookup/brewLookup.jsx
Normal file
82
client/admin/brewLookup/brewLookup.jsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
require('./brewLookup.less');
|
||||||
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
const Moment = require('moment');
|
||||||
|
|
||||||
|
|
||||||
|
const BrewLookup = createClass({
|
||||||
|
getDefaultProps() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
query : '',
|
||||||
|
foundBrew : null,
|
||||||
|
searching : false,
|
||||||
|
error : null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleChange(e){
|
||||||
|
this.setState({ query: e.target.value });
|
||||||
|
},
|
||||||
|
lookup(){
|
||||||
|
this.setState({ searching: true, error: null });
|
||||||
|
|
||||||
|
request.get(`/admin/lookup/${this.state.query}`)
|
||||||
|
.then((res)=>this.setState({ foundBrew: res.body }))
|
||||||
|
.catch((err)=>this.setState({ error: err }))
|
||||||
|
.finally(()=>this.setState({ searching: false }));
|
||||||
|
},
|
||||||
|
|
||||||
|
renderFoundBrew(){
|
||||||
|
const brew = this.state.foundBrew;
|
||||||
|
return <div className='foundBrew'>
|
||||||
|
<dl>
|
||||||
|
<dt>Title</dt>
|
||||||
|
<dd>{brew.title}</dd>
|
||||||
|
|
||||||
|
<dt>Authors</dt>
|
||||||
|
<dd>{brew.authors.join(', ')}</dd>
|
||||||
|
|
||||||
|
<dt>Edit Link</dt>
|
||||||
|
<dd><a href={`/edit/${brew.editId}`} target='_blank' rel='noopener noreferrer'>/edit/{brew.editId}</a></dd>
|
||||||
|
|
||||||
|
<dt>Share Link</dt>
|
||||||
|
<dd><a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>/share/{brew.shareId}</a></dd>
|
||||||
|
|
||||||
|
<dt>Last Updated</dt>
|
||||||
|
<dd>{Moment(brew.updatedAt).fromNow()}</dd>
|
||||||
|
|
||||||
|
<dt>Num of Views</dt>
|
||||||
|
<dd>{brew.views}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return <div className='brewLookup'>
|
||||||
|
<h2>Brew Lookup</h2>
|
||||||
|
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id' />
|
||||||
|
<button onClick={this.lookup}>
|
||||||
|
<i className={cx('fa', {
|
||||||
|
'fa-search' : !this.state.searching,
|
||||||
|
'fa-spin fa-spinner' : this.state.searching,
|
||||||
|
})} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{this.state.error
|
||||||
|
&& <div className='error'>{this.state.error.toString()}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{this.state.foundBrew
|
||||||
|
? this.renderFoundBrew()
|
||||||
|
: <div className='noBrew'>No brew found.</div>
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewLookup;
|
||||||
30
client/admin/brewLookup/brewLookup.less
Normal file
30
client/admin/brewLookup/brewLookup.less
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
.brewLookup{
|
||||||
|
input{
|
||||||
|
height : 33px;
|
||||||
|
margin-bottom : 20px;
|
||||||
|
padding : 0px 10px;
|
||||||
|
font-family : monospace;
|
||||||
|
}
|
||||||
|
button{
|
||||||
|
vertical-align : middle;
|
||||||
|
height : 37px;
|
||||||
|
}
|
||||||
|
dl{
|
||||||
|
@maxItemWidth : 132px;
|
||||||
|
dt{
|
||||||
|
float : left;
|
||||||
|
clear : left;
|
||||||
|
width : @maxItemWidth;
|
||||||
|
text-align : right;
|
||||||
|
&::after {
|
||||||
|
content: " : ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dd{
|
||||||
|
height : 1em;
|
||||||
|
margin-left : @maxItemWidth + 6px;
|
||||||
|
padding : 0 0 0.5em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const cx = require('classnames');
|
|
||||||
|
|
||||||
const request = require('superagent');
|
|
||||||
const Moment = require('moment');
|
|
||||||
|
|
||||||
|
|
||||||
const BrewLookup = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
adminKey : '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
query:'',
|
|
||||||
resultBrew : null,
|
|
||||||
searching : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleChange : function(e){
|
|
||||||
this.setState({
|
|
||||||
query : e.target.value
|
|
||||||
})
|
|
||||||
},
|
|
||||||
lookup : function(){
|
|
||||||
this.setState({ searching : true });
|
|
||||||
|
|
||||||
request.get(`/admin/lookup/${this.state.query}`)
|
|
||||||
.query({ admin_key : this.props.adminKey })
|
|
||||||
.end((err, res) => {
|
|
||||||
this.setState({
|
|
||||||
searching : false,
|
|
||||||
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>;
|
|
||||||
|
|
||||||
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'>/edit/{brew.editId}</a></div>
|
|
||||||
<div><a href={'/share/' + brew.shareId} target='_blank'>/share/{brew.shareId}</a></div>
|
|
||||||
<div>{Moment(brew.updatedAt).fromNow()}</div>
|
|
||||||
<div>{brew.views}</div>
|
|
||||||
</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()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = BrewLookup;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
.brewLookup{
|
|
||||||
height : 200px;
|
|
||||||
input{
|
|
||||||
height : 33px;
|
|
||||||
padding : 0px 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var request = require('superagent');
|
|
||||||
|
|
||||||
var BrewSearch = React.createClass({
|
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
admin_key : ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
searchTerm: '',
|
|
||||||
brew : null,
|
|
||||||
searching : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
search : function(){
|
|
||||||
this.setState({
|
|
||||||
searching : true
|
|
||||||
});
|
|
||||||
|
|
||||||
request.get('/homebrew/api/search?id=' + this.state.searchTerm)
|
|
||||||
.query({
|
|
||||||
admin_key : this.props.admin_key,
|
|
||||||
})
|
|
||||||
.end((err, res)=>{
|
|
||||||
console.log(err, res, res.body.brews[0]);
|
|
||||||
this.setState({
|
|
||||||
brew : res.body.brews[0],
|
|
||||||
|
|
||||||
searching : false
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleChange : function(e){
|
|
||||||
this.setState({
|
|
||||||
searchTerm : e.target.value
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleSearchClick : function(){
|
|
||||||
this.search();
|
|
||||||
},
|
|
||||||
|
|
||||||
renderBrew : function(){
|
|
||||||
if(!this.state.brew) return null;
|
|
||||||
return <div className='brew'>
|
|
||||||
<div>Edit id : {this.state.brew.editId}</div>
|
|
||||||
<div>Share id : {this.state.brew.shareId}</div>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='search'>
|
|
||||||
<input type='text' value={this.state.searchTerm} onChange={this.handleChange} />
|
|
||||||
|
|
||||||
<button onClick={this.handleSearchClick}>Search</button>
|
|
||||||
|
|
||||||
{this.renderBrew()}
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = BrewSearch;
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
var request = require('superagent');
|
|
||||||
|
|
||||||
var Moment = require('moment');
|
|
||||||
|
|
||||||
var BrewSearch = require('./brewSearch.jsx');
|
|
||||||
|
|
||||||
var BrewLookup = require('./brewLookup/brewLookup.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
var HomebrewAdmin = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
admin_key : ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
page: 0,
|
|
||||||
count : 20,
|
|
||||||
brewCache : {},
|
|
||||||
total : 0,
|
|
||||||
|
|
||||||
processingOldBrews : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
fetchBrews : function(page){
|
|
||||||
request.get('/api/search')
|
|
||||||
.query({
|
|
||||||
admin_key : this.props.admin_key,
|
|
||||||
count : this.state.count,
|
|
||||||
page : page
|
|
||||||
})
|
|
||||||
.end((err, res)=>{
|
|
||||||
if(err || !res.body || !res.body.brews) return;
|
|
||||||
this.state.brewCache[page] = res.body.brews;
|
|
||||||
this.setState({
|
|
||||||
brewCache : this.state.brewCache,
|
|
||||||
total : res.body.total,
|
|
||||||
count : res.body.count
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this.fetchBrews(this.state.page);
|
|
||||||
},
|
|
||||||
|
|
||||||
changePageTo : function(page){
|
|
||||||
if(!this.state.brewCache[page]){
|
|
||||||
this.fetchBrews(page);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
page : page
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
clearInvalidBrews : function(){
|
|
||||||
request.get('/api/invalid')
|
|
||||||
.query({admin_key : this.props.admin_key})
|
|
||||||
.end((err, res)=>{
|
|
||||||
if(!confirm("This will remove " + res.body.count + " brews. Are you sure?")) return;
|
|
||||||
request.get('/api/invalid')
|
|
||||||
.query({admin_key : this.props.admin_key, do_it : true})
|
|
||||||
.end((err, res)=>{
|
|
||||||
alert("Done!")
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
deleteBrew : function(brewId){
|
|
||||||
if(!confirm("Are you sure you want to delete '" + brewId + "'?")) return;
|
|
||||||
request.get('/api/remove/' + brewId)
|
|
||||||
.query({admin_key : this.props.admin_key})
|
|
||||||
.end(function(err, res){
|
|
||||||
window.location.reload();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
handlePageChange : function(dir){
|
|
||||||
this.changePageTo(this.state.page + dir);
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
renderPagnination : function(){
|
|
||||||
var outOf;
|
|
||||||
if(this.state.total){
|
|
||||||
outOf = this.state.page + ' / ' + Math.round(this.state.total/this.state.count);
|
|
||||||
}
|
|
||||||
return <div className='pagnination'>
|
|
||||||
<i className='fa fa-chevron-left' onClick={this.handlePageChange.bind(this, -1)}/>
|
|
||||||
{outOf}
|
|
||||||
<i className='fa fa-chevron-right' onClick={this.handlePageChange.bind(this, 1)}/>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
renderBrews : function(){
|
|
||||||
var brews = this.state.brewCache[this.state.page] || _.times(this.state.count);
|
|
||||||
return _.map(brews, (brew)=>{
|
|
||||||
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}>
|
|
||||||
<td><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
|
|
||||||
<td><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
|
|
||||||
<td>{Moment(brew.createdAt).fromNow()}</td>
|
|
||||||
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
|
||||||
<td>{Moment(brew.lastViewed).fromNow()}</td>
|
|
||||||
<td>{brew.views}</td>
|
|
||||||
<td>
|
|
||||||
<div className='deleteButton' onClick={this.deleteBrew.bind(this, brew.editId)}>
|
|
||||||
<i className='fa fa-trash' />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderBrewTable : function(){
|
|
||||||
return <div className='brewTable'>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Edit Id</th>
|
|
||||||
<th>Share Id</th>
|
|
||||||
<th>Created At</th>
|
|
||||||
<th>Last Updated</th>
|
|
||||||
<th>Last Viewed</th>
|
|
||||||
<th>Views</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.renderBrews()}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
var self = this;
|
|
||||||
return <div className='homebrewAdmin'>
|
|
||||||
|
|
||||||
<BrewLookup adminKey={this.props.admin_key} />
|
|
||||||
|
|
||||||
{/*
|
|
||||||
<h2>
|
|
||||||
Homebrews - {this.state.total}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{this.renderPagnination()}
|
|
||||||
{this.renderBrewTable()}
|
|
||||||
|
|
||||||
<button className='clearOldButton' onClick={this.clearInvalidBrews}>
|
|
||||||
Clear Old
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<BrewSearch admin_key={this.props.admin_key} />
|
|
||||||
*/}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = HomebrewAdmin;
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
|
|
||||||
.homebrewAdmin{
|
|
||||||
margin-bottom: 80px;
|
|
||||||
.brewTable{
|
|
||||||
table{
|
|
||||||
|
|
||||||
th{
|
|
||||||
padding : 10px;
|
|
||||||
font-weight : 800;
|
|
||||||
}
|
|
||||||
tr:nth-child(even){
|
|
||||||
background-color : fade(@green, 10%);
|
|
||||||
}
|
|
||||||
tr.isEmpty{
|
|
||||||
background-color : fade(@red, 30%);
|
|
||||||
}
|
|
||||||
td{
|
|
||||||
min-width : 100px;
|
|
||||||
padding : 10px;
|
|
||||||
text-align : center;
|
|
||||||
|
|
||||||
&.preview{
|
|
||||||
position : relative;
|
|
||||||
&:hover{
|
|
||||||
.content{
|
|
||||||
display : block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content{
|
|
||||||
position : absolute;
|
|
||||||
display : none;
|
|
||||||
top : 100%;
|
|
||||||
left : 0px;
|
|
||||||
z-index : 1000;
|
|
||||||
max-height : 500px;
|
|
||||||
width : 300px;
|
|
||||||
padding : 30px;
|
|
||||||
background-color : white;
|
|
||||||
font-family : monospace;
|
|
||||||
text-align : left;
|
|
||||||
pointer-events : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.deleteButton{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button.clearOldButton{
|
|
||||||
float : right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
46
client/admin/stats/stats.jsx
Normal file
46
client/admin/stats/stats.jsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
require('./stats.less');
|
||||||
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
|
||||||
|
|
||||||
|
const Stats = createClass({
|
||||||
|
displayName : 'Stats',
|
||||||
|
getDefaultProps(){
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
getInitialState(){
|
||||||
|
return {
|
||||||
|
stats : {
|
||||||
|
totalBrews : 0
|
||||||
|
},
|
||||||
|
fetching : false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidMount(){
|
||||||
|
this.fetchStats();
|
||||||
|
},
|
||||||
|
fetchStats(){
|
||||||
|
this.setState({ fetching: true });
|
||||||
|
request.get('/admin/stats')
|
||||||
|
.then((res)=>this.setState({ stats: res.body }))
|
||||||
|
.finally(()=>this.setState({ fetching: false }));
|
||||||
|
},
|
||||||
|
render(){
|
||||||
|
return <div className='Stats'>
|
||||||
|
<h2> Stats </h2>
|
||||||
|
<dl>
|
||||||
|
<dt>Total Brew Count</dt>
|
||||||
|
<dd>{this.state.stats.totalBrews}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
{this.state.fetching
|
||||||
|
&& <div className='pending'><i className='fa fa-spin fa-spinner' /></div>
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Stats;
|
||||||
28
client/admin/stats/stats.less
Normal file
28
client/admin/stats/stats.less
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
.Stats{
|
||||||
|
position : relative;
|
||||||
|
.pending{
|
||||||
|
position : absolute;
|
||||||
|
top : 0px;
|
||||||
|
left : 0px;
|
||||||
|
height : 100%;
|
||||||
|
width : 100%;
|
||||||
|
background-color : rgba(238,238,238, 0.5);
|
||||||
|
}
|
||||||
|
dl{
|
||||||
|
@maxItemWidth : 132px;
|
||||||
|
dt{
|
||||||
|
float : left;
|
||||||
|
clear : left;
|
||||||
|
width : @maxItemWidth;
|
||||||
|
text-align : right;
|
||||||
|
&::after {
|
||||||
|
content: " : ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dd{
|
||||||
|
margin : 0 0 0 @maxItemWidth + 10px;
|
||||||
|
padding : 0 0 0.5em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
require('./brewRenderer.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
@@ -6,80 +8,75 @@ const Markdown = require('naturalcrit/markdown.js');
|
|||||||
const ErrorBar = require('./errorBar/errorBar.jsx');
|
const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||||
|
|
||||||
//TODO: move to the brew renderer
|
//TODO: move to the brew renderer
|
||||||
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx')
|
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
|
||||||
|
const NotificationPopup = require('./notificationPopup/notificationPopup.jsx');
|
||||||
|
|
||||||
const PAGE_HEIGHT = 1056;
|
const PAGE_HEIGHT = 1056;
|
||||||
const PPR_THRESHOLD = 50;
|
const PPR_THRESHOLD = 50;
|
||||||
|
|
||||||
const BrewRenderer = React.createClass({
|
const BrewRenderer = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
text : '',
|
text : '',
|
||||||
errors : []
|
errors : []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState: function() {
|
getInitialState : function() {
|
||||||
const pages = this.props.text.split('\\page');
|
const pages = this.props.text.split('\\page');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
viewablePageNumber: 0,
|
viewablePageNumber : 0,
|
||||||
height : 0,
|
height : 0,
|
||||||
isMounted : false,
|
isMounted : false,
|
||||||
|
|
||||||
usePPR : true,
|
pages : pages,
|
||||||
|
|
||||||
pages : pages,
|
|
||||||
usePPR : pages.length >= PPR_THRESHOLD,
|
usePPR : pages.length >= PPR_THRESHOLD,
|
||||||
|
|
||||||
errors : []
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
height : 0,
|
height : 0,
|
||||||
pageHeight : PAGE_HEIGHT,
|
|
||||||
lastRender : <div></div>,
|
lastRender : <div></div>,
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount : function() {
|
||||||
this.updateSize();
|
this.updateSize();
|
||||||
window.addEventListener("resize", this.updateSize);
|
window.addEventListener('resize', this.updateSize);
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount : function() {
|
||||||
window.removeEventListener("resize", this.updateSize);
|
window.removeEventListener('resize', this.updateSize);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps : function(nextProps) {
|
||||||
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
|
||||||
|
|
||||||
const pages = nextProps.text.split('\\page');
|
const pages = nextProps.text.split('\\page');
|
||||||
this.setState({
|
this.setState({
|
||||||
pages : pages,
|
pages : pages,
|
||||||
usePPR : pages.length >= PPR_THRESHOLD
|
usePPR : pages.length >= PPR_THRESHOLD
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSize : function() {
|
updateSize : function() {
|
||||||
setTimeout(()=>{
|
|
||||||
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
|
||||||
}, 1);
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
height : this.refs.main.parentNode.clientHeight,
|
height : this.refs.main.parentNode.clientHeight,
|
||||||
isMounted : true
|
isMounted : true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleScroll : function(e){
|
handleScroll : function(e){
|
||||||
this.setState({
|
const target = e.target;
|
||||||
viewablePageNumber : Math.floor(e.target.scrollTop / this.pageHeight)
|
this.setState((prevState)=>({
|
||||||
});
|
viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * prevState.pages.length)
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldRender : function(pageText, index){
|
shouldRender : function(pageText, index){
|
||||||
if(!this.state.isMounted) return false;
|
if(!this.state.isMounted) return false;
|
||||||
|
|
||||||
var viewIndex = this.state.viewablePageNumber;
|
const viewIndex = this.state.viewablePageNumber;
|
||||||
|
if(index == viewIndex - 3) return true;
|
||||||
|
if(index == viewIndex - 2) return true;
|
||||||
if(index == viewIndex - 1) return true;
|
if(index == viewIndex - 1) return true;
|
||||||
if(index == viewIndex) return true;
|
if(index == viewIndex) return true;
|
||||||
if(index == viewIndex + 1) return true;
|
if(index == viewIndex + 1) return true;
|
||||||
|
if(index == viewIndex + 2) return true;
|
||||||
|
if(index == viewIndex + 3) return true;
|
||||||
|
|
||||||
//Check for style tages
|
//Check for style tages
|
||||||
if(pageText.indexOf('<style>') !== -1) return true;
|
if(pageText.indexOf('<style>') !== -1) return true;
|
||||||
@@ -90,7 +87,7 @@ const BrewRenderer = React.createClass({
|
|||||||
renderPageInfo : function(){
|
renderPageInfo : function(){
|
||||||
return <div className='pageInfo'>
|
return <div className='pageInfo'>
|
||||||
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
|
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
|
||||||
</div>
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPPRmsg : function(){
|
renderPPRmsg : function(){
|
||||||
@@ -98,17 +95,17 @@ const BrewRenderer = React.createClass({
|
|||||||
|
|
||||||
return <div className='ppr_msg'>
|
return <div className='ppr_msg'>
|
||||||
Partial Page Renderer enabled, because your brew is so large. May effect rendering.
|
Partial Page Renderer enabled, because your brew is so large. May effect rendering.
|
||||||
</div>
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderDummyPage : function(index){
|
renderDummyPage : function(index){
|
||||||
return <div className='phb' id={`p${index + 1}`} key={index}>
|
return <div className='phb' id={`p${index + 1}`} key={index}>
|
||||||
<i className='fa fa-spinner fa-spin' />
|
<i className='fa fa-spinner fa-spin' />
|
||||||
</div>
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPage : function(pageText, index){
|
renderPage : function(pageText, index){
|
||||||
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{__html:Markdown.render(pageText)}} key={index} />
|
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} key={index} />;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPages : function(){
|
renderPages : function(){
|
||||||
@@ -116,7 +113,7 @@ const BrewRenderer = React.createClass({
|
|||||||
return _.map(this.state.pages, (page, index)=>{
|
return _.map(this.state.pages, (page, index)=>{
|
||||||
if(this.shouldRender(page, index)){
|
if(this.shouldRender(page, index)){
|
||||||
return this.renderPage(page, index);
|
return this.renderPage(page, index);
|
||||||
}else{
|
} else {
|
||||||
return this.renderDummyPage(index);
|
return this.renderDummyPage(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -129,20 +126,27 @@ const BrewRenderer = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='brewRenderer'
|
return (
|
||||||
onScroll={this.handleScroll}
|
<React.Fragment>
|
||||||
ref='main'
|
<div className='brewRenderer'
|
||||||
style={{height : this.state.height}}>
|
onScroll={this.handleScroll}
|
||||||
|
ref='main'
|
||||||
|
style={{ height: this.state.height }}>
|
||||||
|
|
||||||
<ErrorBar errors={this.props.errors} />
|
<ErrorBar errors={this.props.errors} />
|
||||||
<RenderWarnings />
|
<div className='popups'>
|
||||||
|
<RenderWarnings />
|
||||||
|
<NotificationPopup />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='pages' ref='pages'>
|
<div className='pages' ref='pages'>
|
||||||
{this.renderPages()}
|
{this.renderPages()}
|
||||||
</div>
|
</div>
|
||||||
{this.renderPageInfo()}
|
</div>;
|
||||||
{this.renderPPRmsg()}
|
{this.renderPageInfo()}
|
||||||
</div>
|
{this.renderPPRmsg()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,29 +4,8 @@
|
|||||||
position : relative;
|
position : relative;
|
||||||
}
|
}
|
||||||
.brewRenderer{
|
.brewRenderer{
|
||||||
overflow-y : scroll;
|
will-change : transform;
|
||||||
.pageInfo{
|
overflow-y : scroll;
|
||||||
position : absolute;
|
|
||||||
right : 17px;
|
|
||||||
bottom : 0;
|
|
||||||
z-index : 1000;
|
|
||||||
padding : 8px 10px;
|
|
||||||
background-color : #333;
|
|
||||||
font-size : 10px;
|
|
||||||
font-weight : 800;
|
|
||||||
color : white;
|
|
||||||
}
|
|
||||||
.ppr_msg{
|
|
||||||
position : absolute;
|
|
||||||
left : 0px;
|
|
||||||
bottom : 0;
|
|
||||||
z-index : 1000;
|
|
||||||
padding : 8px 10px;
|
|
||||||
background-color : #333;
|
|
||||||
font-size : 10px;
|
|
||||||
font-weight : 800;
|
|
||||||
color : white;
|
|
||||||
}
|
|
||||||
.pages{
|
.pages{
|
||||||
margin : 30px 0px;
|
margin : 30px 0px;
|
||||||
&>.phb{
|
&>.phb{
|
||||||
@@ -36,4 +15,26 @@
|
|||||||
box-shadow : 1px 4px 14px #000;
|
box-shadow : 1px 4px 14px #000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.pageInfo{
|
||||||
|
position : absolute;
|
||||||
|
right : 17px;
|
||||||
|
bottom : 0;
|
||||||
|
z-index : 1000;
|
||||||
|
padding : 8px 10px;
|
||||||
|
background-color : #333;
|
||||||
|
font-size : 10px;
|
||||||
|
font-weight : 800;
|
||||||
|
color : white;
|
||||||
|
}
|
||||||
|
.ppr_msg{
|
||||||
|
position : absolute;
|
||||||
|
left : 0px;
|
||||||
|
bottom : 0;
|
||||||
|
z-index : 1000;
|
||||||
|
padding : 8px 10px;
|
||||||
|
background-color : #333;
|
||||||
|
font-size : 10px;
|
||||||
|
font-weight : 800;
|
||||||
|
color : white;
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
var React = require('react');
|
require('./errorBar.less');
|
||||||
var _ = require('lodash');
|
const React = require('react');
|
||||||
var cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
var ErrorBar = React.createClass({
|
const ErrorBar = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
errors : []
|
errors : []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
hasOpenError : false,
|
hasOpenError : false,
|
||||||
hasCloseError : false,
|
hasCloseError : false,
|
||||||
hasMatchError : false,
|
hasMatchError : false,
|
||||||
|
|
||||||
@@ -19,29 +21,29 @@ var ErrorBar = React.createClass({
|
|||||||
this.hasMatchError = false;
|
this.hasMatchError = false;
|
||||||
|
|
||||||
|
|
||||||
var errors = _.map(this.props.errors, (err, idx) => {
|
const errors = _.map(this.props.errors, (err, idx)=>{
|
||||||
if(err.id == 'OPEN') this.hasOpenError = true;
|
if(err.id == 'OPEN') this.hasOpenError = true;
|
||||||
if(err.id == 'CLOSE') this.hasCloseError = true;
|
if(err.id == 'CLOSE') this.hasCloseError = true;
|
||||||
if(err.id == 'MISMATCH') this.hasMatchError = true;
|
if(err.id == 'MISMATCH') this.hasMatchError = true;
|
||||||
return <li key={idx}>
|
return <li key={idx}>
|
||||||
Line {err.line} : {err.text}, '{err.type}' tag
|
Line {err.line} : {err.text}, '{err.type}' tag
|
||||||
</li>
|
</li>;
|
||||||
});
|
});
|
||||||
|
|
||||||
return <ul>{errors}</ul>
|
return <ul>{errors}</ul>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderProtip : function(){
|
renderProtip : function(){
|
||||||
var msg = [];
|
const msg = [];
|
||||||
if(this.hasOpenError){
|
if(this.hasOpenError){
|
||||||
msg.push(<div>
|
msg.push(<div>
|
||||||
An unmatched opening tag means there's an opened tag that isn't closed, you need to close a tag, like this {'</div>'}. Make sure to match types!
|
An unmatched opening tag means there's an opened tag that isn't closed. You need to close your tags, like this {'</div>'}. Make sure to match types!
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.hasCloseError){
|
if(this.hasCloseError){
|
||||||
msg.push(<div>
|
msg.push(<div>
|
||||||
An unmatched closing tag means you closed a tag without opening it. Either remove it, you check to where you think you opened it.
|
An unmatched closing tag means you closed a tag without opening it. Either remove it, or check to where you think you opened it.
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ var ErrorBar = React.createClass({
|
|||||||
return <div className='protips'>
|
return <div className='protips'>
|
||||||
<h4>Protips!</h4>
|
<h4>Protips!</h4>
|
||||||
{msg}
|
{msg}
|
||||||
</div>
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
@@ -66,7 +68,7 @@ var ErrorBar = React.createClass({
|
|||||||
{this.renderErrors()}
|
{this.renderErrors()}
|
||||||
<hr />
|
<hr />
|
||||||
{this.renderProtip()}
|
{this.renderProtip()}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
require('./notificationPopup.less');
|
||||||
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames'); //Unused variable
|
||||||
|
|
||||||
|
const DISMISS_KEY = 'dismiss_notification7-24-19';
|
||||||
|
|
||||||
|
const NotificationPopup = createClass({
|
||||||
|
getInitialState : function() {
|
||||||
|
return {
|
||||||
|
notifications : {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidMount : function() {
|
||||||
|
this.checkNotifications();
|
||||||
|
window.addEventListener('resize', this.checkNotifications);
|
||||||
|
},
|
||||||
|
componentWillUnmount : function() {
|
||||||
|
window.removeEventListener('resize', this.checkNotifications);
|
||||||
|
},
|
||||||
|
notifications : {
|
||||||
|
psa : function(){
|
||||||
|
return <li key='psa'>
|
||||||
|
<em>Known bug: Grey Shadow Boxes </em> <br />
|
||||||
|
The shadows around certain brew elements such as notes and statblocks might appear as a solid grey box when generating a PDF.
|
||||||
|
<a target='_blank' href='https://old.reddit.com/r/homebrewery/comments/ch3v0d/psa_grey_boxesshadows_around_notes_stat_blocks_etc/'>
|
||||||
|
See this Reddit post
|
||||||
|
</a> for updates and possible workarounds.
|
||||||
|
</li>;
|
||||||
|
},
|
||||||
|
faq : function(){
|
||||||
|
return <li key='faq'>
|
||||||
|
<em>Protect your work! </em> <br />
|
||||||
|
At the moment we do not save a history of your projects, so please make frequent backups of your brews!
|
||||||
|
<a target='_blank' href='https://www.reddit.com/r/homebrewery/comments/adh6lh/faqs_psas_announcements/'>
|
||||||
|
See the FAQ
|
||||||
|
</a> to learn how to avoid losing your work!
|
||||||
|
</li>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checkNotifications : function(){
|
||||||
|
const hideDismiss = localStorage.getItem(DISMISS_KEY);
|
||||||
|
if(hideDismiss) return this.setState({ notifications: {} });
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
notifications : _.mapValues(this.notifications, (fn)=>{ return fn(); }) //Convert notification functions into their return text value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dismiss : function(){
|
||||||
|
localStorage.setItem(DISMISS_KEY, true);
|
||||||
|
this.checkNotifications();
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
if(_.isEmpty(this.state.notifications)) return null;
|
||||||
|
|
||||||
|
return <div className='notificationPopup'>
|
||||||
|
<i className='fa fa-times dismiss' onClick={this.dismiss}/>
|
||||||
|
<i className='fa fa-info-circle info' />
|
||||||
|
<h3>Notice</h3>
|
||||||
|
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
||||||
|
<ul>{_.values(this.state.notifications)}</ul>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = NotificationPopup;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
.popups{
|
||||||
|
position : fixed;
|
||||||
|
top : @navbarHeight;
|
||||||
|
right : 15px;
|
||||||
|
z-index : 10001;
|
||||||
|
width : 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationPopup{
|
||||||
|
position : relative;
|
||||||
|
float : right;
|
||||||
|
display : inline-block;
|
||||||
|
width : 350px;
|
||||||
|
padding : 20px;
|
||||||
|
padding-bottom : 10px;
|
||||||
|
padding-left : 85px;
|
||||||
|
background-color : @blue;
|
||||||
|
color : white;
|
||||||
|
a{
|
||||||
|
color : @steel;
|
||||||
|
font-weight : 800;
|
||||||
|
}
|
||||||
|
i.info{
|
||||||
|
position : absolute;
|
||||||
|
top : 24px;
|
||||||
|
left : 24px;
|
||||||
|
opacity : 0.8;
|
||||||
|
font-size : 2.5em;
|
||||||
|
}
|
||||||
|
i.dismiss{
|
||||||
|
position : absolute;
|
||||||
|
top : 10px;
|
||||||
|
right : 10px;
|
||||||
|
cursor : pointer;
|
||||||
|
opacity : 0.6;
|
||||||
|
&:hover{
|
||||||
|
opacity : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
small{
|
||||||
|
opacity : 0.7;
|
||||||
|
font-size : 0.6em;
|
||||||
|
}
|
||||||
|
h3{
|
||||||
|
font-size : 1.1em;
|
||||||
|
font-weight : 800;
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
margin-top : 15px;
|
||||||
|
font-size : 0.8em;
|
||||||
|
list-style-position : outside;
|
||||||
|
list-style-type : disc;
|
||||||
|
li{
|
||||||
|
font-size : 0.8em;
|
||||||
|
line-height : 1.4em;
|
||||||
|
margin-top : 1.4em;
|
||||||
|
em{
|
||||||
|
font-weight : 800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,145 +1,141 @@
|
|||||||
const React = require('react');
|
require('./editor.less');
|
||||||
const _ = require('lodash');
|
const React = require('react');
|
||||||
const cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
const cx = require('classnames');
|
||||||
const SnippetBar = require('./snippetbar/snippetbar.jsx');
|
|
||||||
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
|
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
||||||
|
const SnippetBar = require('./snippetbar/snippetbar.jsx');
|
||||||
|
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
|
||||||
const splice = function(str, index, inject){
|
|
||||||
return str.slice(0, index) + inject + str.slice(index);
|
|
||||||
};
|
const splice = function(str, index, inject){
|
||||||
|
return str.slice(0, index) + inject + str.slice(index);
|
||||||
const SNIPPETBAR_HEIGHT = 25;
|
};
|
||||||
|
|
||||||
const Editor = React.createClass({
|
const SNIPPETBAR_HEIGHT = 25;
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
const Editor = createClass({
|
||||||
value : '',
|
getDefaultProps : function() {
|
||||||
onChange : ()=>{},
|
return {
|
||||||
|
value : '',
|
||||||
metadata : {},
|
onChange : ()=>{},
|
||||||
onMetadataChange : ()=>{},
|
|
||||||
};
|
metadata : {},
|
||||||
},
|
onMetadataChange : ()=>{},
|
||||||
getInitialState: function() {
|
};
|
||||||
return {
|
},
|
||||||
showMetadataEditor: false
|
getInitialState : function() {
|
||||||
};
|
return {
|
||||||
},
|
showMetadataEditor : false
|
||||||
cursorPosition : {
|
};
|
||||||
line : 0,
|
},
|
||||||
ch : 0
|
cursorPosition : {
|
||||||
},
|
line : 0,
|
||||||
|
ch : 0
|
||||||
componentDidMount: function() {
|
},
|
||||||
this.updateEditorSize();
|
|
||||||
this.highlightPageLines();
|
componentDidMount : function() {
|
||||||
window.addEventListener("resize", this.updateEditorSize);
|
this.updateEditorSize();
|
||||||
},
|
this.highlightPageLines();
|
||||||
componentWillUnmount: function() {
|
window.addEventListener('resize', this.updateEditorSize);
|
||||||
window.removeEventListener("resize", this.updateEditorSize);
|
},
|
||||||
},
|
componentWillUnmount : function() {
|
||||||
|
window.removeEventListener('resize', this.updateEditorSize);
|
||||||
updateEditorSize : function() {
|
},
|
||||||
let paneHeight = this.refs.main.parentNode.clientHeight;
|
|
||||||
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
updateEditorSize : function() {
|
||||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
let paneHeight = this.refs.main.parentNode.clientHeight;
|
||||||
},
|
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
||||||
|
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
||||||
handleTextChange : function(text){
|
},
|
||||||
this.props.onChange(text);
|
|
||||||
},
|
handleTextChange : function(text){
|
||||||
handleCursorActivty : function(curpos){
|
this.props.onChange(text);
|
||||||
this.cursorPosition = curpos;
|
},
|
||||||
},
|
handleCursorActivty : function(curpos){
|
||||||
handleInject : function(injectText){
|
this.cursorPosition = curpos;
|
||||||
const lines = this.props.value.split('\n');
|
},
|
||||||
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
|
handleInject : function(injectText){
|
||||||
|
const lines = this.props.value.split('\n');
|
||||||
this.handleTextChange(lines.join('\n'));
|
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
|
||||||
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
|
|
||||||
},
|
this.handleTextChange(lines.join('\n'));
|
||||||
handgleToggle : function(){
|
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
|
||||||
this.setState({
|
},
|
||||||
showMetadataEditor : !this.state.showMetadataEditor
|
handgleToggle : function(){
|
||||||
})
|
this.setState({
|
||||||
},
|
showMetadataEditor : !this.state.showMetadataEditor
|
||||||
|
});
|
||||||
getCurrentPage : function(){
|
},
|
||||||
const lines = this.props.value.split('\n').slice(0, this.cursorPosition.line + 1);
|
|
||||||
return _.reduce(lines, (r, line) => {
|
getCurrentPage : function(){
|
||||||
if(line.indexOf('\\page') !== -1) r++;
|
const lines = this.props.value.split('\n').slice(0, this.cursorPosition.line + 1);
|
||||||
return r;
|
return _.reduce(lines, (r, line)=>{
|
||||||
}, 1);
|
if(line.indexOf('\\page') !== -1) r++;
|
||||||
},
|
return r;
|
||||||
|
}, 1);
|
||||||
highlightPageLines : function(){
|
},
|
||||||
if(!this.refs.codeEditor) return;
|
|
||||||
const codeMirror = this.refs.codeEditor.codeMirror;
|
highlightPageLines : function(){
|
||||||
|
if(!this.refs.codeEditor) return;
|
||||||
const lineNumbers = _.reduce(this.props.value.split('\n'), (r, line, lineNumber)=>{
|
const codeMirror = this.refs.codeEditor.codeMirror;
|
||||||
if(line.indexOf('\\page') !== -1){
|
|
||||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
const lineNumbers = _.reduce(this.props.value.split('\n'), (r, line, lineNumber)=>{
|
||||||
r.push(lineNumber);
|
if(line.indexOf('\\page') !== -1){
|
||||||
}
|
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||||
return r;
|
r.push(lineNumber);
|
||||||
}, []);
|
}
|
||||||
return lineNumbers
|
return r;
|
||||||
},
|
}, []);
|
||||||
|
return lineNumbers;
|
||||||
|
},
|
||||||
brewJump : function(){
|
|
||||||
const currentPage = this.getCurrentPage();
|
|
||||||
window.location.hash = 'p' + currentPage;
|
brewJump : function(){
|
||||||
},
|
const currentPage = this.getCurrentPage();
|
||||||
|
window.location.hash = `p${currentPage}`;
|
||||||
//Called when there are changes to the editor's dimensions
|
},
|
||||||
update : function(){
|
|
||||||
this.refs.codeEditor.updateSize();
|
//Called when there are changes to the editor's dimensions
|
||||||
},
|
update : function(){
|
||||||
|
this.refs.codeEditor.updateSize();
|
||||||
renderMetadataEditor : function(){
|
},
|
||||||
if(!this.state.showMetadataEditor) return;
|
|
||||||
return <MetadataEditor
|
renderMetadataEditor : function(){
|
||||||
metadata={this.props.metadata}
|
if(!this.state.showMetadataEditor) return;
|
||||||
onChange={this.props.onMetadataChange}
|
return <MetadataEditor
|
||||||
/>
|
metadata={this.props.metadata}
|
||||||
},
|
onChange={this.props.onMetadataChange}
|
||||||
|
/>;
|
||||||
render : function(){
|
},
|
||||||
this.highlightPageLines();
|
|
||||||
return(
|
render : function(){
|
||||||
<div className='editor' ref='main'>
|
this.highlightPageLines();
|
||||||
<SnippetBar
|
return (
|
||||||
brew={this.props.value}
|
<div className='editor' ref='main'>
|
||||||
onInject={this.handleInject}
|
<SnippetBar
|
||||||
onToggle={this.handgleToggle}
|
brew={this.props.value}
|
||||||
showmeta={this.state.showMetadataEditor} />
|
onInject={this.handleInject}
|
||||||
{this.renderMetadataEditor()}
|
onToggle={this.handgleToggle}
|
||||||
<CodeEditor
|
showmeta={this.state.showMetadataEditor} />
|
||||||
ref='codeEditor'
|
{this.renderMetadataEditor()}
|
||||||
wrap={true}
|
<CodeEditor
|
||||||
language='gfm'
|
ref='codeEditor'
|
||||||
value={this.props.value}
|
wrap={true}
|
||||||
onChange={this.handleTextChange}
|
language='gfm'
|
||||||
onCursorActivity={this.handleCursorActivty} />
|
value={this.props.value}
|
||||||
|
onChange={this.handleTextChange}
|
||||||
{/*
|
onCursorActivity={this.handleCursorActivty} />
|
||||||
<div className='brewJump' onClick={this.brewJump}>
|
|
||||||
<i className='fa fa-arrow-right' />
|
{/*
|
||||||
</div>
|
<div className='brewJump' onClick={this.brewJump}>
|
||||||
*/}
|
<i className='fa fa-arrow-right' />
|
||||||
</div>
|
</div>
|
||||||
);
|
*/}
|
||||||
}
|
</div>
|
||||||
});
|
);
|
||||||
|
}
|
||||||
module.exports = Editor;
|
});
|
||||||
|
|
||||||
|
module.exports = Editor;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
|
require('./metadataEditor.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
const request = require("superagent");
|
const request = require('superagent');
|
||||||
|
|
||||||
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder']
|
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
|
||||||
|
|
||||||
const MetadataEditor = React.createClass({
|
const MetadataEditor = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
metadata: {
|
metadata : {
|
||||||
editId : null,
|
editId : null,
|
||||||
title : '',
|
title : '',
|
||||||
description : '',
|
description : '',
|
||||||
tags : '',
|
tags : '',
|
||||||
published : false,
|
published : false,
|
||||||
authors : [],
|
authors : [],
|
||||||
systems : []
|
systems : []
|
||||||
},
|
},
|
||||||
onChange : ()=>{}
|
onChange : ()=>{}
|
||||||
};
|
};
|
||||||
@@ -24,12 +26,12 @@ const MetadataEditor = React.createClass({
|
|||||||
handleFieldChange : function(name, e){
|
handleFieldChange : function(name, e){
|
||||||
this.props.onChange(_.merge({}, this.props.metadata, {
|
this.props.onChange(_.merge({}, this.props.metadata, {
|
||||||
[name] : e.target.value
|
[name] : e.target.value
|
||||||
}))
|
}));
|
||||||
},
|
},
|
||||||
handleSystem : function(system, e){
|
handleSystem : function(system, e){
|
||||||
if(e.target.checked){
|
if(e.target.checked){
|
||||||
this.props.metadata.systems.push(system);
|
this.props.metadata.systems.push(system);
|
||||||
}else{
|
} else {
|
||||||
this.props.metadata.systems = _.without(this.props.metadata.systems, system);
|
this.props.metadata.systems = _.without(this.props.metadata.systems, system);
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.metadata);
|
this.props.onChange(this.props.metadata);
|
||||||
@@ -41,10 +43,15 @@ const MetadataEditor = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleDelete : function(){
|
handleDelete : function(){
|
||||||
if(!confirm("are you sure you want to delete this brew?")) return;
|
if(this.props.metadata.authors.length <= 1){
|
||||||
if(!confirm("are you REALLY sure? You will not be able to recover it")) return;
|
if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return;
|
||||||
|
if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return;
|
||||||
|
} else {
|
||||||
|
if(!confirm('Are you sure you want to remove this brew from your collection? This will remove you as an editor, but other owners will still be able to access the document.')) return;
|
||||||
|
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
|
||||||
|
}
|
||||||
|
|
||||||
request.get('/api/remove/' + this.props.metadata.editId)
|
request.delete(`/api/${this.props.metadata.editId}`)
|
||||||
.send()
|
.send()
|
||||||
.end(function(err, res){
|
.end(function(err, res){
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
@@ -67,21 +74,21 @@ const MetadataEditor = React.createClass({
|
|||||||
<input
|
<input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={_.includes(this.props.metadata.systems, val)}
|
checked={_.includes(this.props.metadata.systems, val)}
|
||||||
onChange={this.handleSystem.bind(null, val)} />
|
onChange={(e)=>this.handleSystem(val, e)} />
|
||||||
{val}
|
{val}
|
||||||
</label>
|
</label>;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPublish : function(){
|
renderPublish : function(){
|
||||||
if(this.props.metadata.published){
|
if(this.props.metadata.published){
|
||||||
return <button className='unpublish' onClick={this.handlePublish.bind(null, false)}>
|
return <button className='unpublish' onClick={()=>this.handlePublish(false)}>
|
||||||
<i className='fa fa-ban' /> unpublish
|
<i className='fa fa-ban' /> unpublish
|
||||||
</button>
|
</button>;
|
||||||
}else{
|
} else {
|
||||||
return <button className='publish' onClick={this.handlePublish.bind(null, true)}>
|
return <button className='publish' onClick={()=>this.handlePublish(true)}>
|
||||||
<i className='fa fa-globe' /> publish
|
<i className='fa fa-globe' /> publish
|
||||||
</button>
|
</button>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,7 +102,7 @@ const MetadataEditor = React.createClass({
|
|||||||
<i className='fa fa-trash' /> delete brew
|
<i className='fa fa-trash' /> delete brew
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderAuthors : function(){
|
renderAuthors : function(){
|
||||||
@@ -108,7 +115,7 @@ const MetadataEditor = React.createClass({
|
|||||||
<div className='value'>
|
<div className='value'>
|
||||||
{text}
|
{text}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderShareToReddit : function(){
|
renderShareToReddit : function(){
|
||||||
@@ -117,13 +124,13 @@ const MetadataEditor = React.createClass({
|
|||||||
return <div className='field reddit'>
|
return <div className='field reddit'>
|
||||||
<label>reddit</label>
|
<label>reddit</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
<a href={this.getRedditLink()} target='_blank'>
|
<a href={this.getRedditLink()} target='_blank' rel='noopener noreferrer'>
|
||||||
<button className='publish'>
|
<button className='publish'>
|
||||||
<i className='fa fa-reddit-alien' /> share to reddit
|
<i className='fa fa-reddit-alien' /> share to reddit
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
@@ -132,18 +139,18 @@ const MetadataEditor = React.createClass({
|
|||||||
<label>title</label>
|
<label>title</label>
|
||||||
<input type='text' className='value'
|
<input type='text' className='value'
|
||||||
value={this.props.metadata.title}
|
value={this.props.metadata.title}
|
||||||
onChange={this.handleFieldChange.bind(null, 'title')} />
|
onChange={(e)=>this.handleFieldChange('title', e)} />
|
||||||
</div>
|
</div>
|
||||||
<div className='field description'>
|
<div className='field description'>
|
||||||
<label>description</label>
|
<label>description</label>
|
||||||
<textarea value={this.props.metadata.description} className='value'
|
<textarea value={this.props.metadata.description} className='value'
|
||||||
onChange={this.handleFieldChange.bind(null, 'description')} />
|
onChange={(e)=>this.handleFieldChange('description', e)} />
|
||||||
</div>
|
</div>
|
||||||
{/*}
|
{/*}
|
||||||
<div className='field tags'>
|
<div className='field tags'>
|
||||||
<label>tags</label>
|
<label>tags</label>
|
||||||
<textarea value={this.props.metadata.tags}
|
<textarea value={this.props.metadata.tags}
|
||||||
onChange={this.handleFieldChange.bind(null, 'tags')} />
|
onChange={(e)=>this.handleFieldChange('tags', e)} />
|
||||||
</div>
|
</div>
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
@@ -168,7 +175,7 @@ const MetadataEditor = React.createClass({
|
|||||||
|
|
||||||
{this.renderDelete()}
|
{this.renderDelete()}
|
||||||
|
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
require('./snippetbar.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
@@ -8,14 +10,14 @@ const Snippets = require('./snippets/snippets.js');
|
|||||||
const execute = function(val, brew){
|
const execute = function(val, brew){
|
||||||
if(_.isFunction(val)) return val(brew);
|
if(_.isFunction(val)) return val(brew);
|
||||||
return val;
|
return val;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Snippetbar = React.createClass({
|
const Snippetbar = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
brew : '',
|
brew : '',
|
||||||
onInject : ()=>{},
|
onInject : ()=>{},
|
||||||
onToggle : ()=>{},
|
onToggle : ()=>{},
|
||||||
showmeta : false
|
showmeta : false
|
||||||
@@ -23,7 +25,7 @@ const Snippetbar = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleSnippetClick : function(injectedText){
|
handleSnippetClick : function(injectedText){
|
||||||
this.props.onInject(injectedText)
|
this.props.onInject(injectedText);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSnippetGroups : function(){
|
renderSnippetGroups : function(){
|
||||||
@@ -35,18 +37,18 @@ const Snippetbar = React.createClass({
|
|||||||
snippets={snippetGroup.snippets}
|
snippets={snippetGroup.snippets}
|
||||||
key={snippetGroup.groupName}
|
key={snippetGroup.groupName}
|
||||||
onSnippetClick={this.handleSnippetClick}
|
onSnippetClick={this.handleSnippetClick}
|
||||||
/>
|
/>;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='snippetBar'>
|
return <div className='snippetBar'>
|
||||||
{this.renderSnippetGroups()}
|
{this.renderSnippetGroups()}
|
||||||
<div className={cx('toggleMeta', {selected: this.props.showmeta})}
|
<div className={cx('toggleMeta', { selected: this.props.showmeta })}
|
||||||
onClick={this.props.onToggle}>
|
onClick={this.props.onToggle}>
|
||||||
<i className='fa fa-bars' />
|
<i className='fa fa-bars' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,13 +59,13 @@ module.exports = Snippetbar;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SnippetGroup = React.createClass({
|
const SnippetGroup = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
brew : '',
|
brew : '',
|
||||||
groupName : '',
|
groupName : '',
|
||||||
icon : 'fa-rocket',
|
icon : 'fa-rocket',
|
||||||
snippets : [],
|
snippets : [],
|
||||||
onSnippetClick : function(){},
|
onSnippetClick : function(){},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -72,23 +74,23 @@ const SnippetGroup = React.createClass({
|
|||||||
},
|
},
|
||||||
renderSnippets : function(){
|
renderSnippets : function(){
|
||||||
return _.map(this.props.snippets, (snippet)=>{
|
return _.map(this.props.snippets, (snippet)=>{
|
||||||
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
|
return <div className='snippet' key={snippet.name} onClick={()=>this.handleSnippetClick(snippet)}>
|
||||||
<i className={'fa fa-fw ' + snippet.icon} />
|
<i className={`fa fa-fw ${snippet.icon}`} />
|
||||||
{snippet.name}
|
{snippet.name}
|
||||||
</div>
|
</div>;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='snippetGroup'>
|
return <div className='snippetGroup'>
|
||||||
<div className='text'>
|
<div className='text'>
|
||||||
<i className={'fa fa-fw ' + this.props.icon} />
|
<i className={`fa fa-fw ${this.props.icon}`} />
|
||||||
<span className='groupName'>{this.props.groupName}</span>
|
<span className='groupName'>{this.props.groupName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='dropdown'>
|
<div className='dropdown'>
|
||||||
{this.renderSnippets()}
|
{this.renderSnippets()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
module.exports = function(classname){
|
module.exports = function(classname){
|
||||||
|
|
||||||
classname = classname || _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
classname = _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
||||||
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge'])
|
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge']);
|
||||||
|
|
||||||
classname = classname.toLowerCase();
|
classname = classname.toLowerCase();
|
||||||
|
|
||||||
var hitDie = _.sample([4, 6, 8, 10, 12]);
|
const hitDie = _.sample([4, 6, 8, 10, 12]);
|
||||||
|
|
||||||
var abilityList = ["Strength", "Dexerity", "Constitution", "Wisdom", "Charisma", "Intelligence"];
|
const abilityList = ['Strength', 'Dexerity', 'Constitution', 'Wisdom', 'Charisma', 'Intelligence'];
|
||||||
var skillList = ["Acrobatics ", "Animal Handling", "Arcana", "Athletics", "Deception", "History", "Insight", "Intimidation", "Investigation", "Medicine", "Nature", "Perception", "Performance", "Persuasion", "Religion", "Sleight of Hand", "Stealth", "Survival"];
|
const skillList = ['Acrobatics ', 'Animal Handling', 'Arcana', 'Athletics', 'Deception', 'History', 'Insight', 'Intimidation', 'Investigation', 'Medicine', 'Nature', 'Perception', 'Performance', 'Persuasion', 'Religion', 'Sleight of Hand', 'Stealth', 'Survival'];
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"## Class Features",
|
'## Class Features',
|
||||||
"As a " + classname + ", you gain the following class features",
|
`As a ${classname}, you gain the following class features`,
|
||||||
"#### Hit Points",
|
'#### Hit Points',
|
||||||
"___",
|
'___',
|
||||||
"- **Hit Dice:** 1d" + hitDie + " per " + classname + " level",
|
`- **Hit Dice:** 1d${hitDie} per ${classname} level`,
|
||||||
"- **Hit Points at 1st Level:** " + hitDie + " + your Constitution modifier",
|
`- **Hit Points at 1st Level:** ${hitDie} + your Constitution modifier`,
|
||||||
"- **Hit Points at Higher Levels:** 1d" + hitDie + " (or " + (hitDie/2 + 1) + ") + your Constitution modifier per " + classname + " level after 1st",
|
`- **Hit Points at Higher Levels:** 1d${hitDie} (or ${hitDie/2 + 1}) + your Constitution modifier per ${classname} level after 1st`,
|
||||||
"",
|
'',
|
||||||
"#### Proficiencies",
|
'#### Proficiencies',
|
||||||
"___",
|
'___',
|
||||||
"- **Armor:** " + (_.sampleSize(["Light armor", "Medium armor", "Heavy armor", "Shields"], _.random(0,3)).join(', ') || "None"),
|
`- **Armor:** ${_.sampleSize(['Light armor', 'Medium armor', 'Heavy armor', 'Shields'], _.random(0, 3)).join(', ') || 'None'}`,
|
||||||
"- **Weapons:** " + (_.sampleSize(["Squeegee", "Rubber Chicken", "Simple weapons", "Martial weapons"], _.random(0,2)).join(', ') || "None"),
|
`- **Weapons:** ${_.sampleSize(['Squeegee', 'Rubber Chicken', 'Simple weapons', 'Martial weapons'], _.random(0, 2)).join(', ') || 'None'}`,
|
||||||
"- **Tools:** " + (_.sampleSize(["Artian's tools", "one musical instrument", "Thieve's tools"], _.random(0,2)).join(', ') || "None"),
|
`- **Tools:** ${_.sampleSize(['Artian\'s tools', 'one musical instrument', 'Thieve\'s tools'], _.random(0, 2)).join(', ') || 'None'}`,
|
||||||
"",
|
'',
|
||||||
"___",
|
'___',
|
||||||
"- **Saving Throws:** " + (_.sampleSize(abilityList, 2).join(', ')),
|
`- **Saving Throws:** ${_.sampleSize(abilityList, 2).join(', ')}`,
|
||||||
"- **Skills:** Choose two from " + (_.sampleSize(skillList, _.random(4, 6)).join(', ')),
|
`- **Skills:** Choose two from ${_.sampleSize(skillList, _.random(4, 6)).join(', ')}`,
|
||||||
"",
|
'',
|
||||||
"#### Equipment",
|
'#### Equipment',
|
||||||
"You start with the following equipment, in addition to the equipment granted by your background:",
|
'You start with the following equipment, in addition to the equipment granted by your background:',
|
||||||
"- *(a)* a martial weapon and a shield or *(b)* two martial weapons",
|
'- *(a)* a martial weapon and a shield or *(b)* two martial weapons',
|
||||||
"- *(a)* five javelins or *(b)* any simple melee weapon",
|
'- *(a)* five javelins or *(b)* any simple melee weapon',
|
||||||
"- " + (_.sample(["10 lint fluffs", "1 button", "a cherished lost sock"])),
|
`- ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])}`,
|
||||||
"\n\n\n"
|
'\n\n\n'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,114 +1,114 @@
|
|||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
var features = [
|
const features = [
|
||||||
"Astrological Botany",
|
'Astrological Botany',
|
||||||
"Astrological Chemistry",
|
'Astrological Chemistry',
|
||||||
"Biochemical Sorcery",
|
'Biochemical Sorcery',
|
||||||
"Civil Alchemy",
|
'Civil Alchemy',
|
||||||
"Consecrated Biochemistry",
|
'Consecrated Biochemistry',
|
||||||
"Demonic Anthropology",
|
'Demonic Anthropology',
|
||||||
"Divinatory Mineralogy",
|
'Divinatory Mineralogy',
|
||||||
"Genetic Banishing",
|
'Genetic Banishing',
|
||||||
"Hermetic Geography",
|
'Hermetic Geography',
|
||||||
"Immunological Incantations",
|
'Immunological Incantations',
|
||||||
"Nuclear Illusionism",
|
'Nuclear Illusionism',
|
||||||
"Ritual Astronomy",
|
'Ritual Astronomy',
|
||||||
"Seismological Divination",
|
'Seismological Divination',
|
||||||
"Spiritual Biochemistry",
|
'Spiritual Biochemistry',
|
||||||
"Statistical Occultism",
|
'Statistical Occultism',
|
||||||
"Police Necromancer",
|
'Police Necromancer',
|
||||||
"Sixgun Poisoner",
|
'Sixgun Poisoner',
|
||||||
"Pharmaceutical Gunslinger",
|
'Pharmaceutical Gunslinger',
|
||||||
"Infernal Banker",
|
'Infernal Banker',
|
||||||
"Spell Analyst",
|
'Spell Analyst',
|
||||||
"Gunslinger Corruptor",
|
'Gunslinger Corruptor',
|
||||||
"Torque Interfacer",
|
'Torque Interfacer',
|
||||||
"Exo Interfacer",
|
'Exo Interfacer',
|
||||||
"Gunpowder Torturer",
|
'Gunpowder Torturer',
|
||||||
"Orbital Gravedigger",
|
'Orbital Gravedigger',
|
||||||
"Phased Linguist",
|
'Phased Linguist',
|
||||||
"Mathematical Pharmacist",
|
'Mathematical Pharmacist',
|
||||||
"Plasma Outlaw",
|
'Plasma Outlaw',
|
||||||
"Malefic Chemist",
|
'Malefic Chemist',
|
||||||
"Police Cultist"
|
'Police Cultist'
|
||||||
];
|
];
|
||||||
|
|
||||||
var classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
const classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
|
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
|
||||||
|
|
||||||
var levels = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"]
|
const levels = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th', '11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th', '20th'];
|
||||||
|
|
||||||
var profBonus = [2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6];
|
const profBonus = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6];
|
||||||
|
|
||||||
var getFeature = (level)=>{
|
const getFeature = (level)=>{
|
||||||
var res = []
|
let res = [];
|
||||||
if(_.includes([4,6,8,12,14,16,19], level+1)){
|
if(_.includes([4, 6, 8, 12, 14, 16, 19], level+1)){
|
||||||
res = ["Ability Score Improvement"]
|
res = ['Ability Score Improvement'];
|
||||||
}
|
}
|
||||||
res = _.union(res, _.sampleSize(features, _.sample([0,1,1,1,1,1])));
|
res = _.union(res, _.sampleSize(features, _.sample([0, 1, 1, 1, 1, 1])));
|
||||||
if(!res.length) return "─";
|
if(!res.length) return '─';
|
||||||
return res.join(', ');
|
return res.join(', ');
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
full : function(){
|
full : function(){
|
||||||
var classname = _.sample(classnames)
|
const classname = _.sample(classnames);
|
||||||
|
|
||||||
var maxes = [4,3,3,3,3,2,2,1,1]
|
const maxes = [4, 3, 3, 3, 3, 2, 2, 1, 1];
|
||||||
var drawSlots = function(Slots){
|
const drawSlots = function(Slots){
|
||||||
var slots = Number(Slots);
|
let slots = Number(Slots);
|
||||||
return _.times(9, function(i){
|
return _.times(9, function(i){
|
||||||
var max = maxes[i];
|
const max = maxes[i];
|
||||||
if(slots < 1) return "—";
|
if(slots < 1) return '—';
|
||||||
var res = _.min([max, slots]);
|
const res = _.min([max, slots]);
|
||||||
slots -= res;
|
slots -= res;
|
||||||
return res;
|
return res;
|
||||||
}).join(' | ')
|
}).join(' | ');
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
var cantrips = 3;
|
let cantrips = 3;
|
||||||
var spells = 1;
|
let spells = 1;
|
||||||
var slots = 2;
|
let slots = 2;
|
||||||
return "<div class='classTable wide'>\n##### The " + classname + "\n" +
|
return `<div class='classTable wide'>\n##### The ${classname}\n` +
|
||||||
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
|
`| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n`+
|
||||||
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
|
`|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n${
|
||||||
_.map(levels, function(levelName, level){
|
_.map(levels, function(levelName, level){
|
||||||
var res = [
|
const res = [
|
||||||
levelName,
|
levelName,
|
||||||
"+" + profBonus[level],
|
`+${profBonus[level]}`,
|
||||||
getFeature(level),
|
getFeature(level),
|
||||||
cantrips,
|
cantrips,
|
||||||
spells,
|
spells,
|
||||||
drawSlots(slots)
|
drawSlots(slots)
|
||||||
].join(' | ');
|
].join(' | ');
|
||||||
|
|
||||||
cantrips += _.random(0,1);
|
cantrips += _.random(0, 1);
|
||||||
spells += _.random(0,1);
|
spells += _.random(0, 1);
|
||||||
slots += _.random(0,2);
|
slots += _.random(0, 2);
|
||||||
|
|
||||||
return "| " + res + " |";
|
return `| ${res} |`;
|
||||||
}).join('\n') +'\n</div>\n\n';
|
}).join('\n')}\n</div>\n\n`;
|
||||||
},
|
},
|
||||||
|
|
||||||
half : function(){
|
half : function(){
|
||||||
var classname = _.sample(classnames)
|
const classname = _.sample(classnames);
|
||||||
|
|
||||||
var featureScore = 1
|
let featureScore = 1;
|
||||||
return "<div class='classTable'>\n##### The " + classname + "\n" +
|
return `<div class='classTable'>\n##### The ${classname}\n` +
|
||||||
"| Level | Proficiency Bonus | Features | " + _.sample(features) + "|\n" +
|
`| Level | Proficiency Bonus | Features | ${_.sample(features)}|\n` +
|
||||||
"|:---:|:---:|:---|:---:|\n" +
|
`|:---:|:---:|:---|:---:|\n${
|
||||||
_.map(levels, function(levelName, level){
|
_.map(levels, function(levelName, level){
|
||||||
var res = [
|
const res = [
|
||||||
levelName,
|
levelName,
|
||||||
"+" + profBonus[level],
|
`+${profBonus[level]}`,
|
||||||
getFeature(level),
|
getFeature(level),
|
||||||
"+" + featureScore
|
`+${featureScore}`
|
||||||
].join(' | ');
|
].join(' | ');
|
||||||
|
|
||||||
featureScore += _.random(0,1);
|
featureScore += _.random(0, 1);
|
||||||
|
|
||||||
return "| " + res + " |";
|
return `| ${res} |`;
|
||||||
}).join('\n') +'\n</div>\n\n';
|
}).join('\n')}\n</div>\n\n`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,104 +1,104 @@
|
|||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
var titles = [
|
const titles = [
|
||||||
"The Burning Gallows",
|
'The Burning Gallows',
|
||||||
"The Ring of Nenlast",
|
'The Ring of Nenlast',
|
||||||
"Below the Blind Tavern",
|
'Below the Blind Tavern',
|
||||||
"Below the Hungering River",
|
'Below the Hungering River',
|
||||||
"Before Bahamut's Land",
|
'Before Bahamut\'s Land',
|
||||||
"The Cruel Grave from Within",
|
'The Cruel Grave from Within',
|
||||||
"The Strength of Trade Road",
|
'The Strength of Trade Road',
|
||||||
"Through The Raven Queen's Worlds",
|
'Through The Raven Queen\'s Worlds',
|
||||||
"Within the Settlement",
|
'Within the Settlement',
|
||||||
"The Crown from Within",
|
'The Crown from Within',
|
||||||
"The Merchant Within the Battlefield",
|
'The Merchant Within the Battlefield',
|
||||||
"Ioun's Fading Traveler",
|
'Ioun\'s Fading Traveler',
|
||||||
"The Legion Ingredient",
|
'The Legion Ingredient',
|
||||||
"The Explorer Lure",
|
'The Explorer Lure',
|
||||||
"Before the Charming Badlands",
|
'Before the Charming Badlands',
|
||||||
"The Living Dead Above the Fearful Cage",
|
'The Living Dead Above the Fearful Cage',
|
||||||
"Vecna's Hidden Sage",
|
'Vecna\'s Hidden Sage',
|
||||||
"Bahamut's Demonspawn",
|
'Bahamut\'s Demonspawn',
|
||||||
"Across Gruumsh's Elemental Chaos",
|
'Across Gruumsh\'s Elemental Chaos',
|
||||||
"The Blade of Orcus",
|
'The Blade of Orcus',
|
||||||
"Beyond Revenge",
|
'Beyond Revenge',
|
||||||
"Brain of Insanity",
|
'Brain of Insanity',
|
||||||
"Breed Battle!, A New Beginning",
|
'Breed Battle!, A New Beginning',
|
||||||
"Evil Lake, A New Beginning",
|
'Evil Lake, A New Beginning',
|
||||||
"Invasion of the Gigantic Cat, Part II",
|
'Invasion of the Gigantic Cat, Part II',
|
||||||
"Kraken War 2020",
|
'Kraken War 2020',
|
||||||
"The Body Whisperers",
|
'The Body Whisperers',
|
||||||
"The Diabolical Tales of the Ape-Women",
|
'The Diabolical Tales of the Ape-Women',
|
||||||
"The Doctor Immortal",
|
'The Doctor Immortal',
|
||||||
"The Doctor from Heaven",
|
'The Doctor from Heaven',
|
||||||
"The Graveyard",
|
'The Graveyard',
|
||||||
"Azure Core",
|
'Azure Core',
|
||||||
"Core Battle",
|
'Core Battle',
|
||||||
"Core of Heaven: The Guardian of Amazement",
|
'Core of Heaven: The Guardian of Amazement',
|
||||||
"Deadly Amazement III",
|
'Deadly Amazement III',
|
||||||
"Dry Chaos IX",
|
'Dry Chaos IX',
|
||||||
"Gate Thunder",
|
'Gate Thunder',
|
||||||
"Guardian: Skies of the Dark Wizard",
|
'Guardian: Skies of the Dark Wizard',
|
||||||
"Lute of Eternity",
|
'Lute of Eternity',
|
||||||
"Mercury's Planet: Brave Evolution",
|
'Mercury\'s Planet: Brave Evolution',
|
||||||
"Ruby of Atlantis: The Quake of Peace",
|
'Ruby of Atlantis: The Quake of Peace',
|
||||||
"Sky of Zelda: The Thunder of Force",
|
'Sky of Zelda: The Thunder of Force',
|
||||||
"Vyse's Skies",
|
'Vyse\'s Skies',
|
||||||
"White Greatness III",
|
'White Greatness III',
|
||||||
"Yellow Divinity",
|
'Yellow Divinity',
|
||||||
"Zidane's Ghost"
|
'Zidane\'s Ghost'
|
||||||
];
|
];
|
||||||
|
|
||||||
var subtitles = [
|
const subtitles = [
|
||||||
"In an ominous universe, a botanist opposes terrorism.",
|
'In an ominous universe, a botanist opposes terrorism.',
|
||||||
"In a demon-haunted city, in an age of lies and hate, a physicist tries to find an ancient treasure and battles a mob of aliens.",
|
'In a demon-haunted city, in an age of lies and hate, a physicist tries to find an ancient treasure and battles a mob of aliens.',
|
||||||
"In a land of corruption, two cyberneticists and a dungeon delver search for freedom.",
|
'In a land of corruption, two cyberneticists and a dungeon delver search for freedom.',
|
||||||
"In an evil empire of horror, two rangers battle the forces of hell.",
|
'In an evil empire of horror, two rangers battle the forces of hell.',
|
||||||
"In a lost city, in an age of sorcery, a librarian quests for revenge.",
|
'In a lost city, in an age of sorcery, a librarian quests for revenge.',
|
||||||
"In a universe of illusions and danger, three time travellers and an adventurer search for justice.",
|
'In a universe of illusions and danger, three time travellers and an adventurer search for justice.',
|
||||||
"In a forgotten universe of barbarism, in an era of terror and mysticism, a virtual reality programmer and a spy try to find vengance and battle crime.",
|
'In a forgotten universe of barbarism, in an era of terror and mysticism, a virtual reality programmer and a spy try to find vengance and battle crime.',
|
||||||
"In a universe of demons, in an era of insanity and ghosts, three bodyguards and a bodyguard try to find vengance.",
|
'In a universe of demons, in an era of insanity and ghosts, three bodyguards and a bodyguard try to find vengance.',
|
||||||
"In a kingdom of corruption and battle, seven artificial intelligences try to save the last living fertile woman.",
|
'In a kingdom of corruption and battle, seven artificial intelligences try to save the last living fertile woman.',
|
||||||
"In a universe of virutal reality and agony, in an age of ghosts and ghosts, a fortune-teller and a wanderer try to avert the apocalypse.",
|
'In a universe of virutal reality and agony, in an age of ghosts and ghosts, a fortune-teller and a wanderer try to avert the apocalypse.',
|
||||||
"In a crime-infested kingdom, three martial artists quest for the truth and oppose evil.",
|
'In a crime-infested kingdom, three martial artists quest for the truth and oppose evil.',
|
||||||
"In a terrifying universe of lost souls, in an era of lost souls, eight dancers fight evil.",
|
'In a terrifying universe of lost souls, in an era of lost souls, eight dancers fight evil.',
|
||||||
"In a galaxy of confusion and insanity, three martial artists and a duke battle a mob of psychics.",
|
'In a galaxy of confusion and insanity, three martial artists and a duke battle a mob of psychics.',
|
||||||
"In an amazing kingdom, a wizard and a secretary hope to prevent the destruction of mankind.",
|
'In an amazing kingdom, a wizard and a secretary hope to prevent the destruction of mankind.',
|
||||||
"In a kingdom of deception, a reporter searches for fame.",
|
'In a kingdom of deception, a reporter searches for fame.',
|
||||||
"In a hellish empire, a swordswoman and a duke try to find the ultimate weapon and battle a conspiracy.",
|
'In a hellish empire, a swordswoman and a duke try to find the ultimate weapon and battle a conspiracy.',
|
||||||
"In an evil galaxy of illusion, in a time of technology and misery, seven psychiatrists battle crime.",
|
'In an evil galaxy of illusion, in a time of technology and misery, seven psychiatrists battle crime.',
|
||||||
"In a dark city of confusion, three swordswomen and a singer battle lawlessness.",
|
'In a dark city of confusion, three swordswomen and a singer battle lawlessness.',
|
||||||
"In an ominous empire, in an age of hate, two philosophers and a student try to find justice and battle a mob of mages intent on stealing the souls of the innocent.",
|
'In an ominous empire, in an age of hate, two philosophers and a student try to find justice and battle a mob of mages intent on stealing the souls of the innocent.',
|
||||||
"In a kingdom of panic, six adventurers oppose lawlessness.",
|
'In a kingdom of panic, six adventurers oppose lawlessness.',
|
||||||
"In a land of dreams and hopelessness, three hackers and a cyborg search for justice.",
|
'In a land of dreams and hopelessness, three hackers and a cyborg search for justice.',
|
||||||
"On a planet of mysticism, three travelers and a fire fighter quest for the ultimate weapon and oppose evil.",
|
'On a planet of mysticism, three travelers and a fire fighter quest for the ultimate weapon and oppose evil.',
|
||||||
"In a wicked universe, five seers fight lawlessness.",
|
'In a wicked universe, five seers fight lawlessness.',
|
||||||
"In a kingdom of death, in an era of illusion and blood, four colonists search for fame.",
|
'In a kingdom of death, in an era of illusion and blood, four colonists search for fame.',
|
||||||
"In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.",
|
'In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.',
|
||||||
"In a cursed empire, five inventors oppose terrorism.",
|
'In a cursed empire, five inventors oppose terrorism.',
|
||||||
"On a crime-ridden planet of conspiracy, a watchman and an artificial intelligence try to find love and oppose lawlessness.",
|
'On a crime-ridden planet of conspiracy, a watchman and an artificial intelligence try to find love and oppose lawlessness.',
|
||||||
"In a forgotten land, a reporter and a spy try to stop the apocalypse.",
|
'In a forgotten land, a reporter and a spy try to stop the apocalypse.',
|
||||||
"In a forbidden land of prophecy, a scientist and an archivist oppose a cabal of barbarians intent on stealing the souls of the innocent.",
|
'In a forbidden land of prophecy, a scientist and an archivist oppose a cabal of barbarians intent on stealing the souls of the innocent.',
|
||||||
"On an infernal world of illusion, a grave robber and a watchman try to find revenge and combat a syndicate of mages intent on stealing the source of all magic.",
|
'On an infernal world of illusion, a grave robber and a watchman try to find revenge and combat a syndicate of mages intent on stealing the source of all magic.',
|
||||||
"In a galaxy of dark magic, four fighters seek freedom.",
|
'In a galaxy of dark magic, four fighters seek freedom.',
|
||||||
"In an empire of deception, six tomb-robbers quest for the ultimate weapon and combat an army of raiders.",
|
'In an empire of deception, six tomb-robbers quest for the ultimate weapon and combat an army of raiders.',
|
||||||
"In a kingdom of corruption and lost souls, in an age of panic, eight planetologists oppose evil.",
|
'In a kingdom of corruption and lost souls, in an age of panic, eight planetologists oppose evil.',
|
||||||
"In a galaxy of misery and hopelessness, in a time of agony and pain, five planetologists search for vengance.",
|
'In a galaxy of misery and hopelessness, in a time of agony and pain, five planetologists search for vengance.',
|
||||||
"In a universe of technology and insanity, in a time of sorcery, a computer techician quests for hope.",
|
'In a universe of technology and insanity, in a time of sorcery, a computer techician quests for hope.',
|
||||||
"On a planet of dark magic and barbarism, in an age of horror and blasphemy, seven librarians search for fame.",
|
'On a planet of dark magic and barbarism, in an age of horror and blasphemy, seven librarians search for fame.',
|
||||||
"In an empire of dark magic, in a time of blood and illusions, four monks try to find the ultimate weapon and combat terrorism.",
|
'In an empire of dark magic, in a time of blood and illusions, four monks try to find the ultimate weapon and combat terrorism.',
|
||||||
"In a forgotten empire of dark magic, six kings try to prevent the destruction of mankind.",
|
'In a forgotten empire of dark magic, six kings try to prevent the destruction of mankind.',
|
||||||
"In a galaxy of dark magic and horror, in an age of hopelessness, four marines and an outlaw combat evil.",
|
'In a galaxy of dark magic and horror, in an age of hopelessness, four marines and an outlaw combat evil.',
|
||||||
"In a mysterious city of illusion, in an age of computerization, a witch-hunter tries to find the ultimate weapon and opposes an evil corporation.",
|
'In a mysterious city of illusion, in an age of computerization, a witch-hunter tries to find the ultimate weapon and opposes an evil corporation.',
|
||||||
"In a damned kingdom of technology, a virtual reality programmer and a fighter seek fame.",
|
'In a damned kingdom of technology, a virtual reality programmer and a fighter seek fame.',
|
||||||
"In a hellish kingdom, in an age of blasphemy and blasphemy, an astrologer searches for fame.",
|
'In a hellish kingdom, in an age of blasphemy and blasphemy, an astrologer searches for fame.',
|
||||||
"In a damned world of devils, an alien and a ranger quest for love and oppose a syndicate of demons.",
|
'In a damned world of devils, an alien and a ranger quest for love and oppose a syndicate of demons.',
|
||||||
"In a cursed galaxy, in a time of pain, seven librarians hope to avert the apocalypse.",
|
'In a cursed galaxy, in a time of pain, seven librarians hope to avert the apocalypse.',
|
||||||
"In a crime-infested galaxy, in an era of hopelessness and panic, three champions and a grave robber try to solve the ultimate crime."
|
'In a crime-infested galaxy, in an era of hopelessness and panic, three champions and a grave robber try to solve the ultimate crime.'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
module.exports = () => {
|
module.exports = ()=>{
|
||||||
return `<style>
|
return `<style>
|
||||||
.phb#p1{ text-align:center; }
|
.phb#p1{ text-align:center; }
|
||||||
.phb#p1:after{ display:none; }
|
.phb#p1:after{ display:none; }
|
||||||
@@ -113,5 +113,5 @@ module.exports = () => {
|
|||||||
##### ${_.sample(subtitles)}
|
##### ${_.sample(subtitles)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
\\page`
|
\\page`;
|
||||||
}
|
};
|
||||||
@@ -1,43 +1,43 @@
|
|||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
var ClassFeatureGen = require('./classfeature.gen.js');
|
const ClassFeatureGen = require('./classfeature.gen.js');
|
||||||
|
|
||||||
var ClassTableGen = require('./classtable.gen.js');
|
const ClassTableGen = require('./classtable.gen.js');
|
||||||
|
|
||||||
module.exports = function(){
|
module.exports = function(){
|
||||||
|
|
||||||
var classname = _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
const classname = _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'])
|
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge']);
|
||||||
|
|
||||||
|
|
||||||
var image = _.sample(_.map([
|
const image = _.sample(_.map([
|
||||||
"http://orig01.deviantart.net/4682/f/2007/099/f/c/bard_stick_figure_by_wrpigeek.png",
|
'http://orig01.deviantart.net/4682/f/2007/099/f/c/bard_stick_figure_by_wrpigeek.png',
|
||||||
"http://img07.deviantart.net/a3c9/i/2007/099/3/a/archer_stick_figure_by_wrpigeek.png",
|
'http://img07.deviantart.net/a3c9/i/2007/099/3/a/archer_stick_figure_by_wrpigeek.png',
|
||||||
"http://pre04.deviantart.net/d596/th/pre/f/2007/099/5/2/adventurer_stick_figure_by_wrpigeek.png",
|
'http://pre04.deviantart.net/d596/th/pre/f/2007/099/5/2/adventurer_stick_figure_by_wrpigeek.png',
|
||||||
"http://img13.deviantart.net/d501/i/2007/099/d/4/black_mage_stick_figure_by_wrpigeek.png",
|
'http://img13.deviantart.net/d501/i/2007/099/d/4/black_mage_stick_figure_by_wrpigeek.png',
|
||||||
"http://img09.deviantart.net/5cf3/i/2007/099/d/d/dark_knight_stick_figure_by_wrpigeek.png",
|
'http://img09.deviantart.net/5cf3/i/2007/099/d/d/dark_knight_stick_figure_by_wrpigeek.png',
|
||||||
"http://pre01.deviantart.net/7a34/th/pre/f/2007/099/6/3/monk_stick_figure_by_wrpigeek.png",
|
'http://pre01.deviantart.net/7a34/th/pre/f/2007/099/6/3/monk_stick_figure_by_wrpigeek.png',
|
||||||
"http://img11.deviantart.net/5dcc/i/2007/099/d/1/mystic_knight_stick_figure_by_wrpigeek.png",
|
'http://img11.deviantart.net/5dcc/i/2007/099/d/1/mystic_knight_stick_figure_by_wrpigeek.png',
|
||||||
"http://pre08.deviantart.net/ad45/th/pre/f/2007/099/a/0/thief_stick_figure_by_wrpigeek.png",
|
'http://pre08.deviantart.net/ad45/th/pre/f/2007/099/a/0/thief_stick_figure_by_wrpigeek.png',
|
||||||
], function(url){
|
], function(url){
|
||||||
return "<img src = '" + url + "' style='max-width:8cm;max-height:25cm' />"
|
return `<img src = '${url}' style='max-width:8cm;max-height:25cm' />`;
|
||||||
}))
|
}));
|
||||||
|
|
||||||
|
|
||||||
return [
|
return `${[
|
||||||
image,
|
image,
|
||||||
"",
|
'',
|
||||||
"```",
|
'```',
|
||||||
"```",
|
'```',
|
||||||
"<div style='margin-top:240px'></div>\n\n",
|
'<div style=\'margin-top:240px\'></div>\n\n',
|
||||||
"## " + classname,
|
`## ${classname}`,
|
||||||
"Cool intro stuff will go here",
|
'Cool intro stuff will go here',
|
||||||
|
|
||||||
"\\page",
|
'\\page',
|
||||||
ClassTableGen(classname),
|
ClassTableGen(classname),
|
||||||
ClassFeatureGen(classname),
|
ClassFeatureGen(classname),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
].join('\n') + '\n\n\n';
|
].join('\n')}\n\n\n`;
|
||||||
};
|
};
|
||||||
@@ -1,60 +1,60 @@
|
|||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
var spellNames = [
|
const spellNames = [
|
||||||
"Astral Rite of Acne",
|
'Astral Rite of Acne',
|
||||||
"Create Acne",
|
'Create Acne',
|
||||||
"Cursed Ramen Erruption",
|
'Cursed Ramen Erruption',
|
||||||
"Dark Chant of the Dentists",
|
'Dark Chant of the Dentists',
|
||||||
"Erruption of Immaturity",
|
'Erruption of Immaturity',
|
||||||
"Flaming Disc of Inconvenience",
|
'Flaming Disc of Inconvenience',
|
||||||
"Heal Bad Hygene",
|
'Heal Bad Hygene',
|
||||||
"Heavenly Transfiguration of the Cream Devil",
|
'Heavenly Transfiguration of the Cream Devil',
|
||||||
"Hellish Cage of Mucus",
|
'Hellish Cage of Mucus',
|
||||||
"Irritate Peanut Butter Fairy",
|
'Irritate Peanut Butter Fairy',
|
||||||
"Luminous Erruption of Tea",
|
'Luminous Erruption of Tea',
|
||||||
"Mystic Spell of the Poser",
|
'Mystic Spell of the Poser',
|
||||||
"Sorcerous Enchantment of the Chimneysweep",
|
'Sorcerous Enchantment of the Chimneysweep',
|
||||||
"Steak Sauce Ray",
|
'Steak Sauce Ray',
|
||||||
"Talk to Groupie",
|
'Talk to Groupie',
|
||||||
"Astonishing Chant of Chocolate",
|
'Astonishing Chant of Chocolate',
|
||||||
"Astounding Pasta Puddle",
|
'Astounding Pasta Puddle',
|
||||||
"Ball of Annoyance",
|
'Ball of Annoyance',
|
||||||
"Cage of Yarn",
|
'Cage of Yarn',
|
||||||
"Control Noodles Elemental",
|
'Control Noodles Elemental',
|
||||||
"Create Nervousness",
|
'Create Nervousness',
|
||||||
"Cure Baldness",
|
'Cure Baldness',
|
||||||
"Cursed Ritual of Bad Hair",
|
'Cursed Ritual of Bad Hair',
|
||||||
"Dispell Piles in Dentist",
|
'Dispell Piles in Dentist',
|
||||||
"Eliminate Florists",
|
'Eliminate Florists',
|
||||||
"Illusionary Transfiguration of the Babysitter",
|
'Illusionary Transfiguration of the Babysitter',
|
||||||
"Necromantic Armor of Salad Dressing",
|
'Necromantic Armor of Salad Dressing',
|
||||||
"Occult Transfiguration of Foot Fetish",
|
'Occult Transfiguration of Foot Fetish',
|
||||||
"Protection from Mucus Giant",
|
'Protection from Mucus Giant',
|
||||||
"Tinsel Blast",
|
'Tinsel Blast',
|
||||||
"Alchemical Evocation of the Goths",
|
'Alchemical Evocation of the Goths',
|
||||||
"Call Fangirl",
|
'Call Fangirl',
|
||||||
"Divine Spell of Crossdressing",
|
'Divine Spell of Crossdressing',
|
||||||
"Dominate Ramen Giant",
|
'Dominate Ramen Giant',
|
||||||
"Eliminate Vindictiveness in Gym Teacher",
|
'Eliminate Vindictiveness in Gym Teacher',
|
||||||
"Extra-Planar Spell of Irritation",
|
'Extra-Planar Spell of Irritation',
|
||||||
"Induce Whining in Babysitter",
|
'Induce Whining in Babysitter',
|
||||||
"Invoke Complaining",
|
'Invoke Complaining',
|
||||||
"Magical Enchantment of Arrogance",
|
'Magical Enchantment of Arrogance',
|
||||||
"Occult Globe of Salad Dressing",
|
'Occult Globe of Salad Dressing',
|
||||||
"Overwhelming Enchantment of the Chocolate Fairy",
|
'Overwhelming Enchantment of the Chocolate Fairy',
|
||||||
"Sorcerous Dandruff Globe",
|
'Sorcerous Dandruff Globe',
|
||||||
"Spiritual Invocation of the Costumers",
|
'Spiritual Invocation of the Costumers',
|
||||||
"Ultimate Rite of the Confetti Angel",
|
'Ultimate Rite of the Confetti Angel',
|
||||||
"Ultimate Ritual of Mouthwash",
|
'Ultimate Ritual of Mouthwash',
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
spellList : function(){
|
spellList : function(){
|
||||||
var levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level'];
|
const levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level'];
|
||||||
|
|
||||||
var content = _.map(levels, (level)=>{
|
const content = _.map(levels, (level)=>{
|
||||||
var spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{
|
const spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{
|
||||||
return `- ${spell}`;
|
return `- ${spell}`;
|
||||||
}).join('\n');
|
}).join('\n');
|
||||||
return `##### ${level} \n${spells} \n`;
|
return `##### ${level} \n${spells} \n`;
|
||||||
@@ -64,28 +64,28 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
spell : function(){
|
spell : function(){
|
||||||
var level = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th"];
|
const level = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th'];
|
||||||
var spellSchools = ["abjuration", "conjuration", "divination", "enchantment", "evocation", "illusion", "necromancy", "transmutation"];
|
const spellSchools = ['abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation'];
|
||||||
|
|
||||||
|
|
||||||
var components = _.sampleSize(["V", "S", "M"], _.random(1,3)).join(', ');
|
let components = _.sampleSize(['V', 'S', 'M'], _.random(1, 3)).join(', ');
|
||||||
if(components.indexOf("M") !== -1){
|
if(components.indexOf('M') !== -1){
|
||||||
components += " (" + _.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1,3)).join(', ') + ")"
|
components += ` (${_.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1, 3)).join(', ')})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"#### " + _.sample(spellNames),
|
`#### ${_.sample(spellNames)}`,
|
||||||
"*" + _.sample(level) + "-level " + _.sample(spellSchools) + "*",
|
`*${_.sample(level)}-level ${_.sample(spellSchools)}*`,
|
||||||
"___",
|
'___',
|
||||||
"- **Casting Time:** 1 action",
|
'- **Casting Time:** 1 action',
|
||||||
"- **Range:** " + _.sample(["Self", "Touch", "30 feet", "60 feet"]),
|
`- **Range:** ${_.sample(['Self', 'Touch', '30 feet', '60 feet'])}`,
|
||||||
"- **Components:** " + components,
|
`- **Components:** ${components}`,
|
||||||
"- **Duration:** " + _.sample(["Until dispelled", "1 round", "Instantaneous", "Concentration, up to 10 minutes", "1 hour"]),
|
`- **Duration:** ${_.sample(['Until dispelled', '1 round', 'Instantaneous', 'Concentration, up to 10 minutes', '1 hour'])}`,
|
||||||
"",
|
'',
|
||||||
"A flame, equivalent in brightness to a torch, springs from from an object that you touch. ",
|
'A flame, equivalent in brightness to a torch, springs from from an object that you touch. ',
|
||||||
"The effect look like a regular flame, but it creates no heat and doesn't use oxygen. ",
|
'The effect look like a regular flame, but it creates no heat and doesn\'t use oxygen. ',
|
||||||
"A *continual flame* can be covered or hidden but not smothered or quenched.",
|
'A *continual flame* can be covered or hidden but not smothered or quenched.',
|
||||||
"\n\n\n"
|
'\n\n\n'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
@@ -1,196 +1,200 @@
|
|||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
var genList = function(list, max){
|
const genList = function(list, max){
|
||||||
return _.sampleSize(list, _.random(0,max)).join(', ') || "None";
|
return _.sampleSize(list, _.random(0, max)).join(', ') || 'None';
|
||||||
}
|
};
|
||||||
|
|
||||||
var getMonsterName = function(){
|
const getMonsterName = function(){
|
||||||
return _.sample([
|
return _.sample([
|
||||||
"All-devouring Baseball Imp",
|
'All-devouring Baseball Imp',
|
||||||
"All-devouring Gumdrop Wraith",
|
'All-devouring Gumdrop Wraith',
|
||||||
"Chocolate Hydra",
|
'Chocolate Hydra',
|
||||||
"Devouring Peacock",
|
'Devouring Peacock',
|
||||||
"Economy-sized Colossus of the Lemonade Stand",
|
'Economy-sized Colossus of the Lemonade Stand',
|
||||||
"Ghost Pigeon",
|
'Ghost Pigeon',
|
||||||
"Gibbering Duck",
|
'Gibbering Duck',
|
||||||
"Sparklemuffin Peacock Spider",
|
'Sparklemuffin Peacock Spider',
|
||||||
"Gum Elemental",
|
'Gum Elemental',
|
||||||
"Illiterate Construct of the Candy Store",
|
'Illiterate Construct of the Candy Store',
|
||||||
"Ineffable Chihuahua",
|
'Ineffable Chihuahua',
|
||||||
"Irritating Death Hamster",
|
'Irritating Death Hamster',
|
||||||
"Irritating Gold Mouse",
|
'Irritating Gold Mouse',
|
||||||
"Juggernaut Snail",
|
'Juggernaut Snail',
|
||||||
"Juggernaut of the Sock Drawer",
|
'Juggernaut of the Sock Drawer',
|
||||||
"Koala of the Cosmos",
|
'Koala of the Cosmos',
|
||||||
"Mad Koala of the West",
|
'Mad Koala of the West',
|
||||||
"Milk Djinni of the Lemonade Stand",
|
'Milk Djinni of the Lemonade Stand',
|
||||||
"Mind Ferret",
|
'Mind Ferret',
|
||||||
"Mystic Salt Spider",
|
'Mystic Salt Spider',
|
||||||
"Necrotic Halitosis Angel",
|
'Necrotic Halitosis Angel',
|
||||||
"Pinstriped Famine Sheep",
|
'Pinstriped Famine Sheep',
|
||||||
"Ritalin Leech",
|
'Ritalin Leech',
|
||||||
"Shocker Kangaroo",
|
'Shocker Kangaroo',
|
||||||
"Stellar Tennis Juggernaut",
|
'Stellar Tennis Juggernaut',
|
||||||
"Wailing Quail of the Sun",
|
'Wailing Quail of the Sun',
|
||||||
"Angel Pigeon",
|
'Angel Pigeon',
|
||||||
"Anime Sphinx",
|
'Anime Sphinx',
|
||||||
"Bored Avalanche Sheep of the Wasteland",
|
'Bored Avalanche Sheep of the Wasteland',
|
||||||
"Devouring Nougat Sphinx of the Sock Drawer",
|
'Devouring Nougat Sphinx of the Sock Drawer',
|
||||||
"Djinni of the Footlocker",
|
'Djinni of the Footlocker',
|
||||||
"Ectoplasmic Jazz Devil",
|
'Ectoplasmic Jazz Devil',
|
||||||
"Flatuent Angel",
|
'Flatuent Angel',
|
||||||
"Gelatinous Duck of the Dream-Lands",
|
'Gelatinous Duck of the Dream-Lands',
|
||||||
"Gelatinous Mouse",
|
'Gelatinous Mouse',
|
||||||
"Golem of the Footlocker",
|
'Golem of the Footlocker',
|
||||||
"Lich Wombat",
|
'Lich Wombat',
|
||||||
"Mechanical Sloth of the Past",
|
'Mechanical Sloth of the Past',
|
||||||
"Milkshake Succubus",
|
'Milkshake Succubus',
|
||||||
"Puffy Bone Peacock of the East",
|
'Puffy Bone Peacock of the East',
|
||||||
"Rainbow Manatee",
|
'Rainbow Manatee',
|
||||||
"Rune Parrot",
|
'Rune Parrot',
|
||||||
"Sand Cow",
|
'Sand Cow',
|
||||||
"Sinister Vanilla Dragon",
|
'Sinister Vanilla Dragon',
|
||||||
"Snail of the North",
|
'Snail of the North',
|
||||||
"Spider of the Sewer",
|
'Spider of the Sewer',
|
||||||
"Stellar Sawdust Leech",
|
'Stellar Sawdust Leech',
|
||||||
"Storm Anteater of Hell",
|
'Storm Anteater of Hell',
|
||||||
"Stupid Spirit of the Brewery",
|
'Stupid Spirit of the Brewery',
|
||||||
"Time Kangaroo",
|
'Time Kangaroo',
|
||||||
"Tomb Poodle",
|
'Tomb Poodle',
|
||||||
]);
|
]);
|
||||||
}
|
};
|
||||||
|
|
||||||
var getType = function(){
|
const getType = function(){
|
||||||
return _.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast']) + " " + _.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])
|
return `${_.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast'])} ${_.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
var getAlignment = function(){
|
const getAlignment = function(){
|
||||||
return _.sample([
|
return _.sample([
|
||||||
"annoying evil",
|
'annoying evil',
|
||||||
"chaotic gossipy",
|
'chaotic gossipy',
|
||||||
"chaotic sloppy",
|
'chaotic sloppy',
|
||||||
"depressed neutral",
|
'depressed neutral',
|
||||||
"lawful bogus",
|
'lawful bogus',
|
||||||
"lawful coy",
|
'lawful coy',
|
||||||
"manic-depressive evil",
|
'manic-depressive evil',
|
||||||
"narrow-minded neutral",
|
'narrow-minded neutral',
|
||||||
"neutral annoying",
|
'neutral annoying',
|
||||||
"neutral ignorant",
|
'neutral ignorant',
|
||||||
"oedpipal neutral",
|
'oedpipal neutral',
|
||||||
"silly neutral",
|
'silly neutral',
|
||||||
"unoriginal neutral",
|
'unoriginal neutral',
|
||||||
"weird neutral",
|
'weird neutral',
|
||||||
"wordy evil",
|
'wordy evil',
|
||||||
"unaligned"
|
'unaligned'
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
var getStats = function(){
|
const getStats = function(){
|
||||||
return '>|' + _.times(6, function(){
|
return `>|${_.times(6, function(){
|
||||||
var num = _.random(1,20);
|
const num = _.random(1, 20);
|
||||||
var mod = Math.ceil(num/2 - 5)
|
const mod = Math.ceil(num/2 - 5);
|
||||||
return num + " (" + (mod >= 0 ? '+'+mod : mod ) + ")"
|
return `${num} (${mod >= 0 ? `+${mod}` : mod})`;
|
||||||
}).join('|') + '|';
|
}).join('|')}|`;
|
||||||
}
|
};
|
||||||
|
|
||||||
var genAbilities = function(){
|
const genAbilities = function(){
|
||||||
return _.sample([
|
return _.sample([
|
||||||
"> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.",
|
'> ***Pack Tactics.*** These guys work together. Like super well, you don\'t even know.',
|
||||||
"> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.",
|
'> ***Fowl Appearance.*** While the creature remains motionless, it is indistinguishable from a normal chicken.',
|
||||||
]);
|
'> ***Onion Stench.*** Any creatures within 5 feet of this thing develops an irrational craving for onion rings.',
|
||||||
}
|
'> ***Enormous Nose.*** This creature gains advantage on any check involving putting things in its nose.',
|
||||||
|
'> ***Sassiness.*** When questioned, this creature will talk back instead of answering.',
|
||||||
var genAction = function(){
|
'> ***Big Jerk.*** Thinks he is just *waaaay* better than you.',
|
||||||
var name = _.sample([
|
]);
|
||||||
"Abdominal Drop",
|
};
|
||||||
"Airplane Hammer",
|
|
||||||
"Atomic Death Throw",
|
const genAction = function(){
|
||||||
"Bulldog Rake",
|
const name = _.sample([
|
||||||
"Corkscrew Strike",
|
'Abdominal Drop',
|
||||||
"Crossed Splash",
|
'Airplane Hammer',
|
||||||
"Crossface Suplex",
|
'Atomic Death Throw',
|
||||||
"DDT Powerbomb",
|
'Bulldog Rake',
|
||||||
"Dual Cobra Wristlock",
|
'Corkscrew Strike',
|
||||||
"Dual Throw",
|
'Crossed Splash',
|
||||||
"Elbow Hold",
|
'Crossface Suplex',
|
||||||
"Gory Body Sweep",
|
'DDT Powerbomb',
|
||||||
"Heel Jawbreaker",
|
'Dual Cobra Wristlock',
|
||||||
"Jumping Driver",
|
'Dual Throw',
|
||||||
"Open Chin Choke",
|
'Elbow Hold',
|
||||||
"Scorpion Flurry",
|
'Gory Body Sweep',
|
||||||
"Somersault Stump Fists",
|
'Heel Jawbreaker',
|
||||||
"Suffering Wringer",
|
'Jumping Driver',
|
||||||
"Super Hip Submission",
|
'Open Chin Choke',
|
||||||
"Super Spin",
|
'Scorpion Flurry',
|
||||||
"Team Elbow",
|
'Somersault Stump Fists',
|
||||||
"Team Foot",
|
'Suffering Wringer',
|
||||||
"Tilt-a-whirl Chin Sleeper",
|
'Super Hip Submission',
|
||||||
"Tilt-a-whirl Eye Takedown",
|
'Super Spin',
|
||||||
"Turnbuckle Roll"
|
'Team Elbow',
|
||||||
])
|
'Team Foot',
|
||||||
|
'Tilt-a-whirl Chin Sleeper',
|
||||||
return "> ***" + name + ".*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) ";
|
'Tilt-a-whirl Eye Takedown',
|
||||||
}
|
'Turnbuckle Roll'
|
||||||
|
]);
|
||||||
|
|
||||||
module.exports = {
|
return `> ***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `;
|
||||||
|
};
|
||||||
full : function(){
|
|
||||||
return [
|
|
||||||
"___",
|
module.exports = {
|
||||||
"___",
|
|
||||||
"> ## " + getMonsterName(),
|
full : function(){
|
||||||
">*" + getType() + ", " + getAlignment() + "*",
|
return `${[
|
||||||
"> ___",
|
'___',
|
||||||
"> - **Armor Class** " + _.random(10,20),
|
'___',
|
||||||
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
`> ## ${getMonsterName()}`,
|
||||||
"> - **Speed** " + _.random(0,50) + "ft.",
|
`>*${getType()}, ${getAlignment()}*`,
|
||||||
">___",
|
'> ___',
|
||||||
">|STR|DEX|CON|INT|WIS|CHA|",
|
`> - **Armor Class** ${_.random(10, 20)}`,
|
||||||
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
`> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
|
||||||
getStats(),
|
`> - **Speed** ${_.random(0, 50)}ft.`,
|
||||||
">___",
|
'>___',
|
||||||
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
'>|STR|DEX|CON|INT|WIS|CHA|',
|
||||||
"> - **Senses** passive Perception " + _.random(3, 20),
|
'>|:---:|:---:|:---:|:---:|:---:|:---:|',
|
||||||
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
getStats(),
|
||||||
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
'>___',
|
||||||
"> ___",
|
`> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
|
||||||
_.times(_.random(3,6), function(){
|
`> - **Senses** passive Perception ${_.random(3, 20)}`,
|
||||||
return genAbilities()
|
`> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
|
||||||
}).join('\n>\n'),
|
`> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
|
||||||
"> ### Actions",
|
'> ___',
|
||||||
_.times(_.random(4,6), function(){
|
_.times(_.random(3, 6), function(){
|
||||||
return genAction()
|
return genAbilities();
|
||||||
}).join('\n>\n'),
|
}).join('\n>\n'),
|
||||||
].join('\n') + '\n\n\n';
|
'> ### Actions',
|
||||||
},
|
_.times(_.random(4, 6), function(){
|
||||||
|
return genAction();
|
||||||
half : function(){
|
}).join('\n>\n'),
|
||||||
return [
|
].join('\n')}\n\n\n`;
|
||||||
"___",
|
},
|
||||||
"> ## " + getMonsterName(),
|
|
||||||
">*" + getType() + ", " + getAlignment() + "*",
|
half : function(){
|
||||||
"> ___",
|
return `${[
|
||||||
"> - **Armor Class** " + _.random(10,20),
|
'___',
|
||||||
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
`> ## ${getMonsterName()}`,
|
||||||
"> - **Speed** " + _.random(0,50) + "ft.",
|
`>*${getType()}, ${getAlignment()}*`,
|
||||||
">___",
|
'> ___',
|
||||||
">|STR|DEX|CON|INT|WIS|CHA|",
|
`> - **Armor Class** ${_.random(10, 20)}`,
|
||||||
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
`> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
|
||||||
getStats(),
|
`> - **Speed** ${_.random(0, 50)}ft.`,
|
||||||
">___",
|
'>___',
|
||||||
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
'>|STR|DEX|CON|INT|WIS|CHA|',
|
||||||
"> - **Senses** passive Perception " + _.random(3, 20),
|
'>|:---:|:---:|:---:|:---:|:---:|:---:|',
|
||||||
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
getStats(),
|
||||||
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
'>___',
|
||||||
"> ___",
|
`> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
|
||||||
_.times(_.random(0,2), function(){
|
`> - **Senses** passive Perception ${_.random(3, 20)}`,
|
||||||
return genAbilities()
|
`> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
|
||||||
}).join('\n>\n'),
|
`> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
|
||||||
"> ### Actions",
|
'> ___',
|
||||||
_.times(_.random(1,2), function(){
|
_.times(_.random(2, 3), function(){
|
||||||
return genAction()
|
return genAbilities();
|
||||||
}).join('\n>\n'),
|
}).join('\n>\n'),
|
||||||
].join('\n') + '\n\n\n';
|
'> ### Actions',
|
||||||
}
|
_.times(_.random(1, 2), function(){
|
||||||
}
|
return genAction();
|
||||||
|
}).join('\n>\n'),
|
||||||
|
].join('\n')}\n\n\n`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,80 +1,81 @@
|
|||||||
var MagicGen = require('./magic.gen.js');
|
/* eslint-disable max-lines */
|
||||||
var ClassTableGen = require('./classtable.gen.js');
|
|
||||||
var MonsterBlockGen = require('./monsterblock.gen.js');
|
const MagicGen = require('./magic.gen.js');
|
||||||
var ClassFeatureGen = require('./classfeature.gen.js');
|
const ClassTableGen = require('./classtable.gen.js');
|
||||||
var FullClassGen = require('./fullclass.gen.js');
|
const MonsterBlockGen = require('./monsterblock.gen.js');
|
||||||
var CoverPageGen = require('./coverpage.gen.js');
|
const ClassFeatureGen = require('./classfeature.gen.js');
|
||||||
var TableOfContentsGen = require('./tableOfContents.gen.js');
|
const CoverPageGen = require('./coverpage.gen.js');
|
||||||
|
const TableOfContentsGen = require('./tableOfContents.gen.js');
|
||||||
|
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
|
|
||||||
{
|
{
|
||||||
groupName : 'Editor',
|
groupName : 'Editor',
|
||||||
icon : 'fa-pencil',
|
icon : 'fa-pencil',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : "Column Break",
|
name : 'Column Break',
|
||||||
icon : 'fa-columns',
|
icon : 'fa-columns',
|
||||||
gen : "```\n```\n\n"
|
gen : '```\n```\n\n'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "New Page",
|
name : 'New Page',
|
||||||
icon : 'fa-file-text',
|
icon : 'fa-file-text',
|
||||||
gen : "\\page\n\n"
|
gen : '\\page\n\n'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "Vertical Spacing",
|
name : 'Vertical Spacing',
|
||||||
icon : 'fa-arrows-v',
|
icon : 'fa-arrows-v',
|
||||||
gen : "<div style='margin-top:140px'></div>\n\n"
|
gen : '<div style=\'margin-top:140px\'></div>\n\n'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "Wide Block",
|
name : 'Wide Block',
|
||||||
icon : 'fa-arrows-h',
|
icon : 'fa-arrows-h',
|
||||||
gen : "<div class='wide'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n"
|
gen : '<div class=\'wide\'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "Image",
|
name : 'Image',
|
||||||
icon : 'fa-image',
|
icon : 'fa-image',
|
||||||
gen : [
|
gen : [
|
||||||
"<img ",
|
'<img ',
|
||||||
" src='https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg' ",
|
' src=\'https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg\' ',
|
||||||
" style='width:325px' />",
|
' style=\'width:325px\' />',
|
||||||
"Credit: Kyounghwan Kim"
|
'Credit: Kyounghwan Kim'
|
||||||
].join('\n')
|
].join('\n')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "Background Image",
|
name : 'Background Image',
|
||||||
icon : 'fa-tree',
|
icon : 'fa-tree',
|
||||||
gen : [
|
gen : [
|
||||||
"<img ",
|
'<img ',
|
||||||
" src='http://i.imgur.com/hMna6G0.png' ",
|
' src=\'http://i.imgur.com/hMna6G0.png\' ',
|
||||||
" style='position:absolute; top:50px; right:30px; width:280px' />"
|
' style=\'position:absolute; top:50px; right:30px; width:280px\' />'
|
||||||
].join('\n')
|
].join('\n')
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : "Page Number",
|
name : 'Page Number',
|
||||||
icon : 'fa-bookmark',
|
icon : 'fa-bookmark',
|
||||||
gen : "<div class='pageNumber'>1</div>\n<div class='footnote'>PART 1 | FANCINESS</div>\n\n"
|
gen : '<div class=\'pageNumber\'>1</div>\n<div class=\'footnote\'>PART 1 | FANCINESS</div>\n\n'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : "Auto-incrementing Page Number",
|
name : 'Auto-incrementing Page Number',
|
||||||
icon : 'fa-sort-numeric-asc',
|
icon : 'fa-sort-numeric-asc',
|
||||||
gen : "<div class='pageNumber auto'></div>\n"
|
gen : '<div class=\'pageNumber auto\'></div>\n'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : "Link to page",
|
name : 'Link to page',
|
||||||
icon : 'fa-link',
|
icon : 'fa-link',
|
||||||
gen : "[Click here](#p3) to go to page 3\n"
|
gen : '[Click here](#p3) to go to page 3\n'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : "Table of Contents",
|
name : 'Table of Contents',
|
||||||
icon : 'fa-book',
|
icon : 'fa-book',
|
||||||
gen : TableOfContentsGen
|
gen : TableOfContentsGen
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@@ -86,63 +87,63 @@ module.exports = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
groupName : 'PHB',
|
groupName : 'PHB',
|
||||||
icon : 'fa-book',
|
icon : 'fa-book',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : 'Spell',
|
name : 'Spell',
|
||||||
icon : 'fa-magic',
|
icon : 'fa-magic',
|
||||||
gen : MagicGen.spell,
|
gen : MagicGen.spell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Spell List',
|
name : 'Spell List',
|
||||||
icon : 'fa-list',
|
icon : 'fa-list',
|
||||||
gen : MagicGen.spellList,
|
gen : MagicGen.spellList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Class Feature',
|
name : 'Class Feature',
|
||||||
icon : 'fa-trophy',
|
icon : 'fa-trophy',
|
||||||
gen : ClassFeatureGen,
|
gen : ClassFeatureGen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Note',
|
name : 'Note',
|
||||||
icon : 'fa-sticky-note',
|
icon : 'fa-sticky-note',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
"> ##### Time to Drop Knowledge",
|
'> ##### Time to Drop Knowledge',
|
||||||
"> Use notes to point out some interesting information. ",
|
'> Use notes to point out some interesting information. ',
|
||||||
"> ",
|
'> ',
|
||||||
"> **Tables and lists** both work within a note."
|
'> **Tables and lists** both work within a note.'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Descriptive Text Box',
|
name : 'Descriptive Text Box',
|
||||||
icon : 'fa-sticky-note-o',
|
icon : 'fa-sticky-note-o',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
"<div class='descriptive'>",
|
'<div class=\'descriptive\'>',
|
||||||
"##### Time to Drop Knowledge",
|
'##### Time to Drop Knowledge',
|
||||||
"Use notes to point out some interesting information. ",
|
'Use notes to point out some interesting information. ',
|
||||||
"",
|
'',
|
||||||
"**Tables and lists** both work within a note.",
|
'**Tables and lists** both work within a note.',
|
||||||
"</div>"
|
'</div>'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Monster Stat Block',
|
name : 'Monster Stat Block',
|
||||||
icon : 'fa-bug',
|
icon : 'fa-bug',
|
||||||
gen : MonsterBlockGen.half,
|
gen : MonsterBlockGen.half,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Wide Monster Stat Block',
|
name : 'Wide Monster Stat Block',
|
||||||
icon : 'fa-paw',
|
icon : 'fa-paw',
|
||||||
gen : MonsterBlockGen.full,
|
gen : MonsterBlockGen.full,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Cover Page',
|
name : 'Cover Page',
|
||||||
icon : 'fa-file-word-o',
|
icon : 'fa-file-word-o',
|
||||||
gen : CoverPageGen,
|
gen : CoverPageGen,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -153,77 +154,77 @@ module.exports = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
groupName : 'Tables',
|
groupName : 'Tables',
|
||||||
icon : 'fa-table',
|
icon : 'fa-table',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : "Class Table",
|
name : 'Class Table',
|
||||||
icon : 'fa-table',
|
icon : 'fa-table',
|
||||||
gen : ClassTableGen.full,
|
gen : ClassTableGen.full,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "Half Class Table",
|
name : 'Half Class Table',
|
||||||
icon : 'fa-list-alt',
|
icon : 'fa-list-alt',
|
||||||
gen : ClassTableGen.half,
|
gen : ClassTableGen.half,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Table',
|
name : 'Table',
|
||||||
icon : 'fa-th-list',
|
icon : 'fa-th-list',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
"##### Cookie Tastiness",
|
'##### Cookie Tastiness',
|
||||||
"| Tastiness | Cookie Type |",
|
'| Tastiness | Cookie Type |',
|
||||||
"|:----:|:-------------|",
|
'|:----:|:-------------|',
|
||||||
"| -5 | Raisin |",
|
'| -5 | Raisin |',
|
||||||
"| 8th | Chocolate Chip |",
|
'| 8th | Chocolate Chip |',
|
||||||
"| 11th | 2 or lower |",
|
'| 11th | 2 or lower |',
|
||||||
"| 14th | 3 or lower |",
|
'| 14th | 3 or lower |',
|
||||||
"| 17th | 4 or lower |\n\n",
|
'| 17th | 4 or lower |\n\n',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Wide Table',
|
name : 'Wide Table',
|
||||||
icon : 'fa-list',
|
icon : 'fa-list',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
"<div class='wide'>",
|
'<div class=\'wide\'>',
|
||||||
"##### Cookie Tastiness",
|
'##### Cookie Tastiness',
|
||||||
"| Tastiness | Cookie Type |",
|
'| Tastiness | Cookie Type |',
|
||||||
"|:----:|:-------------|",
|
'|:----:|:-------------|',
|
||||||
"| -5 | Raisin |",
|
'| -5 | Raisin |',
|
||||||
"| 8th | Chocolate Chip |",
|
'| 8th | Chocolate Chip |',
|
||||||
"| 11th | 2 or lower |",
|
'| 11th | 2 or lower |',
|
||||||
"| 14th | 3 or lower |",
|
'| 14th | 3 or lower |',
|
||||||
"| 17th | 4 or lower |",
|
'| 17th | 4 or lower |',
|
||||||
"</div>\n\n"
|
'</div>\n\n'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Split Table',
|
name : 'Split Table',
|
||||||
icon : 'fa-th-large',
|
icon : 'fa-th-large',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
"<div style='column-count:2'>",
|
'<div style=\'column-count:2\'>',
|
||||||
"| d10 | Damage Type |",
|
'| d10 | Damage Type |',
|
||||||
"|:---:|:------------|",
|
'|:---:|:------------|',
|
||||||
"| 1 | Acid |",
|
'| 1 | Acid |',
|
||||||
"| 2 | Cold |",
|
'| 2 | Cold |',
|
||||||
"| 3 | Fire |",
|
'| 3 | Fire |',
|
||||||
"| 4 | Force |",
|
'| 4 | Force |',
|
||||||
"| 5 | Lightning |",
|
'| 5 | Lightning |',
|
||||||
"",
|
'',
|
||||||
"```",
|
'```',
|
||||||
"```",
|
'```',
|
||||||
"",
|
'',
|
||||||
"| d10 | Damage Type |",
|
'| d10 | Damage Type |',
|
||||||
"|:---:|:------------|",
|
'|:---:|:------------|',
|
||||||
"| 6 | Necrotic |",
|
'| 6 | Necrotic |',
|
||||||
"| 7 | Poison |",
|
'| 7 | Poison |',
|
||||||
"| 8 | Psychic |",
|
'| 8 | Psychic |',
|
||||||
"| 9 | Radiant |",
|
'| 9 | Radiant |',
|
||||||
"| 10 | Thunder |",
|
'| 10 | Thunder |',
|
||||||
"</div>\n\n",
|
'</div>\n\n',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -237,12 +238,12 @@ module.exports = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
groupName : 'Print',
|
groupName : 'Print',
|
||||||
icon : 'fa-print',
|
icon : 'fa-print',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : "A4 PageSize",
|
name : 'A4 PageSize',
|
||||||
icon : 'fa-file-o',
|
icon : 'fa-file-o',
|
||||||
gen : ['<style>',
|
gen : ['<style>',
|
||||||
' .phb{',
|
' .phb{',
|
||||||
' width : 210mm;',
|
' width : 210mm;',
|
||||||
' height : 296.8mm;',
|
' height : 296.8mm;',
|
||||||
@@ -251,9 +252,9 @@ module.exports = [
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : "Ink Friendly",
|
name : 'Ink Friendly',
|
||||||
icon : 'fa-tint',
|
icon : 'fa-tint',
|
||||||
gen : ['<style>',
|
gen : ['<style>',
|
||||||
' .phb{ background : white;}',
|
' .phb{ background : white;}',
|
||||||
' .phb img{ display : none;}',
|
' .phb img{ display : none;}',
|
||||||
' .phb hr+blockquote{background : white;}',
|
' .phb hr+blockquote{background : white;}',
|
||||||
@@ -264,4 +265,4 @@ module.exports = [
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
];
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const getTOC = (pages) => {
|
const getTOC = (pages)=>{
|
||||||
const add1 = (title, page)=>{
|
const add1 = (title, page)=>{
|
||||||
res.push({
|
res.push({
|
||||||
title : title,
|
title : title,
|
||||||
page : page + 1,
|
page : page + 1,
|
||||||
children : []
|
children : []
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
const add2 = (title, page)=>{
|
const add2 = (title, page)=>{
|
||||||
if(!_.last(res)) add1('', page);
|
if(!_.last(res)) add1('', page);
|
||||||
_.last(res).children.push({
|
_.last(res).children.push({
|
||||||
title : title,
|
title : title,
|
||||||
page : page + 1,
|
page : page + 1,
|
||||||
children : []
|
children : []
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
const add3 = (title, page)=>{
|
const add3 = (title, page)=>{
|
||||||
if(!_.last(res)) add1('', page);
|
if(!_.last(res)) add1('', page);
|
||||||
if(!_.last(_.last(res).children)) add2('', page);
|
if(!_.last(_.last(res).children)) add2('', page);
|
||||||
_.last(_.last(res).children).children.push({
|
_.last(_.last(res).children).children.push({
|
||||||
title : title,
|
title : title,
|
||||||
page : page + 1,
|
page : page + 1,
|
||||||
children : []
|
children : []
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
let res = [];
|
const res = [];
|
||||||
_.each(pages, (page, pageNum)=>{
|
_.each(pages, (page, pageNum)=>{
|
||||||
const lines = page.split('\n');
|
const lines = page.split('\n');
|
||||||
_.each(lines, (line) => {
|
_.each(lines, (line)=>{
|
||||||
if(_.startsWith(line, '# ')){
|
if(_.startsWith(line, '# ')){
|
||||||
const title = line.replace('# ', '');
|
const title = line.replace('# ', '');
|
||||||
add1(title, pageNum)
|
add1(title, pageNum);
|
||||||
}
|
}
|
||||||
if(_.startsWith(line, '## ')){
|
if(_.startsWith(line, '## ')){
|
||||||
const title = line.replace('## ', '');
|
const title = line.replace('## ', '');
|
||||||
@@ -42,21 +42,21 @@ const getTOC = (pages) => {
|
|||||||
const title = line.replace('### ', '');
|
const title = line.replace('### ', '');
|
||||||
add3(title, pageNum);
|
add3(title, pageNum);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = function(brew){
|
module.exports = function(brew){
|
||||||
const pages = brew.split('\\page');
|
const pages = brew.split('\\page');
|
||||||
const TOC = getTOC(pages);
|
const TOC = getTOC(pages);
|
||||||
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
|
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
|
||||||
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`)
|
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`);
|
||||||
if(g1.children.length){
|
if(g1.children.length){
|
||||||
_.each(g1.children, (g2, idx2) => {
|
_.each(g1.children, (g2, idx2)=>{
|
||||||
r.push(` - [${idx1 + 1}.${idx2 + 1} ${g2.title}](#p${g2.page})`);
|
r.push(` - [${idx1 + 1}.${idx2 + 1} ${g2.title}](#p${g2.page})`);
|
||||||
if(g2.children.length){
|
if(g2.children.length){
|
||||||
_.each(g2.children, (g3, idx3) => {
|
_.each(g2.children, (g3, idx3)=>{
|
||||||
r.push(` - [${idx1 + 1}.${idx2 + 1}.${idx3 + 1} ${g3.title}](#p${g3.page})`);
|
r.push(` - [${idx1 + 1}.${idx2 + 1}.${idx3 + 1} ${g3.title}](#p${g3.page})`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -69,4 +69,4 @@ module.exports = function(brew){
|
|||||||
##### Table Of Contents
|
##### Table Of Contents
|
||||||
${markdown}
|
${markdown}
|
||||||
</div>\n`;
|
</div>\n`;
|
||||||
}
|
};
|
||||||
@@ -1,89 +1,71 @@
|
|||||||
const React = require('react');
|
require('./homebrew.less');
|
||||||
const _ = require('lodash');
|
const React = require('react');
|
||||||
const cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
const CreateRouter = require('pico-router').createRouter;
|
const cx = require('classnames');
|
||||||
|
const { StaticRouter:Router, Switch, Route } = require('react-router-dom');
|
||||||
const HomePage = require('./pages/homePage/homePage.jsx');
|
const queryString = require('query-string');
|
||||||
const EditPage = require('./pages/editPage/editPage.jsx');
|
|
||||||
const UserPage = require('./pages/userPage/userPage.jsx');
|
const HomePage = require('./pages/homePage/homePage.jsx');
|
||||||
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
const EditPage = require('./pages/editPage/editPage.jsx');
|
||||||
const NewPage = require('./pages/newPage/newPage.jsx');
|
const UserPage = require('./pages/userPage/userPage.jsx');
|
||||||
const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||||
const PrintPage = require('./pages/printPage/printPage.jsx');
|
const NewPage = require('./pages/newPage/newPage.jsx');
|
||||||
|
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||||
let Router;
|
const PrintPage = require('./pages/printPage/printPage.jsx');
|
||||||
const Homebrew = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
const Homebrew = createClass({
|
||||||
return {
|
getDefaultProps : function() {
|
||||||
url : '',
|
return {
|
||||||
welcomeText : '',
|
url : '',
|
||||||
changelog : '',
|
welcomeText : '',
|
||||||
version : '0.0.0',
|
changelog : '',
|
||||||
account : null,
|
version : '0.0.0',
|
||||||
brew : {
|
account : null,
|
||||||
title : '',
|
brew : {
|
||||||
text : '',
|
title : '',
|
||||||
shareId : null,
|
text : '',
|
||||||
editId : null,
|
shareId : null,
|
||||||
createdAt : null,
|
editId : null,
|
||||||
updatedAt : null,
|
createdAt : null,
|
||||||
}
|
updatedAt : null,
|
||||||
};
|
}
|
||||||
},
|
};
|
||||||
componentWillMount: function() {
|
},
|
||||||
global.account = this.props.account;
|
componentWillMount : function() {
|
||||||
global.version = this.props.version;
|
global.account = this.props.account;
|
||||||
|
global.version = this.props.version;
|
||||||
|
|
||||||
Router = CreateRouter({
|
},
|
||||||
'/edit/:id' : (args) => {
|
render : function (){
|
||||||
if(!this.props.brew.editId){
|
return (
|
||||||
return <ErrorPage errorId={args.id}/>
|
<Router location={this.props.url}>
|
||||||
}
|
<div className='homebrew'>
|
||||||
|
<Switch>
|
||||||
return <EditPage
|
<Route path='/edit/:id' component={(routeProps)=><EditPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
||||||
id={args.id}
|
<Route path='/share/:id' component={(routeProps)=><SharePage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
||||||
brew={this.props.brew} />
|
<Route path='/user/:username' component={(routeProps)=><UserPage username={routeProps.match.params.username} brews={this.props.brews} />}/>
|
||||||
},
|
<Route path='/print/:id' component={(routeProps)=><PrintPage brew={this.props.brew} query={queryString.parse(routeProps.location.search)} /> } />
|
||||||
|
<Route path='/print' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} /> } />
|
||||||
'/share/:id' : (args) => {
|
<Route path='/new' exact component={NewPage}/>
|
||||||
if(!this.props.brew.shareId){
|
<Route path='/changelog' exact component={()=><SharePage brew={{ title: 'Changelog', text: this.props.changelog }} />}/>
|
||||||
return <ErrorPage errorId={args.id}/>
|
<Route path='/' component={()=><HomePage welcomeText={this.props.welcomeText}/>}/>
|
||||||
}
|
</Switch>
|
||||||
|
</div>
|
||||||
return <SharePage
|
</Router>
|
||||||
id={args.id}
|
);
|
||||||
brew={this.props.brew} />
|
}
|
||||||
},
|
});
|
||||||
'/user/:username' : (args) => {
|
|
||||||
return <UserPage
|
module.exports = Homebrew;
|
||||||
username={args.username}
|
|
||||||
brews={this.props.brews}
|
//TODO: Nicer Error page instead of just "cant get that"
|
||||||
/>
|
// '/share/:id' : (args)=>{
|
||||||
},
|
// if(!this.props.brew.shareId){
|
||||||
'/print/:id' : (args, query) => {
|
// return <ErrorPage errorId={args.id}/>;
|
||||||
return <PrintPage brew={this.props.brew} query={query}/>;
|
// }
|
||||||
},
|
//
|
||||||
'/print' : (args, query) => {
|
// return <SharePage
|
||||||
return <PrintPage query={query}/>;
|
// id={args.id}
|
||||||
},
|
// brew={this.props.brew} />;
|
||||||
'/new' : (args) => {
|
// },
|
||||||
return <NewPage />
|
|
||||||
},
|
|
||||||
'/changelog' : (args) => {
|
|
||||||
return <SharePage
|
|
||||||
brew={{title : 'Changelog', text : this.props.changelog}} />
|
|
||||||
},
|
|
||||||
'*' : <HomePage
|
|
||||||
welcomeText={this.props.welcomeText} />,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render : function(){
|
|
||||||
return <div className='homebrew'>
|
|
||||||
<Router initialUrl={this.props.url}/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Homebrew;
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
@import 'naturalcrit/styles/core.less';
|
@import 'naturalcrit/styles/core.less';
|
||||||
.homebrew{
|
.homebrew{
|
||||||
height : 100%;
|
height : 100%;
|
||||||
@@ -8,9 +7,9 @@
|
|||||||
background-color : @steel;
|
background-color : @steel;
|
||||||
flex-direction : column;
|
flex-direction : column;
|
||||||
.content{
|
.content{
|
||||||
position : relative;
|
position : relative;
|
||||||
height : calc(~"100% - 29px"); //Navbar height
|
height : calc(~"100% - 29px"); //Navbar height
|
||||||
flex : auto;
|
flex : auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const createClass = require('create-react-class');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
module.exports = function(props){
|
|
||||||
if(global.account){
|
module.exports = function(props){
|
||||||
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
|
if(global.account){
|
||||||
{global.account.username}
|
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
|
||||||
</Nav.item>
|
{global.account.username}
|
||||||
}
|
</Nav.item>;
|
||||||
let url = '';
|
}
|
||||||
if(typeof window !== 'undefined'){
|
let url = '';
|
||||||
url = window.location.href
|
if(typeof window !== 'undefined'){
|
||||||
}
|
url = window.location.href;
|
||||||
return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'>
|
}
|
||||||
login
|
return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'>
|
||||||
</Nav.item>
|
login
|
||||||
|
</Nav.item>;
|
||||||
};
|
};
|
||||||
@@ -1,33 +1,34 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const createClass = require('create-react-class');
|
||||||
var cx = require('classnames');
|
const _ = require('lodash');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const cx = require('classnames');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const MAX_TITLE_LENGTH = 50;
|
|
||||||
|
const MAX_TITLE_LENGTH = 50;
|
||||||
|
|
||||||
var EditTitle = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
const EditTitle = createClass({
|
||||||
return {
|
getDefaultProps : function() {
|
||||||
title : '',
|
return {
|
||||||
onChange : function(){}
|
title : '',
|
||||||
};
|
onChange : function(){}
|
||||||
},
|
};
|
||||||
|
},
|
||||||
handleChange : function(e){
|
|
||||||
if(e.target.value.length > MAX_TITLE_LENGTH) return;
|
handleChange : function(e){
|
||||||
this.props.onChange(e.target.value);
|
if(e.target.value.length > MAX_TITLE_LENGTH) return;
|
||||||
},
|
this.props.onChange(e.target.value);
|
||||||
render : function(){
|
},
|
||||||
return <Nav.item className='editTitle'>
|
render : function(){
|
||||||
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
|
return <Nav.item className='editTitle'>
|
||||||
|
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
|
||||||
<div className={cx('charCount', {'max' : this.props.title.length >= MAX_TITLE_LENGTH})}>
|
|
||||||
{this.props.title.length}/{MAX_TITLE_LENGTH}
|
<div className={cx('charCount', { 'max': this.props.title.length >= MAX_TITLE_LENGTH })}>
|
||||||
</div>
|
{this.props.title.length}/{MAX_TITLE_LENGTH}
|
||||||
</Nav.item>
|
</div>
|
||||||
},
|
</Nav.item>;
|
||||||
|
},
|
||||||
});
|
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = EditTitle;
|
module.exports = EditTitle;
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const createClass = require('create-react-class');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
module.exports = function(props){
|
|
||||||
return <Nav.item newTab={true} href='https://github.com/stolksdorf/homebrewery/issues' color='red' icon='fa-bug'>
|
module.exports = function(props){
|
||||||
report issue
|
return <Nav.item
|
||||||
</Nav.item>
|
newTab={true}
|
||||||
|
color='red'
|
||||||
|
icon='fa-bug'
|
||||||
|
href={`https://www.reddit.com/r/homebrewery/submit?selftext=true&title=${encodeURIComponent('[Issue] Describe Your Issue Here')}`} >
|
||||||
|
report issue
|
||||||
|
</Nav.item>;
|
||||||
};
|
};
|
||||||
@@ -1,49 +1,51 @@
|
|||||||
const React = require('react');
|
require('./navbar.less');
|
||||||
const _ = require('lodash');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const Navbar = React.createClass({
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
const Navbar = createClass({
|
||||||
//showNonChromeWarning : false,
|
getInitialState : function() {
|
||||||
ver : '0.0.0'
|
return {
|
||||||
};
|
//showNonChromeWarning : false,
|
||||||
},
|
ver : '0.0.0'
|
||||||
|
};
|
||||||
componentDidMount: function() {
|
},
|
||||||
//const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
|
|
||||||
this.setState({
|
componentDidMount : function() {
|
||||||
//showNonChromeWarning : !isChrome,
|
//const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
|
||||||
ver : window.version
|
this.setState({
|
||||||
})
|
//showNonChromeWarning : !isChrome,
|
||||||
},
|
ver : window.version
|
||||||
|
});
|
||||||
/*
|
},
|
||||||
renderChromeWarning : function(){
|
|
||||||
if(!this.state.showNonChromeWarning) return;
|
/*
|
||||||
return <Nav.item className='warning' icon='fa-exclamation-triangle'>
|
renderChromeWarning : function(){
|
||||||
Optimized for Chrome
|
if(!this.state.showNonChromeWarning) return;
|
||||||
<div className='dropdown'>
|
return <Nav.item className='warning' icon='fa-exclamation-triangle'>
|
||||||
If you are experiencing rendering issues, use Chrome instead
|
Optimized for Chrome
|
||||||
</div>
|
<div className='dropdown'>
|
||||||
</Nav.item>
|
If you are experiencing rendering issues, use Chrome instead
|
||||||
},
|
</div>
|
||||||
*/
|
</Nav.item>
|
||||||
render : function(){
|
},
|
||||||
return <Nav.base>
|
*/
|
||||||
<Nav.section>
|
render : function(){
|
||||||
<Nav.logo />
|
return <Nav.base>
|
||||||
<Nav.item href='/' className='homebrewLogo'>
|
<Nav.section>
|
||||||
<div>The Homebrewery</div>
|
<Nav.logo />
|
||||||
</Nav.item>
|
<Nav.item href='/' className='homebrewLogo'>
|
||||||
<Nav.item>{`v${this.state.ver}`}</Nav.item>
|
<div>The Homebrewery</div>
|
||||||
|
</Nav.item>
|
||||||
{/*this.renderChromeWarning()*/}
|
<Nav.item>{`v${this.state.ver}`}</Nav.item>
|
||||||
</Nav.section>
|
|
||||||
{this.props.children}
|
{/*this.renderChromeWarning()*/}
|
||||||
</Nav.base>
|
</Nav.section>
|
||||||
}
|
{this.props.children}
|
||||||
});
|
</Nav.base>;
|
||||||
|
}
|
||||||
module.exports = Navbar;
|
});
|
||||||
|
|
||||||
|
module.exports = Navbar;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const createClass = require('create-react-class');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
module.exports = function(props){
|
|
||||||
return <Nav.item
|
module.exports = function(props){
|
||||||
className='patreon'
|
return <Nav.item
|
||||||
newTab={true}
|
className='patreon'
|
||||||
href='https://www.patreon.com/stolksdorf'
|
newTab={true}
|
||||||
color='green'
|
href='https://www.patreon.com/stolksdorf'
|
||||||
icon='fa-heart'>
|
color='green'
|
||||||
help out
|
icon='fa-heart'>
|
||||||
</Nav.item>
|
help out
|
||||||
|
</Nav.item>;
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const createClass = require('create-react-class');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
module.exports = function(props){
|
|
||||||
return <Nav.item newTab={true} href={'/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-file-pdf-o'>
|
module.exports = function(props){
|
||||||
get PDF
|
return <Nav.item newTab={true} href={`/print/${props.shareId}?dialog=true`} color='purple' icon='fa-file-pdf-o'>
|
||||||
</Nav.item>
|
get PDF
|
||||||
|
</Nav.item>;
|
||||||
};
|
};
|
||||||
@@ -1,199 +1,143 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const createClass = require('create-react-class');
|
||||||
var cx = require('classnames');
|
const _ = require('lodash');
|
||||||
var Moment = require('moment');
|
const Moment = require('moment');
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
const VIEW_KEY = 'homebrewery-recently-viewed';
|
const EDIT_KEY = 'homebrewery-recently-edited';
|
||||||
const EDIT_KEY = 'homebrewery-recently-edited';
|
const VIEW_KEY = 'homebrewery-recently-viewed';
|
||||||
|
|
||||||
var BaseItem = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
const RecentItems = createClass({
|
||||||
return {
|
|
||||||
storageKey : '',
|
getDefaultProps : function() {
|
||||||
text : '',
|
return {
|
||||||
currentBrew:{
|
storageKey : '',
|
||||||
title : '',
|
showEdit : false,
|
||||||
id : '',
|
showView : false
|
||||||
url : ''
|
};
|
||||||
}
|
},
|
||||||
};
|
|
||||||
},
|
getInitialState : function() {
|
||||||
getInitialState: function() {
|
return {
|
||||||
return {
|
showDropdown : false,
|
||||||
showDropdown: false,
|
edit : [],
|
||||||
brews : []
|
view : []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount : function() {
|
||||||
var brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
|
|
||||||
|
//== Load recent items list ==//
|
||||||
brews = _.filter(brews, (brew)=>{
|
let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
||||||
return brew.id !== this.props.currentBrew.id;
|
let viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
|
||||||
});
|
|
||||||
if(this.props.currentBrew.id){
|
//== Add current brew to appropriate recent items list (depending on storageKey) ==//
|
||||||
brews.unshift({
|
if(this.props.storageKey == 'edit'){
|
||||||
id : this.props.currentBrew.id,
|
edited = _.filter(edited, (brew)=>{
|
||||||
url : this.props.currentBrew.url,
|
return brew.id !== this.props.brew.editId;
|
||||||
title : this.props.currentBrew.title,
|
});
|
||||||
ts : Date.now()
|
edited.unshift({
|
||||||
});
|
id : this.props.brew.editId,
|
||||||
}
|
title : this.props.brew.title,
|
||||||
brews = _.slice(brews, 0, 8);
|
url : `/edit/${this.props.brew.editId}`,
|
||||||
localStorage.setItem(this.props.storageKey, JSON.stringify(brews));
|
ts : Date.now()
|
||||||
this.setState({
|
});
|
||||||
brews : brews
|
}
|
||||||
});
|
if(this.props.storageKey == 'view'){
|
||||||
},
|
viewed = _.filter(viewed, (brew)=>{
|
||||||
|
return brew.id !== this.props.brew.shareId;
|
||||||
handleDropdown : function(show){
|
});
|
||||||
this.setState({
|
viewed.unshift({
|
||||||
showDropdown : show
|
id : this.props.brew.shareId,
|
||||||
})
|
title : this.props.brew.title,
|
||||||
},
|
url : `/share/${this.props.brew.shareId}`,
|
||||||
|
ts : Date.now()
|
||||||
renderDropdown : function(){
|
});
|
||||||
if(!this.state.showDropdown) return null;
|
}
|
||||||
|
|
||||||
var items = _.map(this.state.brews, (brew)=>{
|
//== Store the updated lists (up to 8 items each) ==//
|
||||||
return <a href={brew.url} className='item' key={brew.id} target='_blank'>
|
edited = _.slice(edited, 0, 8);
|
||||||
<span className='title'>{brew.title}</span>
|
viewed = _.slice(viewed, 0, 8);
|
||||||
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
|
||||||
</a>
|
localStorage.setItem(EDIT_KEY, JSON.stringify(edited));
|
||||||
});
|
localStorage.setItem(VIEW_KEY, JSON.stringify(viewed));
|
||||||
|
|
||||||
return <div className='dropdown'>{items}</div>
|
this.setState({
|
||||||
},
|
edit : edited,
|
||||||
|
view : viewed
|
||||||
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)}>
|
handleDropdown : function(show){
|
||||||
{this.props.text}
|
this.setState({
|
||||||
{this.renderDropdown()}
|
showDropdown : show
|
||||||
</Nav.item>
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
renderDropdown : function(){
|
||||||
|
if(!this.state.showDropdown) return null;
|
||||||
|
|
||||||
|
const makeItems = (brews)=>{
|
||||||
module.exports = {
|
return _.map(brews, (brew)=>{
|
||||||
viewed : React.createClass({
|
return <a href={brew.url} className='item' key={brew.id} target='_blank' rel='noopener noreferrer'>
|
||||||
getDefaultProps: function() {
|
<span className='title'>{brew.title || '[ no title ]'}</span>
|
||||||
return {
|
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||||
brew : {
|
</a>;
|
||||||
title : '',
|
});
|
||||||
shareId : ''
|
};
|
||||||
}
|
|
||||||
};
|
return <div className='dropdown'>
|
||||||
},
|
{(this.props.showEdit && this.props.showView) ?
|
||||||
render : function(){
|
<h4>edited</h4> : null }
|
||||||
return <BaseItem text='recently viewed' storageKey={VIEW_KEY}
|
{this.props.showEdit ?
|
||||||
currentBrew={{
|
makeItems(this.state.edit) : null }
|
||||||
id : this.props.brew.shareId,
|
{(this.props.showEdit && this.props.showView) ?
|
||||||
title : this.props.brew.title,
|
<h4>viewed</h4> : null }
|
||||||
url : `/share/${this.props.brew.shareId}`
|
{this.props.showView ?
|
||||||
}}
|
makeItems(this.state.view) : null }
|
||||||
/>
|
</div>;
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
|
render : function(){
|
||||||
edited : React.createClass({
|
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||||
getDefaultProps: function() {
|
onMouseEnter={()=>this.handleDropdown(true)}
|
||||||
return {
|
onMouseLeave={()=>this.handleDropdown(false)}>
|
||||||
brew : {
|
{this.props.text}
|
||||||
title : '',
|
{this.renderDropdown()}
|
||||||
editId : ''
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
},
|
});
|
||||||
render : function(){
|
|
||||||
return <BaseItem text='recently edited' storageKey={EDIT_KEY}
|
module.exports = {
|
||||||
currentBrew={{
|
|
||||||
id : this.props.brew.editId,
|
edited : (props)=>{
|
||||||
title : this.props.brew.title,
|
return <RecentItems
|
||||||
url : `/edit/${this.props.brew.editId}`
|
brew={props.brew}
|
||||||
}}
|
storageKey={props.storageKey}
|
||||||
/>
|
text='recently edited'
|
||||||
},
|
showEdit={true}
|
||||||
}),
|
/>;
|
||||||
|
},
|
||||||
both : React.createClass({
|
|
||||||
getDefaultProps: function() {
|
viewed : (props)=>{
|
||||||
return {
|
return <RecentItems
|
||||||
errorId : null
|
brew={props.brew}
|
||||||
};
|
storageKey={props.storageKey}
|
||||||
},
|
text='recently viewed'
|
||||||
|
showView={true}
|
||||||
getInitialState: function() {
|
/>;
|
||||||
return {
|
},
|
||||||
showDropdown: false,
|
|
||||||
edit : [],
|
both : (props)=>{
|
||||||
view : []
|
return <RecentItems
|
||||||
};
|
brew={props.brew}
|
||||||
},
|
storageKey={props.storageKey}
|
||||||
|
text='recent brews'
|
||||||
componentDidMount: function() {
|
showEdit={true}
|
||||||
|
showView={true}
|
||||||
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>
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,51 +1,52 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const createClass = require('create-react-class');
|
||||||
var cx = require('classnames');
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
//var striptags = require('striptags');
|
|
||||||
|
//var striptags = require('striptags');
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const MAX_URL_SIZE = 2083;
|
|
||||||
const MAIN_URL = "https://www.reddit.com/r/UnearthedArcana/submit?selftext=true"
|
const MAX_URL_SIZE = 2083;
|
||||||
|
const MAIN_URL = 'https://www.reddit.com/r/UnearthedArcana/submit?selftext=true';
|
||||||
|
|
||||||
var RedditShare = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
const RedditShare = createClass({
|
||||||
return {
|
getDefaultProps : function() {
|
||||||
brew : {
|
return {
|
||||||
title : '',
|
brew : {
|
||||||
sharedId : '',
|
title : '',
|
||||||
text : ''
|
sharedId : '',
|
||||||
}
|
text : ''
|
||||||
};
|
}
|
||||||
},
|
};
|
||||||
|
},
|
||||||
getText : function(){
|
|
||||||
|
getText : function(){
|
||||||
},
|
|
||||||
|
},
|
||||||
|
|
||||||
handleClick : function(){
|
|
||||||
var url = [
|
handleClick : function(){
|
||||||
MAIN_URL,
|
const url = [
|
||||||
'title=' + encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!'),
|
MAIN_URL,
|
||||||
|
`title=${encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!')}`,
|
||||||
'text=' + encodeURIComponent(this.props.brew.text)
|
|
||||||
|
`text=${encodeURIComponent(this.props.brew.text)}`
|
||||||
|
|
||||||
].join('&');
|
|
||||||
|
].join('&');
|
||||||
window.open(url, '_blank');
|
|
||||||
},
|
window.open(url, '_blank');
|
||||||
|
},
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <Nav.item icon='fa-reddit-alien' color='red' onClick={this.handleClick}>
|
render : function(){
|
||||||
share on reddit
|
return <Nav.item icon='fa-reddit-alien' color='red' onClick={this.handleClick}>
|
||||||
</Nav.item>
|
share on reddit
|
||||||
},
|
</Nav.item>;
|
||||||
|
},
|
||||||
});
|
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = RedditShare;
|
module.exports = RedditShare;
|
||||||
@@ -1,230 +1,226 @@
|
|||||||
const React = require('react');
|
require('./editPage.less');
|
||||||
const _ = require('lodash');
|
const React = require('react');
|
||||||
const cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
const request = require("superagent");
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const request = require('superagent');
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
const { Meta } = require('vitreum/headtags');
|
||||||
|
|
||||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
const Account = require('../../navbar/account.navitem.jsx');
|
|
||||||
//const RecentlyEdited = require('../../navbar/recent.navitem.jsx').edited;
|
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||||
|
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const Account = require('../../navbar/account.navitem.jsx');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
const Markdown = require('naturalcrit/markdown.js');
|
const Editor = require('../../editor/editor.jsx');
|
||||||
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
const SAVE_TIMEOUT = 3000;
|
|
||||||
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
|
|
||||||
const EditPage = React.createClass({
|
const SAVE_TIMEOUT = 3000;
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brew : {
|
const EditPage = createClass({
|
||||||
text : '',
|
getDefaultProps : function() {
|
||||||
shareId : null,
|
return {
|
||||||
editId : null,
|
brew : {
|
||||||
createdAt : null,
|
text : '',
|
||||||
updatedAt : null,
|
shareId : null,
|
||||||
|
editId : null,
|
||||||
title : '',
|
createdAt : null,
|
||||||
description : '',
|
updatedAt : null,
|
||||||
tags : '',
|
|
||||||
published : false,
|
title : '',
|
||||||
authors : [],
|
description : '',
|
||||||
systems : []
|
tags : '',
|
||||||
}
|
published : false,
|
||||||
};
|
authors : [],
|
||||||
},
|
systems : []
|
||||||
|
}
|
||||||
getInitialState: function() {
|
};
|
||||||
return {
|
},
|
||||||
brew : this.props.brew,
|
|
||||||
|
getInitialState : function() {
|
||||||
isSaving : false,
|
return {
|
||||||
isPending : false,
|
brew : this.props.brew,
|
||||||
errors : null,
|
|
||||||
htmlErrors : Markdown.validate(this.props.brew.text),
|
isSaving : false,
|
||||||
lastUpdated : this.props.brew.updatedAt
|
isPending : false,
|
||||||
};
|
errors : null,
|
||||||
},
|
htmlErrors : Markdown.validate(this.props.brew.text),
|
||||||
savedBrew : null,
|
};
|
||||||
|
},
|
||||||
componentDidMount: function(){
|
savedBrew : null,
|
||||||
this.trySave();
|
|
||||||
window.onbeforeunload = ()=>{
|
componentDidMount : function(){
|
||||||
if(this.state.isSaving || this.state.isPending){
|
this.trySave();
|
||||||
return 'You have unsaved changes!';
|
window.onbeforeunload = ()=>{
|
||||||
}
|
if(this.state.isSaving || this.state.isPending){
|
||||||
};
|
return 'You have unsaved changes!';
|
||||||
|
}
|
||||||
this.setState({
|
};
|
||||||
htmlErrors : Markdown.validate(this.state.brew.text)
|
|
||||||
})
|
this.setState((prevState)=>({
|
||||||
|
htmlErrors : Markdown.validate(prevState.brew.text)
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
}));
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
window.onbeforeunload = function(){};
|
},
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
componentWillUnmount : function() {
|
||||||
},
|
window.onbeforeunload = function(){};
|
||||||
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
|
},
|
||||||
handleControlKeys : function(e){
|
|
||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
|
||||||
const S_KEY = 83;
|
handleControlKeys : function(e){
|
||||||
const P_KEY = 80;
|
if(!(e.ctrlKey || e.metaKey)) return;
|
||||||
if(e.keyCode == S_KEY) this.save();
|
const S_KEY = 83;
|
||||||
if(e.keyCode == P_KEY) window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
const P_KEY = 80;
|
||||||
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
if(e.keyCode == S_KEY) this.save();
|
||||||
e.stopPropagation();
|
if(e.keyCode == P_KEY) window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
||||||
e.preventDefault();
|
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
||||||
}
|
e.stopPropagation();
|
||||||
},
|
e.preventDefault();
|
||||||
|
}
|
||||||
handleSplitMove : function(){
|
},
|
||||||
this.refs.editor.update();
|
|
||||||
},
|
handleSplitMove : function(){
|
||||||
|
this.refs.editor.update();
|
||||||
handleMetadataChange : function(metadata){
|
},
|
||||||
this.setState({
|
|
||||||
brew : _.merge({}, this.state.brew, metadata),
|
handleMetadataChange : function(metadata){
|
||||||
isPending : true,
|
this.setState((prevState)=>({
|
||||||
}, ()=>{
|
brew : _.merge({}, prevState.brew, metadata),
|
||||||
this.trySave();
|
isPending : true,
|
||||||
});
|
}), ()=>this.trySave());
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTextChange : function(text){
|
handleTextChange : function(text){
|
||||||
|
|
||||||
//If there are errors, run the validator on everychange to give quick feedback
|
//If there are errors, run the validator on everychange to give quick feedback
|
||||||
var htmlErrors = this.state.htmlErrors;
|
let htmlErrors = this.state.htmlErrors;
|
||||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||||
|
|
||||||
this.setState({
|
this.setState((prevState)=>({
|
||||||
brew : _.merge({}, this.state.brew, {text : text}),
|
brew : _.merge({}, prevState.brew, { text: text }),
|
||||||
isPending : true,
|
isPending : true,
|
||||||
htmlErrors : htmlErrors
|
htmlErrors : htmlErrors
|
||||||
});
|
}), ()=>this.trySave());
|
||||||
|
},
|
||||||
this.trySave();
|
|
||||||
},
|
hasChanges : function(){
|
||||||
|
const savedBrew = this.savedBrew ? this.savedBrew : this.props.brew;
|
||||||
hasChanges : function(){
|
return !_.isEqual(this.state.brew, savedBrew);
|
||||||
if(this.savedBrew){
|
},
|
||||||
return !_.isEqual(this.state.brew, this.savedBrew)
|
|
||||||
}else{
|
trySave : function(){
|
||||||
return !_.isEqual(this.state.brew, this.props.brew)
|
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
||||||
}
|
if(this.hasChanges()){
|
||||||
return false;
|
this.debounceSave();
|
||||||
},
|
} else {
|
||||||
|
this.debounceSave.cancel();
|
||||||
trySave : function(){
|
}
|
||||||
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
},
|
||||||
if(this.hasChanges()){
|
|
||||||
this.debounceSave();
|
save : function(){
|
||||||
}else{
|
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
|
||||||
this.debounceSave.cancel();
|
|
||||||
}
|
this.setState((prevState)=>({
|
||||||
},
|
isSaving : true,
|
||||||
|
errors : null,
|
||||||
save : function(){
|
htmlErrors : Markdown.validate(prevState.brew.text)
|
||||||
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
|
}));
|
||||||
|
|
||||||
this.setState({
|
request
|
||||||
isSaving : true,
|
.put(`/api/${this.props.brew.editId}`)
|
||||||
errors : null,
|
.send(this.state.brew)
|
||||||
htmlErrors : Markdown.validate(this.state.brew.text)
|
.end((err, res)=>{
|
||||||
});
|
if(err){
|
||||||
|
this.setState({
|
||||||
request
|
errors : err,
|
||||||
.put('/api/update/' + this.props.brew.editId)
|
});
|
||||||
.send(this.state.brew)
|
} else {
|
||||||
.end((err, res) => {
|
this.savedBrew = res.body;
|
||||||
if(err){
|
this.setState({
|
||||||
this.setState({
|
isPending : false,
|
||||||
errors : err,
|
isSaving : false,
|
||||||
})
|
});
|
||||||
}else{
|
}
|
||||||
this.savedBrew = res.body;
|
});
|
||||||
this.setState({
|
},
|
||||||
isPending : false,
|
|
||||||
isSaving : false,
|
renderSaveButton : function(){
|
||||||
lastUpdated : res.body.updatedAt
|
if(this.state.errors){
|
||||||
})
|
let errMsg = '';
|
||||||
}
|
try {
|
||||||
})
|
errMsg += `${this.state.errors.toString()}\n\n`;
|
||||||
},
|
errMsg += `\`\`\`\n${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
|
||||||
|
} catch (e){}
|
||||||
renderSaveButton : function(){
|
|
||||||
if(this.state.errors){
|
return <Nav.item className='save error' icon='fa-warning'>
|
||||||
var errMsg = '';
|
Oops!
|
||||||
try{
|
<div className='errorContainer'>
|
||||||
errMsg += this.state.errors.toString() + '\n\n';
|
Looks like there was a problem saving. <br />
|
||||||
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
Report the issue <a target='_blank' rel='noopener noreferrer'
|
||||||
}catch(e){}
|
href={`https://github.com/stolksdorf/naturalcrit/issues/new?body=${encodeURIComponent(errMsg)}`}>
|
||||||
|
here
|
||||||
return <Nav.item className='save error' icon="fa-warning">
|
</a>.
|
||||||
Oops!
|
</div>
|
||||||
<div className='errorContainer'>
|
</Nav.item>;
|
||||||
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
|
if(this.state.isSaving){
|
||||||
</a>.
|
return <Nav.item className='save' icon='fa-spinner fa-spin'>saving...</Nav.item>;
|
||||||
</div>
|
}
|
||||||
</Nav.item>
|
if(this.state.isPending && this.hasChanges()){
|
||||||
}
|
return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item>;
|
||||||
|
}
|
||||||
if(this.state.isSaving){
|
if(!this.state.isPending && !this.state.isSaving){
|
||||||
return <Nav.item className='save' icon="fa-spinner fa-spin">saving...</Nav.item>
|
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>
|
||||||
if(!this.state.isPending && !this.state.isSaving){
|
<Nav.section>
|
||||||
return <Nav.item className='save saved'>saved.</Nav.item>
|
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
|
||||||
}
|
</Nav.section>
|
||||||
},
|
|
||||||
renderNavbar : function(){
|
<Nav.section>
|
||||||
return <Navbar>
|
{this.renderSaveButton()}
|
||||||
<Nav.section>
|
<ReportIssue />
|
||||||
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
|
<Nav.item newTab={true} href={`/share/${this.props.brew.shareId}`} color='teal' icon='fa-share-alt'>
|
||||||
</Nav.section>
|
Share
|
||||||
<Nav.section>
|
</Nav.item>
|
||||||
{this.renderSaveButton()}
|
<PrintLink shareId={this.props.brew.shareId} />
|
||||||
{/*<RecentlyEdited brew={this.props.brew} />*/}
|
<RecentNavItem brew={this.props.brew} storageKey='edit' />
|
||||||
<ReportIssue />
|
<Account />
|
||||||
<Nav.item newTab={true} href={'/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'>
|
</Nav.section>
|
||||||
Share
|
</Navbar>;
|
||||||
</Nav.item>
|
},
|
||||||
<PrintLink shareId={this.props.brew.shareId} />
|
|
||||||
<Account />
|
render : function(){
|
||||||
</Nav.section>
|
return <div className='editPage page'>
|
||||||
</Navbar>
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
},
|
{this.renderNavbar()}
|
||||||
|
|
||||||
render : function(){
|
<div className='content'>
|
||||||
return <div className='editPage page'>
|
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||||
{this.renderNavbar()}
|
<Editor
|
||||||
|
ref='editor'
|
||||||
<div className='content'>
|
value={this.state.brew.text}
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
onChange={this.handleTextChange}
|
||||||
<Editor
|
metadata={this.state.brew}
|
||||||
ref='editor'
|
onMetadataChange={this.handleMetadataChange}
|
||||||
value={this.state.brew.text}
|
/>
|
||||||
onChange={this.handleTextChange}
|
<BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} />
|
||||||
metadata={this.state.brew}
|
</SplitPane>
|
||||||
onMetadataChange={this.handleMetadataChange}
|
</div>
|
||||||
/>
|
</div>;
|
||||||
<BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} />
|
}
|
||||||
</SplitPane>
|
});
|
||||||
</div>
|
|
||||||
</div>
|
module.exports = EditPage;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = EditPage;
|
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
var React = require('react');
|
require('./errorPage.less');
|
||||||
var _ = require('lodash');
|
const React = require('react');
|
||||||
var cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
var Navbar = require('../../navbar/navbar.jsx');
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
var PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||||
var IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||||
var RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
|
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
var ErrorPage = React.createClass({
|
const ErrorPage = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
ver : '0.0.0',
|
ver : '0.0.0',
|
||||||
errorId: ''
|
errorId : ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -32,14 +34,14 @@ var ErrorPage = React.createClass({
|
|||||||
<Nav.section>
|
<Nav.section>
|
||||||
<PatreonNavItem />
|
<PatreonNavItem />
|
||||||
<IssueNavItem />
|
<IssueNavItem />
|
||||||
<RecentNavItem.both errorId={this.props.errorId} />
|
<RecentNavItem />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<BrewRenderer text={this.text} />
|
<BrewRenderer text={this.text} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module.exports = function(shareId){
|
|||||||
return function(event){
|
return function(event){
|
||||||
event = event || window.event;
|
event = event || window.event;
|
||||||
if((event.ctrlKey || event.metaKey) && event.keyCode == 80){
|
if((event.ctrlKey || event.metaKey) && event.keyCode == 80){
|
||||||
var win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank');
|
const win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank');
|
||||||
win.focus();
|
win.focus();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,92 +1,98 @@
|
|||||||
const React = require('react');
|
require('./homePage.less');
|
||||||
const _ = require('lodash');
|
const React = require('react');
|
||||||
const cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
const request = require("superagent");
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const request = require('superagent');
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
const { Meta } = require('vitreum/headtags');
|
||||||
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
|
||||||
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||||
|
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||||
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||||
const Editor = require('../../editor/editor.jsx');
|
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
|
const Editor = require('../../editor/editor.jsx');
|
||||||
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
const HomePage = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
welcomeText : '',
|
const HomePage = createClass({
|
||||||
ver : '0.0.0'
|
getDefaultProps : function() {
|
||||||
};
|
return {
|
||||||
},
|
welcomeText : '',
|
||||||
getInitialState: function() {
|
ver : '0.0.0'
|
||||||
return {
|
};
|
||||||
text: this.props.welcomeText
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
handleSave : function(){
|
getInitialState : function() {
|
||||||
request.post('/api')
|
return {
|
||||||
.send({
|
text : this.props.welcomeText
|
||||||
text : this.state.text
|
};
|
||||||
})
|
},
|
||||||
.end((err, res)=>{
|
handleSave : function(){
|
||||||
if(err) return;
|
request.post('/api')
|
||||||
var brew = res.body;
|
.send({
|
||||||
window.location = '/edit/' + brew.editId;
|
text : this.state.text
|
||||||
});
|
})
|
||||||
},
|
.end((err, res)=>{
|
||||||
handleSplitMove : function(){
|
if(err) return;
|
||||||
this.refs.editor.update();
|
const brew = res.body;
|
||||||
},
|
window.location = `/edit/${brew.editId}`;
|
||||||
handleTextChange : function(text){
|
});
|
||||||
this.setState({
|
},
|
||||||
text : text
|
handleSplitMove : function(){
|
||||||
});
|
this.refs.editor.update();
|
||||||
},
|
},
|
||||||
renderNavbar : function(){
|
handleTextChange : function(text){
|
||||||
return <Navbar ver={this.props.ver}>
|
this.setState({
|
||||||
<Nav.section>
|
text : text
|
||||||
<PatreonNavItem />
|
});
|
||||||
<IssueNavItem />
|
},
|
||||||
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
|
renderNavbar : function(){
|
||||||
Changelog
|
return <Navbar ver={this.props.ver}>
|
||||||
</Nav.item>
|
<Nav.section>
|
||||||
<RecentNavItem.both />
|
<PatreonNavItem />
|
||||||
<AccountNavItem />
|
<IssueNavItem />
|
||||||
{/*}
|
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
|
||||||
<Nav.item href='/new' color='green' icon='fa-external-link'>
|
Changelog
|
||||||
New Brew
|
</Nav.item>
|
||||||
</Nav.item>
|
<RecentNavItem />
|
||||||
*/}
|
<AccountNavItem />
|
||||||
</Nav.section>
|
{/*}
|
||||||
</Navbar>
|
<Nav.item href='/new' color='green' icon='fa-external-link'>
|
||||||
},
|
New Brew
|
||||||
|
</Nav.item>
|
||||||
render : function(){
|
*/}
|
||||||
return <div className='homePage page'>
|
</Nav.section>
|
||||||
{this.renderNavbar()}
|
</Navbar>;
|
||||||
|
},
|
||||||
<div className='content'>
|
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
render : function(){
|
||||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
return <div className='homePage page'>
|
||||||
<BrewRenderer text={this.state.text} />
|
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
|
||||||
</SplitPane>
|
{this.renderNavbar()}
|
||||||
</div>
|
|
||||||
|
<div className='content'>
|
||||||
<div className={cx('floatingSaveButton', {show : this.props.welcomeText != this.state.text})} onClick={this.handleSave}>
|
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||||
Save current <i className='fa fa-save' />
|
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
||||||
</div>
|
<BrewRenderer text={this.state.text} />
|
||||||
|
</SplitPane>
|
||||||
<a href='/new' className='floatingNewButton'>
|
</div>
|
||||||
Create your own <i className='fa fa-magic' />
|
|
||||||
</a>
|
<div className={cx('floatingSaveButton', { show: this.props.welcomeText != this.state.text })} onClick={this.handleSave}>
|
||||||
</div>
|
Save current <i className='fa fa-save' />
|
||||||
}
|
</div>
|
||||||
});
|
|
||||||
|
<a href='/new' className='floatingNewButton'>
|
||||||
module.exports = HomePage;
|
Create your own <i className='fa fa-magic' />
|
||||||
|
</a>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = HomePage;
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ With the next major release of Homebrewery, v3.0.0, this tool *will no longer su
|
|||||||
What's new in the latest update? Check out the full changelog [here](/changelog)
|
What's new in the latest update? Check out the full changelog [here](/changelog)
|
||||||
|
|
||||||
### Bugs, Issues, Suggestions?
|
### Bugs, Issues, Suggestions?
|
||||||
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!.
|
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) and let me know!.
|
||||||
|
|
||||||
### Legal Junk
|
### 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.
|
The Homebrewery is licensed using the [MIT License](https://github.com/naturalcrit/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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<img src='http://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;right:30px;width:280px' />
|
<img src='https://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;right:30px;width:280px' />
|
||||||
|
|
||||||
<div class='pageNumber'>1</div>
|
<div class='pageNumber'>1</div>
|
||||||
<div class='footnote'>PART 1 | FANCINESS</div>
|
<div class='footnote'>PART 1 | FANCINESS</div>
|
||||||
|
|||||||
@@ -1,161 +1,165 @@
|
|||||||
const React = require('react');
|
require('./newPage.less');
|
||||||
const _ = require('lodash');
|
const React = require('react');
|
||||||
const cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
const request = require("superagent");
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
const Markdown = require('naturalcrit/markdown.js');
|
const request = require('superagent');
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
|
||||||
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
|
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||||
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
const Editor = require('../../editor/editor.jsx');
|
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
|
const Editor = require('../../editor/editor.jsx');
|
||||||
const KEY = 'homebrewery-new';
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
const NewPage = React.createClass({
|
|
||||||
getInitialState: function() {
|
const KEY = 'homebrewery-new';
|
||||||
return {
|
|
||||||
metadata : {
|
const NewPage = createClass({
|
||||||
title : '',
|
getInitialState : function() {
|
||||||
description : '',
|
return {
|
||||||
tags : '',
|
metadata : {
|
||||||
published : false,
|
title : '',
|
||||||
authors : [],
|
description : '',
|
||||||
systems : []
|
tags : '',
|
||||||
},
|
published : false,
|
||||||
|
authors : [],
|
||||||
text: '',
|
systems : []
|
||||||
isSaving : false,
|
},
|
||||||
errors : []
|
|
||||||
};
|
text : '',
|
||||||
},
|
isSaving : false,
|
||||||
componentDidMount: function() {
|
errors : []
|
||||||
const storage = localStorage.getItem(KEY);
|
};
|
||||||
if(storage){
|
},
|
||||||
this.setState({
|
componentDidMount : function() {
|
||||||
text : storage
|
const storage = localStorage.getItem(KEY);
|
||||||
})
|
if(storage){
|
||||||
}
|
this.setState({
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
text : storage
|
||||||
},
|
});
|
||||||
componentWillUnmount: function() {
|
}
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
|
componentWillUnmount : function() {
|
||||||
handleControlKeys : function(e){
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
},
|
||||||
const S_KEY = 83;
|
|
||||||
const P_KEY = 80;
|
handleControlKeys : function(e){
|
||||||
if(e.keyCode == S_KEY) this.save();
|
if(!(e.ctrlKey || e.metaKey)) return;
|
||||||
if(e.keyCode == P_KEY) this.print();
|
const S_KEY = 83;
|
||||||
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
const P_KEY = 80;
|
||||||
e.stopPropagation();
|
if(e.keyCode == S_KEY) this.save();
|
||||||
e.preventDefault();
|
if(e.keyCode == P_KEY) this.print();
|
||||||
}
|
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
||||||
},
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
handleSplitMove : function(){
|
}
|
||||||
this.refs.editor.update();
|
},
|
||||||
},
|
|
||||||
|
handleSplitMove : function(){
|
||||||
handleMetadataChange : function(metadata){
|
this.refs.editor.update();
|
||||||
this.setState({
|
},
|
||||||
metadata : _.merge({}, this.state.metadata, metadata)
|
|
||||||
});
|
handleMetadataChange : function(metadata){
|
||||||
},
|
this.setState({
|
||||||
|
metadata : _.merge({}, this.state.metadata, metadata)
|
||||||
handleTextChange : function(text){
|
});
|
||||||
this.setState({
|
},
|
||||||
text : text,
|
|
||||||
errors : Markdown.validate(text)
|
handleTextChange : function(text){
|
||||||
});
|
this.setState({
|
||||||
localStorage.setItem(KEY, text);
|
text : text,
|
||||||
},
|
errors : Markdown.validate(text)
|
||||||
|
});
|
||||||
save : function(){
|
localStorage.setItem(KEY, text);
|
||||||
this.setState({
|
},
|
||||||
isSaving : true
|
|
||||||
});
|
save : function(){
|
||||||
|
this.setState({
|
||||||
request.post('/api')
|
isSaving : true
|
||||||
.send(_.merge({}, this.state.metadata, {
|
});
|
||||||
text : this.state.text
|
|
||||||
}))
|
request.post('/api')
|
||||||
.end((err, res)=>{
|
.send(_.merge({}, this.state.metadata, {
|
||||||
if(err){
|
text : this.state.text
|
||||||
this.setState({
|
}))
|
||||||
isSaving : false
|
.end((err, res)=>{
|
||||||
});
|
if(err){
|
||||||
return;
|
this.setState({
|
||||||
}
|
isSaving : false
|
||||||
window.onbeforeunload = function(){};
|
});
|
||||||
const brew = res.body;
|
return;
|
||||||
localStorage.removeItem(KEY);
|
}
|
||||||
window.location = '/edit/' + brew.editId;
|
window.onbeforeunload = function(){};
|
||||||
})
|
const brew = res.body;
|
||||||
},
|
localStorage.removeItem(KEY);
|
||||||
|
window.location = `/edit/${brew.editId}`;
|
||||||
renderSaveButton : function(){
|
});
|
||||||
if(this.state.isSaving){
|
},
|
||||||
return <Nav.item icon='fa-spinner fa-spin' className='saveButton'>
|
|
||||||
save...
|
renderSaveButton : function(){
|
||||||
</Nav.item>
|
if(this.state.isSaving){
|
||||||
}else{
|
return <Nav.item icon='fa-spinner fa-spin' className='saveButton'>
|
||||||
return <Nav.item icon='fa-save' className='saveButton' onClick={this.save}>
|
save...
|
||||||
save
|
</Nav.item>;
|
||||||
</Nav.item>
|
} else {
|
||||||
}
|
return <Nav.item icon='fa-save' className='saveButton' onClick={this.save}>
|
||||||
},
|
save
|
||||||
|
</Nav.item>;
|
||||||
print : function(){
|
}
|
||||||
localStorage.setItem('print', this.state.text);
|
},
|
||||||
window.open('/print?dialog=true&local=print','_blank');
|
|
||||||
},
|
print : function(){
|
||||||
|
localStorage.setItem('print', this.state.text);
|
||||||
renderLocalPrintButton : function(){
|
window.open('/print?dialog=true&local=print', '_blank');
|
||||||
return <Nav.item color='purple' icon='fa-file-pdf-o' onClick={this.print}>
|
},
|
||||||
get PDF
|
|
||||||
</Nav.item>
|
renderLocalPrintButton : function(){
|
||||||
},
|
return <Nav.item color='purple' icon='fa-file-pdf-o' onClick={this.print}>
|
||||||
|
get PDF
|
||||||
renderNavbar : function(){
|
</Nav.item>;
|
||||||
return <Navbar>
|
},
|
||||||
|
|
||||||
<Nav.section>
|
renderNavbar : function(){
|
||||||
<Nav.item className='brewTitle'>{this.state.metadata.title}</Nav.item>
|
return <Navbar>
|
||||||
</Nav.section>
|
|
||||||
|
<Nav.section>
|
||||||
<Nav.section>
|
<Nav.item className='brewTitle'>{this.state.metadata.title}</Nav.item>
|
||||||
{this.renderSaveButton()}
|
</Nav.section>
|
||||||
{this.renderLocalPrintButton()}
|
|
||||||
<IssueNavItem />
|
<Nav.section>
|
||||||
<AccountNavItem />
|
{this.renderSaveButton()}
|
||||||
</Nav.section>
|
{this.renderLocalPrintButton()}
|
||||||
</Navbar>
|
<IssueNavItem />
|
||||||
},
|
<RecentNavItem />
|
||||||
|
<AccountNavItem />
|
||||||
render : function(){
|
</Nav.section>
|
||||||
return <div className='newPage page'>
|
</Navbar>;
|
||||||
{this.renderNavbar()}
|
},
|
||||||
<div className='content'>
|
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
render : function(){
|
||||||
<Editor
|
return <div className='newPage page'>
|
||||||
ref='editor'
|
{this.renderNavbar()}
|
||||||
value={this.state.text}
|
<div className='content'>
|
||||||
onChange={this.handleTextChange}
|
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||||
metadata={this.state.metadata}
|
<Editor
|
||||||
onMetadataChange={this.handleMetadataChange}
|
ref='editor'
|
||||||
/>
|
value={this.state.text}
|
||||||
<BrewRenderer text={this.state.text} errors={this.state.errors} />
|
onChange={this.handleTextChange}
|
||||||
</SplitPane>
|
metadata={this.state.metadata}
|
||||||
</div>
|
onMetadataChange={this.handleMetadataChange}
|
||||||
</div>
|
/>
|
||||||
}
|
<BrewRenderer text={this.state.text} errors={this.state.errors} />
|
||||||
});
|
</SplitPane>
|
||||||
|
</div>
|
||||||
module.exports = NewPage;
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = NewPage;
|
||||||
|
|||||||
@@ -1,46 +1,52 @@
|
|||||||
|
require('./printPage.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
const { Meta } = require('vitreum/headtags');
|
||||||
const Markdown = require('naturalcrit/markdown.js');
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
|
|
||||||
const PrintPage = React.createClass({
|
const PrintPage = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
query : {},
|
query : {},
|
||||||
brew : {
|
brew : {
|
||||||
text : '',
|
text : '',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
brewText: this.props.brew.text
|
brewText : this.props.brew.text
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount : function() {
|
||||||
if(this.props.query.local){
|
if(this.props.query.local){
|
||||||
this.setState({ brewText : localStorage.getItem(this.props.query.local)});
|
this.setState((prevState, prevProps)=>({
|
||||||
|
brewText : localStorage.getItem(prevProps.query.local)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.props.query.dialog) window.print();
|
if(this.props.query.dialog) window.print();
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPages : function(){
|
renderPages : function(){
|
||||||
return _.map(this.state.brewText.split('\\page'), (page, index) => {
|
return _.map(this.state.brewText.split('\\page'), (page, index)=>{
|
||||||
return <div
|
return <div
|
||||||
className='phb'
|
className='phb'
|
||||||
id={`p${index + 1}`}
|
id={`p${index + 1}`}
|
||||||
dangerouslySetInnerHTML={{__html:Markdown.render(page)}}
|
dangerouslySetInnerHTML={{ __html: Markdown.render(page) }}
|
||||||
key={index} />;
|
key={index} />;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div>
|
return <div>
|
||||||
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
{this.renderPages()}
|
{this.renderPages()}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,71 +1,73 @@
|
|||||||
const React = require('react');
|
require('./sharePage.less');
|
||||||
const _ = require('lodash');
|
const React = require('react');
|
||||||
const cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const cx = require('classnames');
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
const { Meta } = require('vitreum/headtags');
|
||||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
|
||||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
//const RecentlyViewed = require('../../navbar/recent.navitem.jsx').viewed;
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
const Account = require('../../navbar/account.navitem.jsx');
|
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||||
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
|
const Account = require('../../navbar/account.navitem.jsx');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
const SharePage = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
const SharePage = createClass({
|
||||||
brew : {
|
getDefaultProps : function() {
|
||||||
title : '',
|
return {
|
||||||
text : '',
|
brew : {
|
||||||
shareId : null,
|
title : '',
|
||||||
createdAt : null,
|
text : '',
|
||||||
updatedAt : null,
|
shareId : null,
|
||||||
views : 0
|
createdAt : null,
|
||||||
}
|
updatedAt : null,
|
||||||
};
|
views : 0
|
||||||
},
|
}
|
||||||
|
};
|
||||||
componentDidMount: function() {
|
},
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
|
||||||
},
|
componentDidMount : function() {
|
||||||
componentWillUnmount: function() {
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
document.removeEventListener('keydown', this.handleControlKeys);
|
},
|
||||||
},
|
componentWillUnmount : function() {
|
||||||
handleControlKeys : function(e){
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
if(!(e.ctrlKey || e.metaKey)) return;
|
},
|
||||||
const P_KEY = 80;
|
handleControlKeys : function(e){
|
||||||
if(e.keyCode == P_KEY){
|
if(!(e.ctrlKey || e.metaKey)) return;
|
||||||
window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
const P_KEY = 80;
|
||||||
e.stopPropagation();
|
if(e.keyCode == P_KEY){
|
||||||
e.preventDefault();
|
window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
||||||
}
|
e.stopPropagation();
|
||||||
},
|
e.preventDefault();
|
||||||
|
}
|
||||||
render : function(){
|
},
|
||||||
return <div className='sharePage page'>
|
|
||||||
<Navbar>
|
render : function(){
|
||||||
<Nav.section>
|
return <div className='sharePage page'>
|
||||||
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
</Nav.section>
|
<Navbar>
|
||||||
|
<Nav.section>
|
||||||
<Nav.section>
|
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
||||||
<ReportIssue />
|
</Nav.section>
|
||||||
{/*<RecentlyViewed brew={this.props.brew} />*/}
|
|
||||||
<PrintLink shareId={this.props.brew.shareId} />
|
<Nav.section>
|
||||||
<Nav.item href={'/source/' + this.props.brew.shareId} color='teal' icon='fa-code'>
|
<PrintLink shareId={this.props.brew.shareId} />
|
||||||
source
|
<Nav.item href={`/source/${this.props.brew.shareId}`} color='teal' icon='fa-code'>
|
||||||
</Nav.item>
|
source
|
||||||
<Account />
|
</Nav.item>
|
||||||
</Nav.section>
|
<RecentNavItem brew={this.props.brew} storageKey='view' />
|
||||||
</Navbar>
|
<Account />
|
||||||
|
</Nav.section>
|
||||||
<div className='content'>
|
</Navbar>
|
||||||
<BrewRenderer text={this.props.brew.text} />
|
|
||||||
</div>
|
<div className='content'>
|
||||||
</div>
|
<BrewRenderer text={this.props.brew.text} />
|
||||||
}
|
</div>
|
||||||
});
|
</div>;
|
||||||
|
}
|
||||||
module.exports = SharePage;
|
});
|
||||||
|
|
||||||
|
module.exports = SharePage;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
.sharePage{
|
.sharePage{
|
||||||
|
.content{
|
||||||
|
overflow-y : hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
|
require('./brewItem.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const request = require('superagent');
|
||||||
|
|
||||||
const BrewItem = React.createClass({
|
const BrewItem = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
brew : {
|
brew : {
|
||||||
title : '',
|
title : '',
|
||||||
description : '',
|
description : '',
|
||||||
|
|
||||||
authors : []
|
authors : []
|
||||||
@@ -15,12 +18,35 @@ const BrewItem = React.createClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
renderEditLink: function(){
|
deleteBrew : function(){
|
||||||
|
if(this.props.brew.authors.length <= 1){
|
||||||
|
if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return;
|
||||||
|
if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return;
|
||||||
|
} else {
|
||||||
|
if(!confirm('Are you sure you want to remove this brew from your collection? This will remove you as an editor, but other owners will still be able to access the document.')) return;
|
||||||
|
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
request.delete(`/api/${this.props.brew.editId}`)
|
||||||
|
.send()
|
||||||
|
.end(function(err, res){
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDeleteBrewLink : function(){
|
||||||
if(!this.props.brew.editId) return;
|
if(!this.props.brew.editId) return;
|
||||||
|
|
||||||
return <a href={`/edit/${this.props.brew.editId}`} target='_blank'>
|
return <a onClick={this.deleteBrew}>
|
||||||
|
<i className='fa fa-trash' />
|
||||||
|
</a>;
|
||||||
|
},
|
||||||
|
renderEditLink : function(){
|
||||||
|
if(!this.props.brew.editId) return;
|
||||||
|
|
||||||
|
return <a href={`/edit/${this.props.brew.editId}`} target='_blank' rel='noopener noreferrer'>
|
||||||
<i className='fa fa-pencil' />
|
<i className='fa fa-pencil' />
|
||||||
</a>
|
</a>;
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
@@ -43,12 +69,13 @@ const BrewItem = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='links'>
|
<div className='links'>
|
||||||
<a href={`/share/${brew.shareId}`} target='_blank'>
|
<a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>
|
||||||
<i className='fa fa-share-alt' />
|
<i className='fa fa-share-alt' />
|
||||||
</a>
|
</a>
|
||||||
{this.renderEditLink()}
|
{this.renderEditLink()}
|
||||||
|
{this.renderDeleteBrewLink()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
|
require('./userPage.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
const Navbar = require('../../navbar/navbar.jsx');
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
|
|
||||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
const Account = require('../../navbar/account.navitem.jsx');
|
const Account = require('../../navbar/account.navitem.jsx');
|
||||||
const BrewItem = require('./brewItem/brewItem.jsx');
|
const BrewItem = require('./brewItem/brewItem.jsx');
|
||||||
|
|
||||||
const brew = {
|
// const brew = {
|
||||||
title : 'SUPER Long title woah now',
|
// title : 'SUPER Long title woah now',
|
||||||
authors : []
|
// authors : []
|
||||||
}
|
// };
|
||||||
|
|
||||||
const BREWS = _.times(25, ()=>{ return brew});
|
//const BREWS = _.times(25, ()=>{ return brew;});
|
||||||
|
|
||||||
|
|
||||||
const UserPage = React.createClass({
|
const UserPage = createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
username : '',
|
username : '',
|
||||||
brews : []
|
brews : []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -30,45 +32,41 @@ const UserPage = React.createClass({
|
|||||||
|
|
||||||
const sortedBrews = _.sortBy(brews, (brew)=>{ return brew.title; });
|
const sortedBrews = _.sortBy(brews, (brew)=>{ return brew.title; });
|
||||||
|
|
||||||
return _.map(sortedBrews, (brew, idx) => {
|
return _.map(sortedBrews, (brew, idx)=>{
|
||||||
return <BrewItem brew={brew} key={idx}/>
|
return <BrewItem brew={brew} key={idx}/>;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getSortedBrews : function(){
|
getSortedBrews : function(){
|
||||||
return _.groupBy(this.props.brews, (brew)=>{
|
return _.groupBy(this.props.brews, (brew)=>{
|
||||||
return (brew.published ? 'published' : 'private')
|
return (brew.published ? 'published' : 'private');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPrivateBrews : function(privateBrews){
|
|
||||||
if(!privateBrews || !privateBrews.length) return;
|
|
||||||
|
|
||||||
return [
|
|
||||||
<h1>{this.props.username}'s unpublished brews</h1>,
|
|
||||||
this.renderBrews(privateBrews)
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
const brews = this.getSortedBrews();
|
const brews = this.getSortedBrews();
|
||||||
|
|
||||||
return <div className='userPage page'>
|
return <div className='userPage page'>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<RecentNavItem.both />
|
<RecentNavItem />
|
||||||
<Account />
|
<Account />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<div className='phb'>
|
<div className='phb'>
|
||||||
<h1>{this.props.username}'s brews</h1>
|
<div>
|
||||||
{this.renderBrews(brews.published)}
|
<h1>{this.props.username}'s brews</h1>
|
||||||
{this.renderPrivateBrews(brews.private)}
|
{this.renderBrews(brews.published)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1>{this.props.username}'s unpublished brews</h1>
|
||||||
|
{this.renderBrews(brews.private)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
.phb{
|
.phb{
|
||||||
.noColumns();
|
.noColumns();
|
||||||
height : auto;
|
height : auto;
|
||||||
min-height : 279.4mm;
|
min-height : 279.4mm;
|
||||||
margin : 20px auto;
|
margin : 20px auto;
|
||||||
&::after{
|
&::after{
|
||||||
display : none;
|
display : none;
|
||||||
@@ -30,4 +30,4 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
@import (less) 'shared/naturalcrit/styles/reset.less';
|
@import (less) 'shared/naturalcrit/styles/reset.less';
|
||||||
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
|
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
|
||||||
@import (less) './client/homebrew/phbStyle/phb.assets.less';
|
@import (less) './client/homebrew/phbStyle/phb.assets.less';
|
||||||
@@ -156,6 +155,7 @@ body {
|
|||||||
margin-bottom : 1em;
|
margin-bottom : 1em;
|
||||||
font-size : 10pt;
|
font-size : 10pt;
|
||||||
thead{
|
thead{
|
||||||
|
display: table-row-group;
|
||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
th{
|
th{
|
||||||
vertical-align : bottom;
|
vertical-align : bottom;
|
||||||
@@ -337,8 +337,8 @@ body {
|
|||||||
p,blockquote,table{
|
p,blockquote,table{
|
||||||
z-index : 15;
|
z-index : 15;
|
||||||
-webkit-column-break-inside : avoid;
|
-webkit-column-break-inside : avoid;
|
||||||
column-break-inside : avoid;
|
page-break-inside : avoid;
|
||||||
overflow: hidden; /* Firefox fix */
|
break-inside : avoid;
|
||||||
}
|
}
|
||||||
//Better spacing for spell blocks
|
//Better spacing for spell blocks
|
||||||
h4+p+hr+ul{
|
h4+p+hr+ul{
|
||||||
@@ -355,7 +355,8 @@ body {
|
|||||||
}
|
}
|
||||||
li{
|
li{
|
||||||
-webkit-column-break-inside : avoid;
|
-webkit-column-break-inside : avoid;
|
||||||
column-break-inside : avoid;
|
page-break-inside : avoid;
|
||||||
|
break-inside : avoid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//*****************************
|
//*****************************
|
||||||
@@ -380,20 +381,8 @@ body {
|
|||||||
text-indent : -1em;
|
text-indent : -1em;
|
||||||
list-style-type : none;
|
list-style-type : none;
|
||||||
-webkit-column-break-inside : auto;
|
-webkit-column-break-inside : auto;
|
||||||
column-break-inside : auto;
|
page-break-inside : auto;
|
||||||
}
|
break-inside : auto;
|
||||||
}
|
|
||||||
//*****************************
|
|
||||||
// * PRINT
|
|
||||||
// *****************************/
|
|
||||||
.phb.print{
|
|
||||||
blockquote{
|
|
||||||
box-shadow : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media print {
|
|
||||||
.phb .descriptive, .phb blockquote{
|
|
||||||
box-shadow : none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//*****************************
|
//*****************************
|
||||||
@@ -415,7 +404,7 @@ body {
|
|||||||
border : initial;
|
border : initial;
|
||||||
border-style : solid;
|
border-style : solid;
|
||||||
border-image-outset : 25px 17px;
|
border-image-outset : 25px 17px;
|
||||||
border-image-repeat : round;
|
border-image-repeat : stretch;
|
||||||
border-image-slice : 150 200 150 200;
|
border-image-slice : 150 200 150 200;
|
||||||
border-image-source : @frameBorderImage;
|
border-image-source : @frameBorderImage;
|
||||||
border-image-width : 47px;
|
border-image-width : 47px;
|
||||||
@@ -423,9 +412,9 @@ body {
|
|||||||
margin-bottom : 10px;
|
margin-bottom : 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//*****************************
|
//************************************
|
||||||
// * CLASS TABLE
|
// * DESCRIPTIVE TEXT BOX
|
||||||
// *****************************/
|
// ************************************/
|
||||||
.phb .descriptive{
|
.phb .descriptive{
|
||||||
display : block-inline;
|
display : block-inline;
|
||||||
margin-bottom : 1em;
|
margin-bottom : 1em;
|
||||||
@@ -433,7 +422,7 @@ body {
|
|||||||
font-family : ScalySans;
|
font-family : ScalySans;
|
||||||
border-style : solid;
|
border-style : solid;
|
||||||
border-width : 7px;
|
border-width : 7px;
|
||||||
border-image : @descriptiveBoxImage 12 round;
|
border-image : @descriptiveBoxImage 12 stretch;
|
||||||
border-image-outset : 4px;
|
border-image-outset : 4px;
|
||||||
box-shadow : 0px 0px 6px #faf7ea;
|
box-shadow : 0px 0px 6px #faf7ea;
|
||||||
p{
|
p{
|
||||||
@@ -462,7 +451,8 @@ body {
|
|||||||
// *****************************/
|
// *****************************/
|
||||||
.phb .toc{
|
.phb .toc{
|
||||||
-webkit-column-break-inside : avoid;
|
-webkit-column-break-inside : avoid;
|
||||||
column-break-inside : avoid;
|
page-break-inside : avoid;
|
||||||
|
break-inside : avoid;
|
||||||
a{
|
a{
|
||||||
color : black;
|
color : black;
|
||||||
text-decoration : none;
|
text-decoration : none;
|
||||||
@@ -477,4 +467,4 @@ body {
|
|||||||
&>ul>li{
|
&>ul>li{
|
||||||
margin-bottom : 10px;
|
margin-bottom : 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
module.exports = function(vitreum){
|
module.exports = async (name, props={})=>{
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||||
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
<link href=${`/${name}/bundle.css`} rel='stylesheet'></link>
|
||||||
<title>The Homebrewery - NaturalCrit</title>
|
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
||||||
${vitreum.head}
|
<title>The Homebrewery - NaturalCrit</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main id="reactRoot">${vitreum.body}</main>
|
<main id="reactRoot">${require(`../build/${name}/ssr.js`)(props)}</main>
|
||||||
</body>
|
</body>
|
||||||
${vitreum.js}
|
<script src=${`/${name}/bundle.js`}></script>
|
||||||
</html>
|
<script>start_app(${JSON.stringify(props)})</script>
|
||||||
`;
|
</html>
|
||||||
}
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
45
contributing.md
Normal file
45
contributing.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Contributing to Homebrewery
|
||||||
|
|
||||||
|
## How can I contribute?
|
||||||
|
|
||||||
|
### Improve documentation
|
||||||
|
|
||||||
|
As a user of Homebrewery you're the perfect candidate to help us improve our documentation. Typo corrections, error fixes, better explanations, more examples, etc. Open issues for things that could be improved. Anything. Even improvements to this document.
|
||||||
|
|
||||||
|
|
||||||
|
### Improve issues
|
||||||
|
|
||||||
|
Some issues are created with missing information, not reproducible, or plain invalid. Help make them easier to resolve. Handling issues takes a lot of time that we could rather spend on fixing bugs and adding features.
|
||||||
|
|
||||||
|
|
||||||
|
### Write code
|
||||||
|
|
||||||
|
You can use issue labels to discover issues you could help out with:
|
||||||
|
|
||||||
|
* [`blocked` issues](https://github.com/naturalcrit/homebrewery/labels/blocked) need help getting unstuck
|
||||||
|
* [`bug` issues](https://github.com/naturalcrit/homebrewery/labels/bug) are known bugs we'd like to fix
|
||||||
|
* [`feature` issues](https://github.com/naturalcrit/homebrewery/labels/feature) are features we're open to including
|
||||||
|
* [`help wanted`](https://github.com/naturalcrit/homebrewery/labels/help%20wanted) labels are especially useful.
|
||||||
|
|
||||||
|
If you're updating dependencies, please make sure you use npm@5.6.0 and commit the updated `package-lock.json` file.
|
||||||
|
|
||||||
|
You can also refer to the [Development Roadmap on Trello](https://trello.com/b/q6kE29F8/development-roadmap)
|
||||||
|
|
||||||
|
|
||||||
|
## Submitting an issue
|
||||||
|
|
||||||
|
- The issue tracker is for issues. Use the [subreddit](https://www.reddit.com/r/homebrewery/) for support.
|
||||||
|
- Search the issue tracker before opening an issue.
|
||||||
|
- Use a clear and descriptive title.
|
||||||
|
- Include as much information as possible: Steps to reproduce the issue, error message, browser type and version, etc.
|
||||||
|
|
||||||
|
|
||||||
|
## Submitting a pull request
|
||||||
|
|
||||||
|
- Non-trivial changes are often best discussed in an issue first, to prevent you from doing unnecessary work.
|
||||||
|
- For ambitious tasks, you should try to get your work in front of the community for feedback as soon as possible. Open a pull request as soon as you have done the minimum needed to demonstrate your idea. At this early stage, don't worry about making things perfect, or 100% complete. Add a [WIP] prefix to the title, and describe what you still need to do. This lets reviewers know not to nit-pick small details or point out improvements you already know you need to make.
|
||||||
|
- New features should be accompanied with tests and documentation if applicable.
|
||||||
|
- Lint and test before submitting the pull request by running `$ npm run verify`.
|
||||||
|
- If your code is not passing Linting checks due to a non-fixable warning, and you feel it's valid (eg. we lint on a file being too long, but sometimes a file just _has_ to be long), add `/* eslint-disable [rule-name] */` to the top of the file. Be sure to justfiy your lint override in your PR description.
|
||||||
|
- Use a clear and descriptive title for the pull request and commits.
|
||||||
|
- You might be asked to do changes to your pull request. There's never a need to open another pull request. [Just update the existing one.](https://github.com/RichardLitt/knowledge/blob/master/github/amending-a-commit-guide.md)
|
||||||
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: mongo:latest
|
||||||
|
volumes:
|
||||||
|
- mongodata:/data/db
|
||||||
|
homebrewery:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: homebrewery
|
||||||
|
environment:
|
||||||
|
MONGODB_URI: mongodb://mongodb/homebrewery
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
mongodata:
|
||||||
7790
package-lock.json
generated
Normal file
7790
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
85
package.json
85
package.json
@@ -1,39 +1,74 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "2.7.1",
|
"version": "2.8.2",
|
||||||
|
"engines": {
|
||||||
|
"node": "12.16.x"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/naturalcrit/homebrewery.git"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
"quick": "node scripts/quick.js",
|
"quick": "node scripts/quick.js",
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/buildHomebrew.js",
|
||||||
|
"buildall": "node scripts/buildHomebrew.js && node scripts/buildAdmin.js",
|
||||||
|
"lint": "eslint --fix **/*.{js,jsx}",
|
||||||
|
"lint:dry": "eslint **/*.{js,jsx}",
|
||||||
|
"circleci": "npm test && eslint **/*.{js,jsx} --max-warnings=0",
|
||||||
|
"verify": "npm run lint && npm test",
|
||||||
|
"test": "pico-check",
|
||||||
|
"test:dev": "pico-check -v -w",
|
||||||
"phb": "node scripts/phb.js",
|
"phb": "node scripts/phb.js",
|
||||||
"prod": "set NODE_ENV=production&& npm run build",
|
"prod": "set NODE_ENV=production && npm run build",
|
||||||
"postinstall": "npm run build",
|
"postinstall": "npm run buildall",
|
||||||
"start": "node server.js"
|
"start": "node server.js"
|
||||||
},
|
},
|
||||||
"author": "stolksdorf",
|
"author": "stolksdorf",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"eslintIgnore": [
|
||||||
|
"build/*"
|
||||||
|
],
|
||||||
|
"pico-check": {
|
||||||
|
"require": "./tests/test.init.js"
|
||||||
|
},
|
||||||
|
"babel": {
|
||||||
|
"presets": [
|
||||||
|
"env",
|
||||||
|
"react"
|
||||||
|
]
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-preset-env": "^1.1.8",
|
"@babel/core": "^7.9.0",
|
||||||
"basic-auth": "^1.0.3",
|
"@babel/preset-env": "^7.9.6",
|
||||||
"body-parser": "^1.14.2",
|
"@babel/preset-react": "^7.9.4",
|
||||||
"classnames": "^2.2.0",
|
"body-parser": "^1.19.0",
|
||||||
"codemirror": "^5.22.0",
|
"classnames": "^2.2.6",
|
||||||
"cookie-parser": "^1.4.3",
|
"codemirror": "^5.54.0",
|
||||||
"express": "^4.13.3",
|
"cookie-parser": "^1.4.5",
|
||||||
"jwt-simple": "^0.5.1",
|
"create-react-class": "^15.6.3",
|
||||||
"lodash": "^4.11.2",
|
"express": "^4.17.1",
|
||||||
"marked": "^0.3.5",
|
"fs-extra": "9.0.0",
|
||||||
"moment": "^2.11.0",
|
"jwt-simple": "^0.5.6",
|
||||||
"mongoose": "^4.3.3",
|
"less": "^3.11.1",
|
||||||
"nconf": "^0.8.4",
|
"lodash": "^4.17.15",
|
||||||
"pico-flux": "^1.1.0",
|
"marked": "^0.3.19",
|
||||||
"pico-router": "^1.1.0",
|
"moment": "^2.26.0",
|
||||||
"react": "^15.0.2",
|
"mongoose": "^5.9.15",
|
||||||
"react-dom": "^15.0.2",
|
"nconf": "^0.10.0",
|
||||||
"shortid": "^2.2.4",
|
"prop-types": "15.7.2",
|
||||||
"striptags": "^2.1.1",
|
"query-string": "6.12.1",
|
||||||
"superagent": "^1.6.1",
|
"react": "^16.13.1",
|
||||||
"vitreum": "^4.0.12"
|
"react-dom": "^16.13.1",
|
||||||
|
"react-router-dom": "5.2.0",
|
||||||
|
"shortid": "^2.2.15",
|
||||||
|
"superagent": "^5.2.2",
|
||||||
|
"vitreum": "github:calculuschild/vitreum#21a8e1c9421f1d3a3b474c12f480feb2fbd28c5b"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^7.0.0",
|
||||||
|
"eslint-plugin-react": "^7.20.0",
|
||||||
|
"pico-check": "^1.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
robots.txt
Normal file
2
robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Notes
|
||||||
|
User-agent: *
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
const label = 'build';
|
|
||||||
console.time(label);
|
|
||||||
|
|
||||||
const clean = require('vitreum/steps/clean.js');
|
|
||||||
const jsx = require('vitreum/steps/jsx.js').partial;
|
|
||||||
const lib = require('vitreum/steps/libs.js').partial;
|
|
||||||
const less = require('vitreum/steps/less.js').partial;
|
|
||||||
const asset = require('vitreum/steps/assets.js').partial;
|
|
||||||
|
|
||||||
const Proj = require('./project.json');
|
|
||||||
|
|
||||||
clean()
|
|
||||||
.then(lib(Proj.libs))
|
|
||||||
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
|
|
||||||
.then(less('homebrew', ['./shared']))
|
|
||||||
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
|
|
||||||
.then(less('admin', ['./shared']))
|
|
||||||
.then(asset(Proj.assets, ['./shared', './client']))
|
|
||||||
.then(console.timeEnd.bind(console, label))
|
|
||||||
.catch(console.error);
|
|
||||||
31
scripts/buildAdmin.js
Normal file
31
scripts/buildAdmin.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const Proj = require('./project.json');
|
||||||
|
|
||||||
|
const { pack } = require('vitreum');
|
||||||
|
const isDev = !!process.argv.find((arg)=>arg=='--dev');
|
||||||
|
|
||||||
|
const lessTransform = require('vitreum/transforms/less.js');
|
||||||
|
const assetTransform = require('vitreum/transforms/asset.js');
|
||||||
|
//const Meta = require('vitreum/headtags');
|
||||||
|
|
||||||
|
const transforms = {
|
||||||
|
'.less' : lessTransform,
|
||||||
|
'*' : assetTransform('./build')
|
||||||
|
};
|
||||||
|
|
||||||
|
const build = async ({ bundle, render, ssr })=>{
|
||||||
|
await fs.outputFile('./build/admin/bundle.css', await lessTransform.generate({ paths: './shared' }));
|
||||||
|
await fs.outputFile('./build/admin/bundle.js', bundle);
|
||||||
|
await fs.outputFile('./build/admin/ssr.js', ssr);
|
||||||
|
await fs.outputFile('./build/admin/render.js', render);
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.emptyDirSync('./build/admin');
|
||||||
|
pack('./client/admin/admin.jsx', {
|
||||||
|
paths : ['./shared'],
|
||||||
|
libs : Proj.libs,
|
||||||
|
dev : isDev && build,
|
||||||
|
transforms
|
||||||
|
})
|
||||||
|
.then(build)
|
||||||
|
.catch(console.error);
|
||||||
31
scripts/buildHomebrew.js
Normal file
31
scripts/buildHomebrew.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const Proj = require('./project.json');
|
||||||
|
|
||||||
|
const { pack } = require('vitreum');
|
||||||
|
const isDev = !!process.argv.find((arg)=>arg=='--dev');
|
||||||
|
|
||||||
|
const lessTransform = require('vitreum/transforms/less.js');
|
||||||
|
const assetTransform = require('vitreum/transforms/asset.js');
|
||||||
|
//const Meta = require('vitreum/headtags');
|
||||||
|
|
||||||
|
const transforms = {
|
||||||
|
'.less' : lessTransform,
|
||||||
|
'*' : assetTransform('./build')
|
||||||
|
};
|
||||||
|
|
||||||
|
const build = async ({ bundle, render, ssr })=>{
|
||||||
|
await fs.outputFile('./build/homebrew/bundle.css', await lessTransform.generate({ paths: './shared' }));
|
||||||
|
await fs.outputFile('./build/homebrew/bundle.js', bundle);
|
||||||
|
await fs.outputFile('./build/homebrew/ssr.js', ssr);
|
||||||
|
await fs.outputFile('./build/homebrew/render.js', render);
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.emptyDirSync('./build/homebrew');
|
||||||
|
pack('./client/homebrew/homebrew.jsx', {
|
||||||
|
paths : ['./shared'],
|
||||||
|
libs : Proj.libs,
|
||||||
|
dev : isDev && build,
|
||||||
|
transforms
|
||||||
|
})
|
||||||
|
.then(build)
|
||||||
|
.catch(console.error);
|
||||||
@@ -1,23 +1,22 @@
|
|||||||
const label = 'dev';
|
const label = 'dev';
|
||||||
console.time(label);
|
console.time(label);
|
||||||
|
|
||||||
const jsx = require('vitreum/steps/jsx.watch.js').partial;
|
const jsx = require('vitreum/steps/jsx.watch.js');
|
||||||
const less = require('vitreum/steps/less.watch.js').partial;
|
const less = require('vitreum/steps/less.watch.js');
|
||||||
const assets = require('vitreum/steps/assets.watch.js').partial;
|
const assets = require('vitreum/steps/assets.watch.js');
|
||||||
const server = require('vitreum/steps/server.watch.js').partial;
|
const server = require('vitreum/steps/server.watch.js');
|
||||||
const livereload = require('vitreum/steps/livereload.js').partial;
|
const livereload = require('vitreum/steps/livereload.js');
|
||||||
|
|
||||||
const Proj = require('./project.json');
|
const Proj = require('./project.json');
|
||||||
|
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, './shared'))
|
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', { libs: Proj.libs, shared: ['./shared'] }))
|
||||||
.then(less('homebrew', './shared'))
|
.then((deps)=>less('homebrew', { shared: ['./shared'] }, deps))
|
||||||
|
.then(()=>jsx('admin', './client/admin/admin.jsx', { libs: Proj.libs, shared: ['./shared'] }))
|
||||||
|
.then((deps)=>less('admin', { shared: ['./shared'] }, deps))
|
||||||
|
|
||||||
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
|
.then(()=>assets(Proj.assets, ['./shared', './client']))
|
||||||
.then(less('admin', './shared'))
|
.then(()=>livereload())
|
||||||
|
.then(()=>server('./server.js', ['server']))
|
||||||
.then(assets(Proj.assets, ['./shared', './client']))
|
|
||||||
.then(livereload())
|
|
||||||
.then(server('./server.js', ['server']))
|
|
||||||
.then(console.timeEnd.bind(console, label))
|
.then(console.timeEnd.bind(console, label))
|
||||||
.catch(console.error)
|
.catch(console.error);
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
const less = require('less');
|
const less = require('less');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
less.render(fs.readFileSync('./client/homebrew/phbStyle/phb.style.less', 'utf8'), {compress : true})
|
less.render(fs.readFileSync('./client/homebrew/phbStyle/phb.style.less', 'utf8'), { compress: true })
|
||||||
.then((output) => {
|
.then((output)=>{
|
||||||
fs.writeFileSync('./phb.standalone.css', output.css);
|
fs.writeFileSync('./phb.standalone.css', output.css);
|
||||||
console.log('phb.standalone.css created!');
|
console.log('phb.standalone.css created!');
|
||||||
}, (err) => {
|
}, (err)=>{
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
"libs" : [
|
"libs" : [
|
||||||
"react",
|
"react",
|
||||||
"react-dom",
|
"react-dom",
|
||||||
|
"create-react-class",
|
||||||
"lodash",
|
"lodash",
|
||||||
"classnames",
|
"classnames",
|
||||||
"codemirror",
|
"codemirror",
|
||||||
@@ -12,8 +13,6 @@
|
|||||||
"codemirror/mode/javascript/javascript.js",
|
"codemirror/mode/javascript/javascript.js",
|
||||||
"moment",
|
"moment",
|
||||||
"superagent",
|
"superagent",
|
||||||
"marked",
|
"marked"
|
||||||
"pico-router",
|
|
||||||
"pico-flux"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
288
server.js
288
server.js
@@ -1,141 +1,147 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const jwt = require('jwt-simple');
|
const jwt = require('jwt-simple');
|
||||||
const express = require("express");
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use(express.static(__dirname + '/build'));''
|
app.use(express.static(`${__dirname}/build`));
|
||||||
app.use(require('body-parser').json({limit: '25mb'}));
|
app.use(require('body-parser').json({ limit: '25mb' }));
|
||||||
app.use(require('cookie-parser')());
|
app.use(require('cookie-parser')());
|
||||||
|
app.use(require('./server/forcessl.mw.js'));
|
||||||
const config = require('nconf')
|
|
||||||
.argv()
|
const config = require('nconf')
|
||||||
.env({ lowerCase: true })
|
.argv()
|
||||||
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
|
.env({ lowerCase: true })
|
||||||
.file('defaults', { file: 'config/default.json' });
|
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
|
||||||
|
.file('defaults', { file: 'config/default.json' });
|
||||||
//DB
|
|
||||||
require('mongoose')
|
//DB
|
||||||
.connect(process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/naturalcrit')
|
const mongoose = require('mongoose');
|
||||||
.connection.on('error', () => {
|
mongoose.connect(config.get('mongodb_uri') || config.get('mongolab_uri') || 'mongodb://localhost/naturalcrit',
|
||||||
console.log('Error : Could not connect to a Mongo Database.');
|
{ retryWrites: false, useNewUrlParser: true });
|
||||||
console.log(' If you are running locally, make sure mongodb.exe is running.');
|
mongoose.connection.on('error', ()=>{
|
||||||
});
|
console.log('Error : Could not connect to a Mongo Database.');
|
||||||
|
console.log(' If you are running locally, make sure mongodb.exe is running.');
|
||||||
|
throw 'Can not connect to Mongo';
|
||||||
//Account MIddleware
|
});
|
||||||
app.use((req, res, next) => {
|
|
||||||
if(req.cookies && req.cookies.nc_session){
|
|
||||||
try{
|
//Account Middleware
|
||||||
req.account = jwt.decode(req.cookies.nc_session, config.get('secret'));
|
app.use((req, res, next)=>{
|
||||||
}catch(e){}
|
if(req.cookies && req.cookies.nc_session){
|
||||||
}
|
try {
|
||||||
return next();
|
req.account = jwt.decode(req.cookies.nc_session, config.get('secret'));
|
||||||
});
|
} catch (e){}
|
||||||
|
}
|
||||||
|
return next();
|
||||||
app.use(require('./server/homebrew.api.js'));
|
});
|
||||||
app.use(require('./server/admin.api.js'));
|
|
||||||
|
|
||||||
|
app.use(require('./server/homebrew.api.js'));
|
||||||
const HomebrewModel = require('./server/homebrew.model.js').model;
|
app.use(require('./server/admin.api.js'));
|
||||||
const welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
|
|
||||||
const changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
|
|
||||||
|
const HomebrewModel = require('./server/homebrew.model.js').model;
|
||||||
|
const welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
|
||||||
|
const changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
|
||||||
//Source page
|
|
||||||
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)}
|
String.prototype.replaceAll = function(s, r){return this.split(s).join(r);};
|
||||||
app.get('/source/:id', (req, res)=>{
|
|
||||||
HomebrewModel.get({shareId : req.params.id})
|
//Robots.txt
|
||||||
.then((brew)=>{
|
app.get('/robots.txt', (req, res)=>{
|
||||||
const text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
|
return res.sendFile(`${__dirname}/robots.txt`);
|
||||||
return res.send(`<code><pre>${text}</pre></code>`);
|
});
|
||||||
})
|
|
||||||
.catch((err)=>{
|
//Source page
|
||||||
console.log(err);
|
app.get('/source/:id', (req, res)=>{
|
||||||
return res.status(404).send('Could not find Homebrew with that id');
|
HomebrewModel.get({ shareId: req.params.id })
|
||||||
})
|
.then((brew)=>{
|
||||||
});
|
const text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
|
||||||
|
return res.send(`<code><pre style="white-space: pre-wrap;">${text}</pre></code>`);
|
||||||
|
})
|
||||||
app.get('/user/:username', (req, res, next) => {
|
.catch((err)=>{
|
||||||
const fullAccess = req.account && (req.account.username == req.params.username);
|
console.log(err);
|
||||||
HomebrewModel.getByUser(req.params.username, fullAccess)
|
return res.status(404).send('Could not find Homebrew with that id');
|
||||||
.then((brews) => {
|
});
|
||||||
req.brews = brews;
|
});
|
||||||
return next();
|
|
||||||
})
|
//User Page
|
||||||
.catch((err) => {
|
app.get('/user/:username', (req, res, next)=>{
|
||||||
console.log(err);
|
const fullAccess = req.account && (req.account.username == req.params.username);
|
||||||
})
|
HomebrewModel.getByUser(req.params.username, fullAccess)
|
||||||
})
|
.then((brews)=>{
|
||||||
|
req.brews = brews;
|
||||||
|
return next();
|
||||||
app.get('/edit/:id', (req, res, next)=>{
|
})
|
||||||
HomebrewModel.get({editId : req.params.id})
|
.catch((err)=>{
|
||||||
.then((brew)=>{
|
console.log(err);
|
||||||
req.brew = brew.sanatize();
|
});
|
||||||
return next();
|
});
|
||||||
})
|
|
||||||
.catch((err)=>{
|
//Edit Page
|
||||||
console.log(err);
|
app.get('/edit/:id', (req, res, next)=>{
|
||||||
return res.status(400).send(`Can't get that`);
|
HomebrewModel.get({ editId: req.params.id })
|
||||||
});
|
.then((brew)=>{
|
||||||
});
|
req.brew = brew.sanatize();
|
||||||
|
return next();
|
||||||
//Share Page
|
})
|
||||||
app.get('/share/:id', (req, res, next)=>{
|
.catch((err)=>{
|
||||||
HomebrewModel.get({shareId : req.params.id})
|
console.log(err);
|
||||||
.then((brew)=>{
|
return res.status(400).send(`Can't get that`);
|
||||||
return brew.increaseView();
|
});
|
||||||
})
|
});
|
||||||
.then((brew)=>{
|
|
||||||
req.brew = brew.sanatize(true);
|
//Share Page
|
||||||
return next();
|
app.get('/share/:id', (req, res, next)=>{
|
||||||
})
|
HomebrewModel.get({ shareId: req.params.id })
|
||||||
.catch((err)=>{
|
.then((brew)=>{
|
||||||
console.log(err);
|
return brew.increaseView();
|
||||||
return res.status(400).send(`Can't get that`);
|
})
|
||||||
});
|
.then((brew)=>{
|
||||||
});
|
req.brew = brew.sanatize(true);
|
||||||
|
return next();
|
||||||
//Print Page
|
})
|
||||||
app.get('/print/:id', (req, res, next)=>{
|
.catch((err)=>{
|
||||||
HomebrewModel.get({shareId : req.params.id})
|
console.log(err);
|
||||||
.then((brew)=>{
|
return res.status(400).send(`Can't get that`);
|
||||||
req.brew = brew.sanatize(true);
|
});
|
||||||
return next();
|
});
|
||||||
})
|
|
||||||
.catch((err)=>{
|
//Print Page
|
||||||
console.log(err);
|
app.get('/print/:id', (req, res, next)=>{
|
||||||
return res.status(400).send(`Can't get that`);
|
HomebrewModel.get({ shareId: req.params.id })
|
||||||
});
|
.then((brew)=>{
|
||||||
});
|
req.brew = brew.sanatize(true);
|
||||||
|
return next();
|
||||||
|
})
|
||||||
//Render Page
|
.catch((err)=>{
|
||||||
const render = require('vitreum/steps/render');
|
console.log(err);
|
||||||
const templateFn = require('./client/template.js');
|
return res.status(400).send(`Can't get that`);
|
||||||
app.use((req, res) => {
|
});
|
||||||
render('homebrew', templateFn, {
|
});
|
||||||
version : require('./package.json').version,
|
|
||||||
url: req.originalUrl,
|
|
||||||
welcomeText : welcomeText,
|
//Render the page
|
||||||
changelog : changelogText,
|
//const render = require('.build/render');
|
||||||
brew : req.brew,
|
const templateFn = require('./client/template.js');
|
||||||
brews : req.brews,
|
app.use((req, res)=>{
|
||||||
account : req.account
|
const props = {
|
||||||
})
|
version : require('./package.json').version,
|
||||||
.then((page) => {
|
url : req.originalUrl,
|
||||||
return res.send(page)
|
welcomeText : welcomeText,
|
||||||
})
|
changelog : changelogText,
|
||||||
.catch((err) => {
|
brew : req.brew,
|
||||||
console.log(err);
|
brews : req.brews,
|
||||||
return res.sendStatus(500);
|
account : req.account,
|
||||||
});
|
};
|
||||||
});
|
templateFn('homebrew', props)
|
||||||
|
.then((page)=>res.send(page))
|
||||||
|
.catch((err)=>{
|
||||||
const PORT = process.env.PORT || 8000;
|
console.log(err);
|
||||||
app.listen(PORT);
|
return res.sendStatus(500);
|
||||||
console.log(`server on port:${PORT}`);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 8000;
|
||||||
|
app.listen(PORT);
|
||||||
|
console.log(`server on port:${PORT}`);
|
||||||
|
|||||||
@@ -1,85 +1,110 @@
|
|||||||
const _ = require('lodash');
|
|
||||||
const auth = require('basic-auth');
|
|
||||||
const HomebrewModel = require('./homebrew.model.js').model;
|
const HomebrewModel = require('./homebrew.model.js').model;
|
||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
|
const Moment = require('moment');
|
||||||
|
//const render = require('vitreum/steps/render');
|
||||||
|
const templateFn = require('../client/template.js');
|
||||||
|
const zlib = require('zlib');
|
||||||
|
|
||||||
|
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
||||||
|
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password3';
|
||||||
|
|
||||||
const mw = {
|
const mw = {
|
||||||
adminOnly : (req, res, next)=>{
|
adminOnly : (req, res, next)=>{
|
||||||
if(req.query && req.query.admin_key == process.env.ADMIN_KEY) return next();
|
if(!req.get('authorization')){
|
||||||
|
return res
|
||||||
|
.set('WWW-Authenticate', 'Basic realm="Authorization Required"')
|
||||||
|
.status(401)
|
||||||
|
.send('Authorization Required');
|
||||||
|
}
|
||||||
|
const [username, password] = new Buffer(req.get('authorization').split(' ').pop(), 'base64')
|
||||||
|
.toString('ascii')
|
||||||
|
.split(':');
|
||||||
|
if(process.env.ADMIN_USER === username && process.env.ADMIN_PASS === password){
|
||||||
|
return next();
|
||||||
|
}
|
||||||
return res.status(401).send('Access denied');
|
return res.status(401).send('Access denied');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
|
||||||
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password';
|
|
||||||
process.env.ADMIN_KEY = process.env.ADMIN_KEY || 'admin_key';
|
|
||||||
|
|
||||||
|
/* Search for brews that are older than 3 days and that are shorter than a tweet */
|
||||||
|
const junkBrewQuery = HomebrewModel.find({
|
||||||
|
'$where' : 'this.text.length < 140',
|
||||||
|
createdAt : {
|
||||||
|
$lt : Moment().subtract(30, 'days').toDate()
|
||||||
|
}
|
||||||
|
}).limit(100).maxTime(60000);
|
||||||
|
|
||||||
|
/* Search for brews that aren't compressed (missing the compressed text field) */
|
||||||
|
const uncompressedBrewQuery = HomebrewModel.find({
|
||||||
|
'textBin' : null
|
||||||
|
}).lean().limit(10000).select('_id');
|
||||||
|
|
||||||
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
|
router.get('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||||
router.get('/api/invalid', mw.adminOnly, (req, res)=>{
|
junkBrewQuery.exec((err, objs)=>{
|
||||||
const invalidBrewQuery = HomebrewModel.find({
|
if(err) return res.status(500).send(err);
|
||||||
'$where' : "this.text.length < 140",
|
return res.json({ count: objs.length });
|
||||||
createdAt: {
|
});
|
||||||
$lt: Moment().subtract(3, 'days').toDate()
|
});
|
||||||
}
|
/* Removes all empty brews that are older than 3 days and that are shorter than a tweet */
|
||||||
|
router.post('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||||
|
junkBrewQuery.remove().exec((err, objs)=>{
|
||||||
|
if(err) return res.status(500).send(err);
|
||||||
|
return res.json({ count: objs.length });
|
||||||
});
|
});
|
||||||
|
|
||||||
if(req.query.do_it){
|
|
||||||
invalidBrewQuery.remove().exec((err, objs)=>{
|
|
||||||
refreshCount();
|
|
||||||
return res.send(200);
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
invalidBrewQuery.exec((err, objs)=>{
|
|
||||||
if(err) console.log(err);
|
|
||||||
return res.json({
|
|
||||||
count : objs.length
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next) => {
|
/* Searches for matching edit or share id, also attempts to partial match */
|
||||||
//search for mathcing edit id
|
router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next)=>{
|
||||||
//search for matching share id
|
HomebrewModel.findOne({ $or : [
|
||||||
// search for partial match
|
{ editId: { '$regex': req.params.id, '$options': 'i' } },
|
||||||
|
{ shareId: { '$regex': req.params.id, '$options': 'i' } },
|
||||||
HomebrewModel.findOne({ $or:[
|
] }).exec((err, brew)=>{
|
||||||
{editId : { "$regex": req.params.id, "$options": "i" }},
|
return res.json(brew);
|
||||||
{shareId : { "$regex": req.params.id, "$options": "i" }},
|
});
|
||||||
]}).exec((err, brew) => {
|
|
||||||
return res.json(brew);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Find 50 brews that aren't compressed yet */
|
||||||
|
router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
|
||||||
|
uncompressedBrewQuery.exec((err, objs)=>{
|
||||||
|
if(err) return res.status(500).send(err);
|
||||||
|
objs = objs.map((obj)=>{return obj._id;});
|
||||||
|
return res.json({ count: objs.length, ids: objs });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Compresses the "text" field of a brew to binary */
|
||||||
|
router.put('/admin/compress/:id', (req, res)=>{
|
||||||
|
HomebrewModel.get({ _id: req.params.id })
|
||||||
|
.then((brew)=>{
|
||||||
|
brew.textBin = zlib.deflateRawSync(brew.text); // Compress brew text to binary before saving
|
||||||
|
brew.text = undefined; // Delete the non-binary text field since it's not needed anymore
|
||||||
|
|
||||||
//Admin route
|
brew.save((err, obj)=>{
|
||||||
|
if(err) throw err;
|
||||||
const render = require('vitreum/steps/render');
|
return res.status(200).send(obj);
|
||||||
const templateFn = require('../client/template.js');
|
});
|
||||||
router.get('/admin', function(req, res){
|
|
||||||
const credentials = auth(req)
|
|
||||||
if (!credentials || credentials.name !== process.env.ADMIN_USER || credentials.pass !== process.env.ADMIN_PASS) {
|
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="example"')
|
|
||||||
return res.status(401).send('Access denied')
|
|
||||||
}
|
|
||||||
render('admin', templateFn, {
|
|
||||||
url: req.originalUrl,
|
|
||||||
admin_key : process.env.ADMIN_KEY,
|
|
||||||
})
|
})
|
||||||
.then((page) => {
|
.catch((err)=>{
|
||||||
return res.send(page)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
console.log(err);
|
||||||
return res.sendStatus(500);
|
return res.status(500).send('Error while saving');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/admin/stats', mw.adminOnly, (req, res)=>{
|
||||||
|
HomebrewModel.count({}, (err, count)=>{
|
||||||
|
return res.json({
|
||||||
|
totalBrews : count
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/admin', mw.adminOnly, (req, res)=>{
|
||||||
|
templateFn('admin', {
|
||||||
|
url : req.originalUrl
|
||||||
|
})
|
||||||
|
.then((page)=>res.send(page))
|
||||||
|
.catch((err)=>res.sendStatus(500));
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
module.exports = router;
|
|
||||||
|
|||||||
7
server/forcessl.mw.js
Normal file
7
server/forcessl.mw.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = (req, res, next)=>{
|
||||||
|
if(process.env.NODE_ENV === 'local' || process.env.NODE_ENV === 'docker') return next();
|
||||||
|
if(req.header('x-forwarded-proto') !== 'https') {
|
||||||
|
return res.redirect(302, `https://${req.get('Host')}${req.url}`);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
};
|
||||||
@@ -1,144 +1,151 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const Moment = require('moment');
|
const HomebrewModel = require('./homebrew.model.js').model;
|
||||||
const HomebrewModel = require('./homebrew.model.js').model;
|
const router = require('express').Router();
|
||||||
const router = require('express').Router();
|
const zlib = require('zlib');
|
||||||
|
|
||||||
|
// const getTopBrews = (cb) => {
|
||||||
|
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
|
||||||
//TODO: Possiblity remove
|
// cb(brews);
|
||||||
let homebrewTotal = 0;
|
// });
|
||||||
const refreshCount = ()=>{
|
// };
|
||||||
HomebrewModel.count({}, (err, total)=>{
|
|
||||||
homebrewTotal = total;
|
const getGoodBrewTitle = (text)=>{
|
||||||
});
|
const titlePos = text.indexOf('# ');
|
||||||
};
|
if(titlePos !== -1) {
|
||||||
refreshCount();
|
const ending = text.indexOf('\n', titlePos);
|
||||||
|
return text.substring(titlePos + 2, ending);
|
||||||
|
} else {
|
||||||
|
return _.find(text.split('\n'), (line)=>line);
|
||||||
const getTopBrews = (cb)=>{
|
}
|
||||||
HomebrewModel.find().sort({views: -1}).limit(5).exec(function(err, brews) {
|
};
|
||||||
cb(brews);
|
|
||||||
});
|
const newBrew = (req, res)=>{
|
||||||
}
|
const authors = (req.account) ? [req.account.username] : [];
|
||||||
|
|
||||||
const getGoodBrewTitle = (text) => {
|
const newHomebrew = new HomebrewModel(_.merge({},
|
||||||
const titlePos = text.indexOf('# ');
|
req.body,
|
||||||
if(titlePos !== -1){
|
{ authors: authors }
|
||||||
const ending = text.indexOf('\n', titlePos);
|
));
|
||||||
return text.substring(titlePos + 2, ending);
|
|
||||||
}else{
|
if(!newHomebrew.title) {
|
||||||
return _.find(text.split('\n'), (line)=>{
|
newHomebrew.title = getGoodBrewTitle(newHomebrew.text);
|
||||||
return line;
|
}
|
||||||
});
|
|
||||||
}
|
// Compress brew text to binary before saving
|
||||||
};
|
newHomebrew.textBin = zlib.deflateRawSync(newHomebrew.text);
|
||||||
|
// Delete the non-binary text field since it's not needed anymore
|
||||||
|
newHomebrew.text = undefined;
|
||||||
|
|
||||||
router.post('/api', (req, res)=>{
|
newHomebrew.save((err, obj)=>{
|
||||||
|
if(err) {
|
||||||
let authors = [];
|
console.error(err, err.toString(), err.stack);
|
||||||
if(req.account) authors = [req.account.username];
|
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
|
||||||
|
}
|
||||||
const newHomebrew = new HomebrewModel(_.merge({},
|
return res.json(obj);
|
||||||
req.body,
|
});
|
||||||
{authors : authors}
|
};
|
||||||
));
|
|
||||||
if(!newHomebrew.title){
|
const updateBrew = (req, res)=>{
|
||||||
newHomebrew.title = getGoodBrewTitle(newHomebrew.text);
|
HomebrewModel.get({ editId: req.params.id })
|
||||||
}
|
.then((brew)=>{
|
||||||
newHomebrew.save((err, obj)=>{
|
brew = _.merge(brew, req.body);
|
||||||
if(err){
|
// Compress brew text to binary before saving
|
||||||
console.error(err, err.toString(), err.stack);
|
brew.textBin = zlib.deflateRawSync(req.body.text);
|
||||||
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
|
// Delete the non-binary text field since it's not needed anymore
|
||||||
}
|
brew.text = undefined;
|
||||||
return res.json(obj);
|
brew.updatedAt = new Date();
|
||||||
})
|
|
||||||
});
|
if(req.account) {
|
||||||
|
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
||||||
router.put('/api/update/:id', (req, res)=>{
|
}
|
||||||
HomebrewModel.get({editId : req.params.id})
|
|
||||||
.then((brew)=>{
|
brew.markModified('authors');
|
||||||
brew = _.merge(brew, req.body);
|
brew.markModified('systems');
|
||||||
brew.updatedAt = new Date();
|
|
||||||
if(req.account) brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
brew.save((err, obj)=>{
|
||||||
|
if(err) throw err;
|
||||||
brew.markModified('authors');
|
return res.status(200).send(obj);
|
||||||
brew.markModified('systems');
|
});
|
||||||
|
})
|
||||||
brew.save((err, obj)=>{
|
.catch((err)=>{
|
||||||
if(err) throw err;
|
console.error(err);
|
||||||
return res.status(200).send(obj);
|
return res.status(500).send('Error while saving');
|
||||||
})
|
});
|
||||||
})
|
};
|
||||||
.catch((err)=>{
|
|
||||||
console.log(err);
|
const deleteBrew = (req, res)=>{
|
||||||
return res.status(500).send("Error while saving");
|
HomebrewModel.find({ editId: req.params.id }, (err, objs)=>{
|
||||||
});
|
if(!objs.length || err) {
|
||||||
});
|
return res.status(404).send('Can not find homebrew with that id');
|
||||||
|
}
|
||||||
router.get('/api/remove/:id', (req, res)=>{
|
|
||||||
HomebrewModel.find({editId : req.params.id}, (err, objs)=>{
|
const brew = objs[0];
|
||||||
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
|
|
||||||
var resEntry = objs[0];
|
if(req.account) {
|
||||||
resEntry.remove((err)=>{
|
// Remove current user as author
|
||||||
if(err) return res.status(500).send("Error while removing");
|
brew.authors = _.pull(brew.authors, req.account.username);
|
||||||
return res.status(200).send();
|
brew.markModified('authors');
|
||||||
})
|
}
|
||||||
});
|
|
||||||
});
|
if(brew.authors.length === 0) {
|
||||||
|
// Delete brew if there are no authors left
|
||||||
|
brew.remove((err)=>{
|
||||||
module.exports = router;
|
if(err) return res.status(500).send('Error while removing');
|
||||||
|
return res.status(200).send();
|
||||||
/*
|
});
|
||||||
|
} else {
|
||||||
|
// Otherwise, save the brew with updated author list
|
||||||
|
brew.save((err, savedBrew)=>{
|
||||||
module.exports = function(app){
|
if(err) throw err;
|
||||||
|
return res.status(200).send(savedBrew);
|
||||||
app;
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
app.get('/api/search', mw.adminOnly, function(req, res){
|
router.post('/api', newBrew);
|
||||||
|
router.put('/api/:id', updateBrew);
|
||||||
var page = req.query.page || 0;
|
router.put('/api/update/:id', updateBrew);
|
||||||
var count = req.query.count || 20;
|
router.delete('/api/:id', deleteBrew);
|
||||||
|
router.get('/api/remove/:id', deleteBrew);
|
||||||
var query = {};
|
|
||||||
if(req.query && req.query.id){
|
module.exports = router;
|
||||||
query = {
|
|
||||||
"$or" : [{
|
/*
|
||||||
editId : req.query.id
|
module.exports = function(app) {
|
||||||
},{
|
app;
|
||||||
shareId : req.query.id
|
|
||||||
}]
|
app.get('/api/search', mw.adminOnly, function(req, res) {
|
||||||
};
|
var page = req.query.page || 0;
|
||||||
}
|
var count = req.query.count || 20;
|
||||||
|
|
||||||
HomebrewModel.find(query, {
|
var query = {};
|
||||||
text : 0 //omit the text
|
if (req.query && req.query.id) {
|
||||||
}, {
|
query = {
|
||||||
skip: page*count,
|
"$or": [{
|
||||||
limit: count*1
|
editId : req.query.id
|
||||||
}, function(err, objs){
|
}, {
|
||||||
if(err) console.log(err);
|
shareId : req.query.id
|
||||||
return res.json({
|
}]
|
||||||
page : page,
|
};
|
||||||
count : count,
|
}
|
||||||
total : homebrewTotal,
|
|
||||||
brews : objs
|
HomebrewModel.find(query, {
|
||||||
});
|
text : 0 //omit the text
|
||||||
|
}, {
|
||||||
});
|
skip: page*count,
|
||||||
})
|
limit: count*1
|
||||||
|
}, function(err, objs) {
|
||||||
|
if (err) console.error(err);
|
||||||
|
return res.json({
|
||||||
|
page : page,
|
||||||
return app;
|
count : count,
|
||||||
}
|
total : homebrewTotal,
|
||||||
*/
|
brews : objs
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,81 +1,87 @@
|
|||||||
var mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
var shortid = require('shortid');
|
const shortid = require('shortid');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const zlib = require('zlib');
|
||||||
var HomebrewSchema = mongoose.Schema({
|
|
||||||
shareId : {type : String, default: shortid.generate, index: { unique: true }},
|
const HomebrewSchema = mongoose.Schema({
|
||||||
editId : {type : String, default: shortid.generate, index: { unique: true }},
|
shareId : { type: String, default: shortid.generate, index: { unique: true } },
|
||||||
title : {type : String, default : ""},
|
editId : { type: String, default: shortid.generate, index: { unique: true } },
|
||||||
text : {type : String, default : ""},
|
title : { type: String, default: '' },
|
||||||
|
text : { type: String, default: '' },
|
||||||
description : {type : String, default : ""},
|
textBin : { type: Buffer },
|
||||||
tags : {type : String, default : ""},
|
|
||||||
systems : [String],
|
description : { type: String, default: '' },
|
||||||
authors : [String],
|
tags : { type: String, default: '' },
|
||||||
published : {type : Boolean, default : false},
|
systems : [String],
|
||||||
|
authors : [String],
|
||||||
createdAt : { type: Date, default: Date.now },
|
published : { type: Boolean, default: false },
|
||||||
updatedAt : { type: Date, default: Date.now},
|
|
||||||
lastViewed : { type: Date, default: Date.now},
|
createdAt : { type: Date, default: Date.now },
|
||||||
views : {type:Number, default:0},
|
updatedAt : { type: Date, default: Date.now },
|
||||||
version : {type: Number, default:1}
|
lastViewed : { type: Date, default: Date.now },
|
||||||
}, { versionKey: false });
|
views : { type: Number, default: 0 },
|
||||||
|
version : { type: Number, default: 1 }
|
||||||
|
}, { versionKey: false });
|
||||||
|
|
||||||
HomebrewSchema.methods.sanatize = function(full=false){
|
|
||||||
const brew = this.toJSON();
|
|
||||||
delete brew._id;
|
HomebrewSchema.methods.sanatize = function(full=false){
|
||||||
delete brew.__v;
|
const brew = this.toJSON();
|
||||||
if(full){
|
delete brew._id;
|
||||||
delete brew.editId;
|
delete brew.__v;
|
||||||
}
|
if(full){
|
||||||
return brew;
|
delete brew.editId;
|
||||||
};
|
}
|
||||||
|
return brew;
|
||||||
|
};
|
||||||
HomebrewSchema.methods.increaseView = function(){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.lastViewed = new Date();
|
HomebrewSchema.methods.increaseView = function(){
|
||||||
this.views = this.views + 1;
|
return new Promise((resolve, reject)=>{
|
||||||
this.save((err) => {
|
this.lastViewed = new Date();
|
||||||
if(err) return reject(err);
|
this.views = this.views + 1;
|
||||||
return resolve(this);
|
this.save((err)=>{
|
||||||
});
|
if(err) return reject(err);
|
||||||
});
|
return resolve(this);
|
||||||
};
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
HomebrewSchema.statics.get = function(query){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
Homebrew.find(query, (err, brews)=>{
|
HomebrewSchema.statics.get = function(query){
|
||||||
if(err || !brews.length) return reject('Can not find brew');
|
return new Promise((resolve, reject)=>{
|
||||||
return resolve(brews[0]);
|
Homebrew.find(query, (err, brews)=>{
|
||||||
});
|
if(err || !brews.length) return reject('Can not find brew');
|
||||||
});
|
if(!_.isNil(brews[0].textBin)) { // Uncompress zipped text field
|
||||||
};
|
unzipped = zlib.inflateRawSync(brews[0].textBin);
|
||||||
|
brews[0].text = unzipped.toString();
|
||||||
HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return resolve(brews[0]);
|
||||||
let query = {authors : username, published : true};
|
});
|
||||||
if(allowAccess){
|
});
|
||||||
delete query.published;
|
};
|
||||||
}
|
|
||||||
Homebrew.find(query, (err, brews)=>{
|
HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
|
||||||
if(err) return reject('Can not find brew');
|
return new Promise((resolve, reject)=>{
|
||||||
return resolve(_.map(brews, (brew)=>{
|
const query = { authors: username, published: true };
|
||||||
return brew.sanatize(!allowAccess);
|
if(allowAccess){
|
||||||
}));
|
delete query.published;
|
||||||
});
|
}
|
||||||
});
|
Homebrew.find(query, (err, brews)=>{
|
||||||
};
|
if(err) return reject('Can not find brew');
|
||||||
|
return resolve(_.map(brews, (brew)=>{
|
||||||
|
return brew.sanatize(!allowAccess);
|
||||||
|
}));
|
||||||
var Homebrew = mongoose.model('Homebrew', HomebrewSchema);
|
});
|
||||||
|
});
|
||||||
module.exports = {
|
};
|
||||||
schema : HomebrewSchema,
|
|
||||||
model : Homebrew,
|
|
||||||
}
|
|
||||||
|
const Homebrew = mongoose.model('Homebrew', HomebrewSchema);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
schema : HomebrewSchema,
|
||||||
|
model : Homebrew,
|
||||||
|
};
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
|
require('./renderWarnings.less');
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
const RenderWarnings = React.createClass({
|
const DISMISS_KEY = 'dismiss_render_warning';
|
||||||
getInitialState: function() {
|
|
||||||
|
const RenderWarnings = createClass({
|
||||||
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
warnings: {}
|
warnings : {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
componentDidMount : function() {
|
||||||
this.checkWarnings();
|
this.checkWarnings();
|
||||||
window.addEventListener('resize', this.checkWarnings);
|
window.addEventListener('resize', this.checkWarnings);
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount : function() {
|
||||||
window.removeEventListener('resize', this.checkWarnings);
|
window.removeEventListener('resize', this.checkWarnings);
|
||||||
},
|
},
|
||||||
warnings : {
|
warnings : {
|
||||||
@@ -22,41 +25,40 @@ const RenderWarnings = React.createClass({
|
|||||||
if(!isChrome){
|
if(!isChrome){
|
||||||
return <li key='chrome'>
|
return <li key='chrome'>
|
||||||
<em>Built for Chrome </em> <br />
|
<em>Built for Chrome </em> <br />
|
||||||
Other browsers do not support
|
Other browsers have not been tested for compatiblilty. If you
|
||||||
<a target='_blank' href='https://developer.mozilla.org/en-US/docs/Web/CSS/column-span#Browser_compatibility'>
|
experience issues with your document not rendering or printing
|
||||||
key features
|
properly, please try using the latest version of Chrome before
|
||||||
</a> this site uses.
|
submitting a bug report.
|
||||||
</li>;
|
</li>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
zoom : function(){
|
|
||||||
return false;
|
|
||||||
if(window.devicePixelRatio !== 1){
|
|
||||||
return <li key='zoom'>
|
|
||||||
<em>Your browser is zoomed. </em> <br />
|
|
||||||
This can cause content to jump columns.
|
|
||||||
</li>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
checkWarnings : function(){
|
checkWarnings : function(){
|
||||||
|
const hideDismiss = localStorage.getItem(DISMISS_KEY);
|
||||||
|
if(hideDismiss) return this.setState({ warnings: {} });
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
warnings : _.reduce(this.warnings, (r, fn, type) => {
|
warnings : _.reduce(this.warnings, (r, fn, type)=>{
|
||||||
const element = fn();
|
const element = fn();
|
||||||
if(element) r[type] = element;
|
if(element) r[type] = element;
|
||||||
return r;
|
return r;
|
||||||
}, {})
|
}, {})
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
render: function(){
|
dismiss : function(){
|
||||||
|
localStorage.setItem(DISMISS_KEY, true);
|
||||||
|
this.checkWarnings();
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
if(_.isEmpty(this.state.warnings)) return null;
|
if(_.isEmpty(this.state.warnings)) return null;
|
||||||
|
|
||||||
return <div className='renderWarnings'>
|
return <div className='renderWarnings'>
|
||||||
<i className='fa fa-exclamation-triangle' />
|
<i className='fa fa-times dismiss' onClick={this.dismiss}/>
|
||||||
|
<i className='fa fa-exclamation-triangle ohno' />
|
||||||
<h3>Render Warnings</h3>
|
<h3>Render Warnings</h3>
|
||||||
<small>If this homebrew is rendering badly if might be because of the following:</small>
|
<small>If this homebrew is rendering badly if might be because of the following:</small>
|
||||||
<ul>{_.values(this.state.warnings)}</ul>
|
<ul>{_.values(this.state.warnings)}</ul>
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,33 @@
|
|||||||
|
|
||||||
.renderWarnings{
|
.renderWarnings{
|
||||||
position : fixed;
|
position : relative;
|
||||||
|
float : right;
|
||||||
display : inline-block;
|
display : inline-block;
|
||||||
top : @navbarHeight;
|
|
||||||
right : 15px;
|
|
||||||
z-index : 10001;
|
|
||||||
width : 350px;
|
width : 350px;
|
||||||
padding : 20px;
|
padding : 20px;
|
||||||
padding-bottom : 10px;
|
padding-bottom : 10px;
|
||||||
padding-left : 85px;
|
padding-left : 85px;
|
||||||
|
margin-bottom : 10px;
|
||||||
background-color : @yellow;
|
background-color : @yellow;
|
||||||
color : white;
|
color : white;
|
||||||
i{
|
a{
|
||||||
position: absolute;
|
font-weight : 800;
|
||||||
left: 24px;
|
}
|
||||||
opacity: 0.8;
|
i.ohno{
|
||||||
font-size: 2.5em;
|
position : absolute;
|
||||||
top: 24px;
|
top : 24px;
|
||||||
|
left : 24px;
|
||||||
|
opacity : 0.8;
|
||||||
|
font-size : 2.5em;
|
||||||
|
}
|
||||||
|
i.dismiss{
|
||||||
|
position : absolute;
|
||||||
|
top : 10px;
|
||||||
|
right : 10px;
|
||||||
|
cursor : pointer;
|
||||||
|
opacity : 0.6;
|
||||||
|
&:hover{
|
||||||
|
opacity : 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
small{
|
small{
|
||||||
opacity : 0.7;
|
opacity : 0.7;
|
||||||
@@ -32,10 +43,10 @@
|
|||||||
list-style-position : outside;
|
list-style-position : outside;
|
||||||
list-style-type : disc;
|
list-style-type : disc;
|
||||||
li{
|
li{
|
||||||
|
font-size : 0.8em;
|
||||||
line-height : 1.6em;
|
line-height : 1.6em;
|
||||||
font-size: 0.8em;
|
|
||||||
em{
|
em{
|
||||||
font-weight: 800;
|
font-weight : 800;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,89 @@
|
|||||||
var React = require('react');
|
require('./codeEditor.less');
|
||||||
var _ = require('lodash');
|
const React = require('react');
|
||||||
var cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
var CodeMirror;
|
|
||||||
if(typeof navigator !== 'undefined'){
|
|
||||||
var CodeMirror = require('codemirror');
|
let CodeMirror;
|
||||||
|
if(typeof navigator !== 'undefined'){
|
||||||
//Language Modes
|
CodeMirror = require('codemirror');
|
||||||
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
|
|
||||||
require('codemirror/mode/javascript/javascript.js');
|
//Language Modes
|
||||||
}
|
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
|
||||||
|
require('codemirror/mode/javascript/javascript.js');
|
||||||
|
}
|
||||||
var CodeEditor = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
const CodeEditor = createClass({
|
||||||
language : '',
|
getDefaultProps : function() {
|
||||||
value : '',
|
return {
|
||||||
wrap : false,
|
language : '',
|
||||||
onChange : function(){},
|
value : '',
|
||||||
onCursorActivity : function(){},
|
wrap : false,
|
||||||
};
|
onChange : function(){},
|
||||||
},
|
onCursorActivity : function(){},
|
||||||
|
};
|
||||||
componentDidMount: function() {
|
},
|
||||||
this.codeMirror = CodeMirror(this.refs.editor,{
|
|
||||||
value : this.props.value,
|
componentDidMount : function() {
|
||||||
lineNumbers: true,
|
this.codeMirror = CodeMirror(this.refs.editor, {
|
||||||
lineWrapping : this.props.wrap,
|
value : this.props.value,
|
||||||
mode : this.props.language
|
lineNumbers : true,
|
||||||
});
|
lineWrapping : this.props.wrap,
|
||||||
|
mode : this.props.language,
|
||||||
this.codeMirror.on('change', this.handleChange);
|
extraKeys : {
|
||||||
this.codeMirror.on('cursorActivity', this.handleCursorActivity);
|
'Ctrl-B' : this.makeBold,
|
||||||
this.updateSize();
|
'Ctrl-I' : this.makeItalic
|
||||||
},
|
}
|
||||||
|
});
|
||||||
componentWillReceiveProps: function(nextProps){
|
|
||||||
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
|
this.codeMirror.on('change', this.handleChange);
|
||||||
this.codeMirror.setValue(nextProps.value);
|
this.codeMirror.on('cursorActivity', this.handleCursorActivity);
|
||||||
}
|
this.updateSize();
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldComponentUpdate: function(nextProps, nextState) {
|
makeBold : function() {
|
||||||
return false;
|
const selection = this.codeMirror.getSelection();
|
||||||
},
|
this.codeMirror.replaceSelection(`**${selection}**`, 'around');
|
||||||
|
},
|
||||||
setCursorPosition : function(line, char){
|
|
||||||
setTimeout(()=>{
|
makeItalic : function() {
|
||||||
this.codeMirror.focus();
|
const selection = this.codeMirror.getSelection();
|
||||||
this.codeMirror.doc.setCursor(line, char);
|
this.codeMirror.replaceSelection(`*${selection}*`, 'around');
|
||||||
}, 10);
|
},
|
||||||
},
|
|
||||||
|
componentWillReceiveProps : function(nextProps){
|
||||||
updateSize : function(){
|
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
|
||||||
this.codeMirror.refresh();
|
this.codeMirror.setValue(nextProps.value);
|
||||||
},
|
}
|
||||||
|
},
|
||||||
handleChange : function(editor){
|
|
||||||
this.props.onChange(editor.getValue());
|
shouldComponentUpdate : function(nextProps, nextState) {
|
||||||
},
|
return false;
|
||||||
handleCursorActivity : function(){
|
},
|
||||||
this.props.onCursorActivity(this.codeMirror.doc.getCursor());
|
|
||||||
},
|
setCursorPosition : function(line, char){
|
||||||
|
setTimeout(()=>{
|
||||||
render : function(){
|
this.codeMirror.focus();
|
||||||
return <div className='codeEditor' ref='editor' />
|
this.codeMirror.doc.setCursor(line, char);
|
||||||
}
|
}, 10);
|
||||||
});
|
},
|
||||||
|
|
||||||
module.exports = CodeEditor;
|
updateSize : function(){
|
||||||
|
this.codeMirror.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChange : function(editor){
|
||||||
|
this.props.onChange(editor.getValue());
|
||||||
|
},
|
||||||
|
handleCursorActivity : function(){
|
||||||
|
this.props.onCursorActivity(this.codeMirror.doc.getCursor());
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='codeEditor' ref='editor' />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = CodeEditor;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var Markdown = require('marked');
|
const Markdown = require('marked');
|
||||||
var renderer = new Markdown.Renderer();
|
const renderer = new Markdown.Renderer();
|
||||||
|
|
||||||
//Processes the markdown within an HTML block if it's just a class-wrapper
|
//Processes the markdown within an HTML block if it's just a class-wrapper
|
||||||
renderer.html = function (html) {
|
renderer.html = function (html) {
|
||||||
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
|
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
|
||||||
var openTag = html.substring(0, html.indexOf('>')+1);
|
const openTag = html.substring(0, html.indexOf('>')+1);
|
||||||
html = html.substring(html.indexOf('>')+1);
|
html = html.substring(html.indexOf('>')+1);
|
||||||
html = html.substring(0, html.lastIndexOf('</div>'));
|
html = html.substring(0, html.lastIndexOf('</div>'));
|
||||||
return `${openTag} ${Markdown(html)} </div>`;
|
return `${openTag} ${Markdown(html)} </div>`;
|
||||||
@@ -13,25 +13,33 @@ renderer.html = function (html) {
|
|||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sanatizeScriptTags = (content)=>{
|
||||||
|
return content
|
||||||
|
.replace(/<script/ig, '<script')
|
||||||
|
.replace(/<\/script>/ig, '</script>');
|
||||||
|
};
|
||||||
|
|
||||||
const tagTypes = ['div', 'span', 'a'];
|
const tagTypes = ['div', 'span', 'a'];
|
||||||
const tagRegex = new RegExp('(' +
|
const tagRegex = new RegExp(`(${
|
||||||
_.map(tagTypes, (type)=>{
|
_.map(tagTypes, (type)=>{
|
||||||
return `\\<${type}|\\</${type}>`;
|
return `\\<${type}|\\</${type}>`;
|
||||||
}).join('|') + ')', 'g');
|
}).join('|')})`, 'g');
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
marked : Markdown,
|
marked : Markdown,
|
||||||
render : (rawBrewText)=>{
|
render : (rawBrewText)=>{
|
||||||
return Markdown(rawBrewText, {renderer : renderer})
|
return Markdown(
|
||||||
|
sanatizeScriptTags(rawBrewText),
|
||||||
|
{ renderer: renderer }
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
validate : (rawBrewText) => {
|
validate : (rawBrewText)=>{
|
||||||
var errors = [];
|
const errors = [];
|
||||||
var leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber) => {
|
const leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber)=>{
|
||||||
var lineNumber = _lineNumber + 1;
|
const lineNumber = _lineNumber + 1;
|
||||||
var matches = line.match(tagRegex);
|
const matches = line.match(tagRegex);
|
||||||
if(!matches || !matches.length) return acc;
|
if(!matches || !matches.length) return acc;
|
||||||
|
|
||||||
_.each(matches, (match)=>{
|
_.each(matches, (match)=>{
|
||||||
@@ -48,16 +56,16 @@ module.exports = {
|
|||||||
line : lineNumber,
|
line : lineNumber,
|
||||||
type : type,
|
type : type,
|
||||||
text : 'Unmatched closing tag',
|
text : 'Unmatched closing tag',
|
||||||
id : 'CLOSE'
|
id : 'CLOSE'
|
||||||
});
|
});
|
||||||
}else if(_.last(acc).type == type){
|
} else if(_.last(acc).type == type){
|
||||||
acc.pop();
|
acc.pop();
|
||||||
}else{
|
} else {
|
||||||
errors.push({
|
errors.push({
|
||||||
line : _.last(acc).line + ' to ' + lineNumber,
|
line : `${_.last(acc).line} to ${lineNumber}`,
|
||||||
type : type,
|
type : type,
|
||||||
text : 'Type mismatch on closing tag',
|
text : 'Type mismatch on closing tag',
|
||||||
id : 'MISMATCH'
|
id : 'MISMATCH'
|
||||||
});
|
});
|
||||||
acc.pop();
|
acc.pop();
|
||||||
}
|
}
|
||||||
@@ -71,9 +79,9 @@ module.exports = {
|
|||||||
errors.push({
|
errors.push({
|
||||||
line : unmatched.line,
|
line : unmatched.line,
|
||||||
type : unmatched.type,
|
type : unmatched.type,
|
||||||
text : "Unmatched opening tag",
|
text : 'Unmatched opening tag',
|
||||||
id : 'OPEN'
|
id : 'OPEN'
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
|||||||
@@ -1,72 +1,74 @@
|
|||||||
var React = require('react');
|
require('./nav.less');
|
||||||
var _ = require('lodash');
|
const React = require('react');
|
||||||
var cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
var NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
const cx = require('classnames');
|
||||||
|
|
||||||
var Nav = {
|
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
||||||
base : React.createClass({
|
|
||||||
render : function(){
|
const Nav = {
|
||||||
return <nav>
|
base : createClass({
|
||||||
<div className='navContent'>
|
render : function(){
|
||||||
{this.props.children}
|
return <nav>
|
||||||
</div>
|
<div className='navContent'>
|
||||||
</nav>
|
{this.props.children}
|
||||||
}
|
</div>
|
||||||
}),
|
</nav>;
|
||||||
logo : function(){
|
}
|
||||||
return <a className='navLogo' href="http://naturalcrit.com">
|
}),
|
||||||
<NaturalCritIcon />
|
logo : function(){
|
||||||
<span className='name'>
|
return <a className='navLogo' href='http://naturalcrit.com'>
|
||||||
Natural<span className='crit'>Crit</span>
|
<NaturalCritIcon />
|
||||||
</span>
|
<span className='name'>
|
||||||
</a>;
|
Natural<span className='crit'>Crit</span>
|
||||||
},
|
</span>
|
||||||
|
</a>;
|
||||||
section : React.createClass({
|
},
|
||||||
render : function(){
|
|
||||||
return <div className='navSection'>
|
section : createClass({
|
||||||
{this.props.children}
|
render : function(){
|
||||||
</div>
|
return <div className='navSection'>
|
||||||
}
|
{this.props.children}
|
||||||
}),
|
</div>;
|
||||||
|
}
|
||||||
item : React.createClass({
|
}),
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
item : createClass({
|
||||||
icon : null,
|
getDefaultProps : function() {
|
||||||
href : null,
|
return {
|
||||||
newTab : false,
|
icon : null,
|
||||||
onClick : function(){},
|
href : null,
|
||||||
color : null
|
newTab : false,
|
||||||
};
|
onClick : function(){},
|
||||||
},
|
color : null
|
||||||
handleClick : function(){
|
};
|
||||||
this.props.onClick();
|
},
|
||||||
},
|
handleClick : function(){
|
||||||
render : function(){
|
this.props.onClick();
|
||||||
var classes = cx('navItem', this.props.color, this.props.className);
|
},
|
||||||
|
render : function(){
|
||||||
var icon;
|
const classes = cx('navItem', this.props.color, this.props.className);
|
||||||
if(this.props.icon) icon = <i className={'fa ' + this.props.icon} />;
|
|
||||||
|
let icon;
|
||||||
const props = _.omit(this.props, ['newTab']);
|
if(this.props.icon) icon = <i className={`fa ${this.props.icon}`} />;
|
||||||
|
|
||||||
if(this.props.href){
|
const props = _.omit(this.props, ['newTab']);
|
||||||
return <a {...props} className={classes} target={this.props.newTab ? '_blank' : '_self'} >
|
|
||||||
{this.props.children}
|
if(this.props.href){
|
||||||
{icon}
|
return <a {...props} className={classes} target={this.props.newTab ? '_blank' : '_self'} >
|
||||||
</a>
|
{this.props.children}
|
||||||
}else{
|
{icon}
|
||||||
return <div {...props} className={classes} onClick={this.handleClick} >
|
</a>;
|
||||||
{this.props.children}
|
} else {
|
||||||
{icon}
|
return <div {...props} className={classes} onClick={this.handleClick} >
|
||||||
</div>
|
{this.props.children}
|
||||||
}
|
{icon}
|
||||||
}
|
</div>;
|
||||||
}),
|
}
|
||||||
|
}
|
||||||
};
|
}),
|
||||||
|
|
||||||
|
};
|
||||||
module.exports = Nav;
|
|
||||||
|
|
||||||
|
module.exports = Nav;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//@import (less) 'naturalcrit/styles/style.fonts.css';
|
||||||
nav{
|
nav{
|
||||||
background-color : #333;
|
background-color : #333;
|
||||||
.navContent{
|
.navContent{
|
||||||
@@ -77,4 +78,4 @@ nav{
|
|||||||
.navSection:last-child .navItem{
|
.navSection:last-child .navItem{
|
||||||
border-left : 1px solid #666;
|
border-left : 1px solid #666;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +1,101 @@
|
|||||||
var React = require('react');
|
require('./splitPane.less');
|
||||||
var _ = require('lodash');
|
const React = require('react');
|
||||||
var cx = require('classnames');
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
var SplitPane = React.createClass({
|
const cx = require('classnames');
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
const SplitPane = createClass({
|
||||||
storageKey : 'naturalcrit-pane-split',
|
getDefaultProps : function() {
|
||||||
onDragFinish : function(){} //fires when dragging
|
return {
|
||||||
|
storageKey : 'naturalcrit-pane-split',
|
||||||
};
|
onDragFinish : function(){} //fires when dragging
|
||||||
},
|
|
||||||
getInitialState: function() {
|
};
|
||||||
return {
|
},
|
||||||
size : null,
|
getInitialState : function() {
|
||||||
isDragging : false
|
return {
|
||||||
};
|
size : null,
|
||||||
},
|
isDragging : false
|
||||||
componentDidMount: function() {
|
};
|
||||||
var paneSize = window.localStorage.getItem(this.props.storageKey);
|
},
|
||||||
if(paneSize){
|
componentDidMount : function() {
|
||||||
this.setState({
|
const paneSize = window.localStorage.getItem(this.props.storageKey);
|
||||||
size : paneSize
|
if(paneSize){
|
||||||
})
|
this.setState({
|
||||||
}
|
size : paneSize
|
||||||
},
|
});
|
||||||
|
}
|
||||||
handleUp : function(){
|
},
|
||||||
if(this.state.isDragging){
|
|
||||||
this.props.onDragFinish(this.state.size);
|
handleUp : function(){
|
||||||
window.localStorage.setItem(this.props.storageKey, this.state.size);
|
if(this.state.isDragging){
|
||||||
}
|
this.props.onDragFinish(this.state.size);
|
||||||
this.setState({ isDragging : false });
|
window.localStorage.setItem(this.props.storageKey, this.state.size);
|
||||||
},
|
}
|
||||||
handleDown : function(){
|
this.setState({ isDragging: false });
|
||||||
this.setState({ isDragging : true });
|
},
|
||||||
//this.unFocus()
|
handleDown : function(){
|
||||||
},
|
this.setState({ isDragging: true });
|
||||||
handleMove : function(e){
|
//this.unFocus()
|
||||||
if(!this.state.isDragging) return;
|
},
|
||||||
this.setState({
|
handleMove : function(e){
|
||||||
size : e.pageX
|
if(!this.state.isDragging) return;
|
||||||
});
|
this.setState({
|
||||||
},
|
size : e.pageX
|
||||||
/*
|
});
|
||||||
unFocus : function() {
|
},
|
||||||
if(document.selection){
|
/*
|
||||||
document.selection.empty();
|
unFocus : function() {
|
||||||
}else{
|
if(document.selection){
|
||||||
window.getSelection().removeAllRanges();
|
document.selection.empty();
|
||||||
}
|
}else{
|
||||||
},
|
window.getSelection().removeAllRanges();
|
||||||
*/
|
}
|
||||||
renderDivider : function(){
|
},
|
||||||
return <div className='divider' onMouseDown={this.handleDown}>
|
*/
|
||||||
<div className='dots'>
|
renderDivider : function(){
|
||||||
<i className='fa fa-circle' />
|
return <div className='divider' onMouseDown={this.handleDown}>
|
||||||
<i className='fa fa-circle' />
|
<div className='dots'>
|
||||||
<i className='fa fa-circle' />
|
<i className='fa fa-circle' />
|
||||||
</div>
|
<i className='fa fa-circle' />
|
||||||
</div>
|
<i className='fa fa-circle' />
|
||||||
},
|
</div>
|
||||||
|
</div>;
|
||||||
render : function(){
|
},
|
||||||
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
|
||||||
<Pane ref='pane1' width={this.state.size}>{this.props.children[0]}</Pane>
|
render : function(){
|
||||||
{this.renderDivider()}
|
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
||||||
<Pane ref='pane2'>{this.props.children[1]}</Pane>
|
<Pane ref='pane1' width={this.state.size}>{this.props.children[0]}</Pane>
|
||||||
</div>
|
{this.renderDivider()}
|
||||||
}
|
<Pane ref='pane2'>{this.props.children[1]}</Pane>
|
||||||
});
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var Pane = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
const Pane = createClass({
|
||||||
width : null
|
getDefaultProps : function() {
|
||||||
};
|
return {
|
||||||
},
|
width : null
|
||||||
render : function(){
|
};
|
||||||
var styles = {};
|
},
|
||||||
if(this.props.width){
|
render : function(){
|
||||||
styles = {
|
let styles = {};
|
||||||
flex : 'none',
|
if(this.props.width){
|
||||||
width : this.props.width + 'px'
|
styles = {
|
||||||
}
|
flex : 'none',
|
||||||
}
|
width : `${this.props.width}px`
|
||||||
return <div className={cx('pane', this.props.className)} style={styles}>
|
};
|
||||||
{this.props.children}
|
}
|
||||||
</div>
|
return <div className={cx('pane', this.props.className)} style={styles}>
|
||||||
}
|
{this.props.children}
|
||||||
});
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
module.exports = SplitPane;
|
|
||||||
|
|
||||||
|
module.exports = SplitPane;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
flex-direction : row;
|
flex-direction : row;
|
||||||
.pane{
|
.pane{
|
||||||
overflow-x : hidden;
|
overflow-x : hidden;
|
||||||
|
overflow-y : hidden;
|
||||||
flex : 1;
|
flex : 1;
|
||||||
}
|
}
|
||||||
.divider{
|
.divider{
|
||||||
@@ -28,4 +29,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
@import 'naturalcrit/styles/colors.less';
|
@import 'naturalcrit/styles/colors.less';
|
||||||
@import 'naturalcrit/styles/tooltip.less';
|
@import 'naturalcrit/styles/tooltip.less';
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family : CodeLight;
|
font-family : 'CodeLight';
|
||||||
src : url('/assets/naturalcrit/styles/CODE Light.otf');
|
src : data-uri('naturalcrit/styles/CODE Light.otf') format('opentype');
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family : CodeBold;
|
font-family : 'CodeBold';
|
||||||
src : url('/assets/naturalcrit/styles/CODE Bold.otf');
|
src : data-uri('naturalcrit/styles/CODE Bold.otf') format('opentype');
|
||||||
}
|
}
|
||||||
html,body, #reactRoot{
|
html,body, #reactRoot{
|
||||||
height : 100vh;
|
height : 100vh;
|
||||||
@@ -47,4 +47,4 @@ button{
|
|||||||
&:disabled{
|
&:disabled{
|
||||||
background-color : @silver !important;
|
background-color : @silver !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 80 100" enableBackground="new 0 0 80 80"><g><g><polygon fill="#000000" points="12.9,71.4 7.6,66.1 19.3,54.4 20.7,55.8 10.4,66.1 12.9,68.6 23.2,58.3 24.6,59.7 "/></g><g><path fill="#000000" d="M29,61.6c-1.7,0-3.4-0.7-4.6-1.9l-5.1-5.1c-2.5-2.5-2.5-6.6,0-9.2l0.7-0.7L34.3,59l-0.7,0.7 C32.4,60.9,30.8,61.6,29,61.6z M20.1,47.6c-1.1,1.7-0.9,4.1,0.6,5.6l5.1,5.1c0.8,0.8,2,1.3,3.2,1.3c0.9,0,1.7-0.2,2.4-0.7 L20.1,47.6z"/></g><g><path fill="#000000" d="M12.3,74.8c-0.8,0-1.5-0.3-2-0.8l-5.2-5.2c-0.5-0.5-0.8-1.2-0.8-2c0-0.8,0.3-1.5,0.8-2 c1.1-1.1,2.9-1.1,4,0l5.2,5.2c1.1,1.1,1.1,2.9,0,4C13.8,74.5,13.1,74.8,12.3,74.8z M7.1,65.9c-0.2,0-0.4,0.1-0.6,0.2 c-0.2,0.2-0.2,0.4-0.2,0.6s0.1,0.4,0.2,0.6l5.2,5.2c0.3,0.3,0.9,0.3,1.2,0c0.3-0.3,0.3-0.8,0-1.2l-5.2-5.2 C7.5,66,7.3,65.9,7.1,65.9z"/></g><g><polygon fill="#000000" points="31.7,58.7 30.3,57.3 70,17.6 70,9 62.4,9 23.3,49.4 21.9,48 61.6,7 72,7 72,18.4 "/></g><g><rect x="46" y="6.7" transform="matrix(0.7168 0.6973 -0.6973 0.7168 35.9716 -23.568)" fill="#000000" width="2" height="51.6"/></g><g><rect x="13" y="61" fill="#000000" width="2" height="7"/></g><g><rect x="17" y="57" fill="#000000" width="2" height="7"/></g></g><g><g><polygon fill="#000000" points="68.4,71.4 56.7,59.7 58.1,58.3 68.4,68.6 70.8,66.1 60.5,55.8 61.9,54.4 73.6,66.1 "/></g><g><path fill="#000000" d="M52.2,61.6c-1.7,0-3.4-0.7-4.6-1.9L46.9,59l14.3-14.3l0.7,0.7c2.5,2.5,2.5,6.6,0,9.2l-5.1,5.1 C55.6,60.9,53.9,61.6,52.2,61.6z M49.8,58.9c0.7,0.4,1.5,0.7,2.4,0.7c1.2,0,2.3-0.5,3.2-1.3l5.1-5.1c1.5-1.5,1.7-3.8,0.6-5.6 L49.8,58.9z"/></g><g><path fill="#000000" d="M68.9,74.8c-0.8,0-1.5-0.3-2-0.8c-1.1-1.1-1.1-2.9,0-4l5.2-5.2c1.1-1.1,2.9-1.1,4,0c0.5,0.5,0.8,1.2,0.8,2 c0,0.8-0.3,1.5-0.8,2L70.9,74C70.4,74.5,69.7,74.8,68.9,74.8z M74.2,65.9c-0.2,0-0.4,0.1-0.6,0.2l-5.2,5.2c-0.3,0.3-0.3,0.8,0,1.2 c0.3,0.3,0.9,0.3,1.2,0l5.2-5.2c0.2-0.2,0.2-0.4,0.2-0.6s-0.1-0.4-0.2-0.6C74.6,66,74.4,65.9,74.2,65.9z"/></g><g><rect x="38.6" y="52.3" transform="matrix(0.7082 0.706 -0.706 0.7082 50.8397 -16.4875)" fill="#000000" width="13.4" height="2"/></g><g><polygon fill="#000000" points="30.6,39.9 9,18.4 9,7 19.7,7 41.1,29.1 39.7,30.5 18.8,9 11,9 11,17.6 32,38.5 "/></g><g><rect x="47.8" y="43.1" transform="matrix(0.6959 0.7181 -0.7181 0.6959 48.1381 -25.5246)" fill="#000000" width="12.8" height="2"/></g><g><rect x="12" y="23.1" transform="matrix(0.6974 0.7167 -0.7167 0.6974 25.1384 -11.3825)" fill="#000000" width="28.1" height="2"/></g><g><rect x="43.8" y="46.4" transform="matrix(0.6974 0.7167 -0.7167 0.6974 48.7492 -20.5985)" fill="#000000" width="10" height="2"/></g><g><rect x="66" y="61" fill="#000000" width="2" height="7"/></g><g><rect x="62" y="57" fill="#000000" width="2" height="7"/></g></g></svg>;
|
return <svg version='1.1' x='0px' y='0px' viewBox='0 0 80 100' enableBackground='new 0 0 80 80'><g><g><polygon fill='#000000' points='12.9,71.4 7.6,66.1 19.3,54.4 20.7,55.8 10.4,66.1 12.9,68.6 23.2,58.3 24.6,59.7 '/></g><g><path fill='#000000' d='M29,61.6c-1.7,0-3.4-0.7-4.6-1.9l-5.1-5.1c-2.5-2.5-2.5-6.6,0-9.2l0.7-0.7L34.3,59l-0.7,0.7 C32.4,60.9,30.8,61.6,29,61.6z M20.1,47.6c-1.1,1.7-0.9,4.1,0.6,5.6l5.1,5.1c0.8,0.8,2,1.3,3.2,1.3c0.9,0,1.7-0.2,2.4-0.7 L20.1,47.6z'/></g><g><path fill='#000000' d='M12.3,74.8c-0.8,0-1.5-0.3-2-0.8l-5.2-5.2c-0.5-0.5-0.8-1.2-0.8-2c0-0.8,0.3-1.5,0.8-2 c1.1-1.1,2.9-1.1,4,0l5.2,5.2c1.1,1.1,1.1,2.9,0,4C13.8,74.5,13.1,74.8,12.3,74.8z M7.1,65.9c-0.2,0-0.4,0.1-0.6,0.2 c-0.2,0.2-0.2,0.4-0.2,0.6s0.1,0.4,0.2,0.6l5.2,5.2c0.3,0.3,0.9,0.3,1.2,0c0.3-0.3,0.3-0.8,0-1.2l-5.2-5.2 C7.5,66,7.3,65.9,7.1,65.9z'/></g><g><polygon fill='#000000' points='31.7,58.7 30.3,57.3 70,17.6 70,9 62.4,9 23.3,49.4 21.9,48 61.6,7 72,7 72,18.4 '/></g><g><rect x='46' y='6.7' transform='matrix(0.7168 0.6973 -0.6973 0.7168 35.9716 -23.568)' fill='#000000' width='2' height='51.6'/></g><g><rect x='13' y='61' fill='#000000' width='2' height='7'/></g><g><rect x='17' y='57' fill='#000000' width='2' height='7'/></g></g><g><g><polygon fill='#000000' points='68.4,71.4 56.7,59.7 58.1,58.3 68.4,68.6 70.8,66.1 60.5,55.8 61.9,54.4 73.6,66.1 '/></g><g><path fill='#000000' d='M52.2,61.6c-1.7,0-3.4-0.7-4.6-1.9L46.9,59l14.3-14.3l0.7,0.7c2.5,2.5,2.5,6.6,0,9.2l-5.1,5.1 C55.6,60.9,53.9,61.6,52.2,61.6z M49.8,58.9c0.7,0.4,1.5,0.7,2.4,0.7c1.2,0,2.3-0.5,3.2-1.3l5.1-5.1c1.5-1.5,1.7-3.8,0.6-5.6 L49.8,58.9z'/></g><g><path fill='#000000' d='M68.9,74.8c-0.8,0-1.5-0.3-2-0.8c-1.1-1.1-1.1-2.9,0-4l5.2-5.2c1.1-1.1,2.9-1.1,4,0c0.5,0.5,0.8,1.2,0.8,2 c0,0.8-0.3,1.5-0.8,2L70.9,74C70.4,74.5,69.7,74.8,68.9,74.8z M74.2,65.9c-0.2,0-0.4,0.1-0.6,0.2l-5.2,5.2c-0.3,0.3-0.3,0.8,0,1.2 c0.3,0.3,0.9,0.3,1.2,0l5.2-5.2c0.2-0.2,0.2-0.4,0.2-0.6s-0.1-0.4-0.2-0.6C74.6,66,74.4,65.9,74.2,65.9z'/></g><g><rect x='38.6' y='52.3' transform='matrix(0.7082 0.706 -0.706 0.7082 50.8397 -16.4875)' fill='#000000' width='13.4' height='2'/></g><g><polygon fill='#000000' points='30.6,39.9 9,18.4 9,7 19.7,7 41.1,29.1 39.7,30.5 18.8,9 11,9 11,17.6 32,38.5 '/></g><g><rect x='47.8' y='43.1' transform='matrix(0.6959 0.7181 -0.7181 0.6959 48.1381 -25.5246)' fill='#000000' width='12.8' height='2'/></g><g><rect x='12' y='23.1' transform='matrix(0.6974 0.7167 -0.7167 0.6974 25.1384 -11.3825)' fill='#000000' width='28.1' height='2'/></g><g><rect x='43.8' y='46.4' transform='matrix(0.6974 0.7167 -0.7167 0.6974 48.7492 -20.5985)' fill='#000000' width='10' height='2'/></g><g><rect x='66' y='61' fill='#000000' width='2' height='7'/></g><g><rect x='62' y='57' fill='#000000' width='2' height='7'/></g></g></svg>;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
module.exports = function(props){
|
|
||||||
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 90 112.5" enableBackground="new 0 0 90 90" >
|
module.exports = function(props){
|
||||||
<path d="M25.363,25.54c0,1.906,8.793,3.454,19.636,3.454c10.848,0,19.638-1.547,19.638-3.454c0-1.12-3.056-2.117-7.774-2.75 c-1.418,1.891-3.659,3.133-6.208,3.133c-2.85,0-5.315-1.547-6.67-3.833C33.617,22.185,25.363,23.692,25.363,25.54z"/><path d="M84.075,54.142c0-8.68-2.868-17.005-8.144-23.829c1.106-1.399,1.41-2.771,1.41-3.854c0-6.574-10.245-9.358-19.264-10.533 c0.209,0.706,0.359,1.439,0.359,2.215c0,0.09-0.022,0.17-0.028,0.26l0,0c-0.028,0.853-0.195,1.667-0.479,2.429 c9.106,1.282,14.508,3.754,14.508,5.63c0,2.644-10.688,6.486-27.439,6.486c-16.748,0-27.438-3.842-27.438-6.486 c0-2.542,9.904-6.183,25.559-6.459c-0.098-0.396-0.159-0.807-0.2-1.223c0.006,0,0.013,0,0.017,0 c-0.017-0.213-0.063-0.417-0.063-0.636c0-1.084,0.226-2.119,0.628-3.058c-6.788,0.129-30.846,1.299-30.846,11.376 c0,1.083,0.305,2.455,1.411,3.854c-5.276,6.823-8.145,15.149-8.145,23.829c0,11.548,5.187,20.107,14.693,25.115 c-0.902,3.146-1.391,7.056,1.111,8.181c2.626,1.178,5.364-2.139,7.111-5.005c4.73,1.261,10.13,1.923,16.161,1.923 c6.034,0,11.428-0.661,16.158-1.922c1.75,2.865,4.493,6.18,7.112,5.004c2.504-1.123,2.014-5.035,1.113-8.179 C78.889,74.249,84.075,65.689,84.075,54.142z M70.39,31.392c5.43,6.046,8.78,14,8.78,22.75c0,20.919-18.582,25.309-34.171,25.309 c-15.587,0-34.17-4.39-34.17-25.309c0-8.75,3.35-16.7,8.781-22.753c5.561,2.643,15.502,4.009,25.389,4.009 C54.886,35.397,64.829,34.031,70.39,31.392z"/><path d="M50.654,23.374c2.892,0,5.234-2.341,5.234-5.233c0-2.887-2.343-5.23-5.234-5.23c-2.887,0-5.231,2.343-5.231,5.23 C45.423,21.032,47.768,23.374,50.654,23.374z"/>
|
return <svg version='1.1' x='0px' y='0px' viewBox='0 0 90 112.5' enableBackground='new 0 0 90 90' >
|
||||||
<circle cx="62.905" cy="10.089" r="3.595"/>
|
<path d='M25.363,25.54c0,1.906,8.793,3.454,19.636,3.454c10.848,0,19.638-1.547,19.638-3.454c0-1.12-3.056-2.117-7.774-2.75 c-1.418,1.891-3.659,3.133-6.208,3.133c-2.85,0-5.315-1.547-6.67-3.833C33.617,22.185,25.363,23.692,25.363,25.54z'/><path d='M84.075,54.142c0-8.68-2.868-17.005-8.144-23.829c1.106-1.399,1.41-2.771,1.41-3.854c0-6.574-10.245-9.358-19.264-10.533 c0.209,0.706,0.359,1.439,0.359,2.215c0,0.09-0.022,0.17-0.028,0.26l0,0c-0.028,0.853-0.195,1.667-0.479,2.429 c9.106,1.282,14.508,3.754,14.508,5.63c0,2.644-10.688,6.486-27.439,6.486c-16.748,0-27.438-3.842-27.438-6.486 c0-2.542,9.904-6.183,25.559-6.459c-0.098-0.396-0.159-0.807-0.2-1.223c0.006,0,0.013,0,0.017,0 c-0.017-0.213-0.063-0.417-0.063-0.636c0-1.084,0.226-2.119,0.628-3.058c-6.788,0.129-30.846,1.299-30.846,11.376 c0,1.083,0.305,2.455,1.411,3.854c-5.276,6.823-8.145,15.149-8.145,23.829c0,11.548,5.187,20.107,14.693,25.115 c-0.902,3.146-1.391,7.056,1.111,8.181c2.626,1.178,5.364-2.139,7.111-5.005c4.73,1.261,10.13,1.923,16.161,1.923 c6.034,0,11.428-0.661,16.158-1.922c1.75,2.865,4.493,6.18,7.112,5.004c2.504-1.123,2.014-5.035,1.113-8.179 C78.889,74.249,84.075,65.689,84.075,54.142z M70.39,31.392c5.43,6.046,8.78,14,8.78,22.75c0,20.919-18.582,25.309-34.171,25.309 c-15.587,0-34.17-4.39-34.17-25.309c0-8.75,3.35-16.7,8.781-22.753c5.561,2.643,15.502,4.009,25.389,4.009 C54.886,35.397,64.829,34.031,70.39,31.392z'/><path d='M50.654,23.374c2.892,0,5.234-2.341,5.234-5.233c0-2.887-2.343-5.23-5.234-5.23c-2.887,0-5.231,2.343-5.231,5.23 C45.423,21.032,47.768,23.374,50.654,23.374z'/>
|
||||||
<circle cx="52.616" cy="5.048" r="2.73"/>
|
<circle cx='62.905' cy='10.089' r='3.595'/>
|
||||||
</svg>;
|
<circle cx='52.616' cy='5.048' r='2.73'/>
|
||||||
};
|
</svg>;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
module.exports = function(props){
|
|
||||||
return <svg version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enableBackground="new 0 0 100 100"><path d="M80.644,87.982l16.592-41.483c0.054-0.128,0.088-0.26,0.108-0.394c0.006-0.039,0.007-0.077,0.011-0.116 c0.007-0.087,0.008-0.174,0.002-0.26c-0.003-0.046-0.007-0.091-0.014-0.137c-0.014-0.089-0.036-0.176-0.063-0.262 c-0.012-0.034-0.019-0.069-0.031-0.103c-0.047-0.118-0.106-0.229-0.178-0.335c-0.004-0.006-0.006-0.012-0.01-0.018L67.999,3.358 c-0.01-0.013-0.003-0.026-0.013-0.04L68,3.315V4c0,0-0.033,0-0.037,0c-0.403-1-1.094-1.124-1.752-0.976 c0,0.004-0.004-0.012-0.007-0.012C66.201,3.016,66.194,3,66.194,3H66.19h-0.003h-0.003h-0.004h-0.003c0,0-0.004,0-0.007,0 s-0.003-0.151-0.007-0.151L20.495,15.227c-0.025,0.007-0.046-0.019-0.071-0.011c-0.087,0.028-0.172,0.041-0.253,0.083 c-0.054,0.027-0.102,0.053-0.152,0.085c-0.051,0.033-0.101,0.061-0.147,0.099c-0.044,0.036-0.084,0.073-0.124,0.113 c-0.048,0.048-0.093,0.098-0.136,0.152c-0.03,0.039-0.059,0.076-0.085,0.117c-0.046,0.07-0.084,0.145-0.12,0.223 c-0.011,0.023-0.027,0.042-0.036,0.066L2.911,57.664C2.891,57.715,3,57.768,3,57.82v0.002c0,0.186,0,0.375,0,0.562 c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004v0.002c0,0.074-0.002,0.15,0.012,0.223 C3.015,58.631,3,58.631,3,58.633c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004c0,0,0,0,0,0.002v0.004 c0,0.191-0.046,0.377,0.06,0.545c0-0.002-0.03,0.004-0.03,0.004c0,0.004-0.03,0.004-0.03,0.004c0,0.002,0,0.002,0,0.002 l-0.045,0.004c0.03,0.047,0.036,0.09,0.068,0.133l29.049,37.359c0.002,0.004,0,0.006,0.002,0.01c0.002,0.002,0,0.004,0.002,0.008 c0.006,0.008,0.014,0.014,0.021,0.021c0.024,0.029,0.052,0.051,0.078,0.078c0.027,0.029,0.053,0.057,0.082,0.082 c0.03,0.027,0.055,0.062,0.086,0.088c0.026,0.02,0.057,0.033,0.084,0.053c0.04,0.027,0.081,0.053,0.123,0.076 c0.005,0.004,0.01,0.008,0.016,0.01c0.087,0.051,0.176,0.09,0.269,0.123c0.042,0.014,0.082,0.031,0.125,0.043 c0.021,0.006,0.041,0.018,0.062,0.021c0.123,0.027,0.249,0.043,0.375,0.043c0.099,0,0.202-0.012,0.304-0.027l45.669-8.303 c0.057-0.01,0.108-0.021,0.163-0.037C79.547,88.992,79.562,89,79.575,89c0.004,0,0.004,0,0.004,0c0.021,0,0.039-0.027,0.06-0.035 c0.041-0.014,0.08-0.034,0.12-0.052c0.021-0.01,0.044-0.019,0.064-0.03c0.017-0.01,0.026-0.015,0.033-0.017 c0.014-0.008,0.023-0.021,0.037-0.028c0.14-0.078,0.269-0.174,0.38-0.285c0.014-0.016,0.024-0.034,0.038-0.048 c0.109-0.119,0.201-0.252,0.271-0.398c0.006-0.01,0.016-0.018,0.021-0.029c0.004-0.008,0.008-0.017,0.011-0.026 c0.002-0.004,0.003-0.006,0.005-0.01C80.627,88.021,80.635,88.002,80.644,87.982z M77.611,84.461L48.805,66.453l32.407-25.202 L77.611,84.461z M46.817,63.709L35.863,23.542l43.818,14.608L46.817,63.709z M84.668,40.542l8.926,5.952l-11.902,29.75 L84.668,40.542z M89.128,39.446L84.53,36.38l-6.129-12.257L89.128,39.446z M79.876,34.645L37.807,20.622L65.854,6.599L79.876,34.645 z M33.268,19.107l-6.485-2.162l23.781-6.487L33.268,19.107z M21.92,18.895l8.67,2.891L10.357,47.798L21.92,18.895z M32.652,24.649 l10.845,39.757L7.351,57.178L32.652,24.649z M43.472,67.857L32.969,92.363L8.462,60.855L43.472,67.857z M46.631,69.09l27.826,17.393 l-38.263,6.959L46.631,69.09z"></path></svg>;
|
module.exports = function(props){
|
||||||
};
|
return <svg version='1.1' x='0px' y='0px' viewBox='0 0 100 100' enableBackground='new 0 0 100 100'><path d='M80.644,87.982l16.592-41.483c0.054-0.128,0.088-0.26,0.108-0.394c0.006-0.039,0.007-0.077,0.011-0.116 c0.007-0.087,0.008-0.174,0.002-0.26c-0.003-0.046-0.007-0.091-0.014-0.137c-0.014-0.089-0.036-0.176-0.063-0.262 c-0.012-0.034-0.019-0.069-0.031-0.103c-0.047-0.118-0.106-0.229-0.178-0.335c-0.004-0.006-0.006-0.012-0.01-0.018L67.999,3.358 c-0.01-0.013-0.003-0.026-0.013-0.04L68,3.315V4c0,0-0.033,0-0.037,0c-0.403-1-1.094-1.124-1.752-0.976 c0,0.004-0.004-0.012-0.007-0.012C66.201,3.016,66.194,3,66.194,3H66.19h-0.003h-0.003h-0.004h-0.003c0,0-0.004,0-0.007,0 s-0.003-0.151-0.007-0.151L20.495,15.227c-0.025,0.007-0.046-0.019-0.071-0.011c-0.087,0.028-0.172,0.041-0.253,0.083 c-0.054,0.027-0.102,0.053-0.152,0.085c-0.051,0.033-0.101,0.061-0.147,0.099c-0.044,0.036-0.084,0.073-0.124,0.113 c-0.048,0.048-0.093,0.098-0.136,0.152c-0.03,0.039-0.059,0.076-0.085,0.117c-0.046,0.07-0.084,0.145-0.12,0.223 c-0.011,0.023-0.027,0.042-0.036,0.066L2.911,57.664C2.891,57.715,3,57.768,3,57.82v0.002c0,0.186,0,0.375,0,0.562 c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004v0.002c0,0.074-0.002,0.15,0.012,0.223 C3.015,58.631,3,58.631,3,58.633c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004c0,0,0,0,0,0.002v0.004 c0,0.191-0.046,0.377,0.06,0.545c0-0.002-0.03,0.004-0.03,0.004c0,0.004-0.03,0.004-0.03,0.004c0,0.002,0,0.002,0,0.002 l-0.045,0.004c0.03,0.047,0.036,0.09,0.068,0.133l29.049,37.359c0.002,0.004,0,0.006,0.002,0.01c0.002,0.002,0,0.004,0.002,0.008 c0.006,0.008,0.014,0.014,0.021,0.021c0.024,0.029,0.052,0.051,0.078,0.078c0.027,0.029,0.053,0.057,0.082,0.082 c0.03,0.027,0.055,0.062,0.086,0.088c0.026,0.02,0.057,0.033,0.084,0.053c0.04,0.027,0.081,0.053,0.123,0.076 c0.005,0.004,0.01,0.008,0.016,0.01c0.087,0.051,0.176,0.09,0.269,0.123c0.042,0.014,0.082,0.031,0.125,0.043 c0.021,0.006,0.041,0.018,0.062,0.021c0.123,0.027,0.249,0.043,0.375,0.043c0.099,0,0.202-0.012,0.304-0.027l45.669-8.303 c0.057-0.01,0.108-0.021,0.163-0.037C79.547,88.992,79.562,89,79.575,89c0.004,0,0.004,0,0.004,0c0.021,0,0.039-0.027,0.06-0.035 c0.041-0.014,0.08-0.034,0.12-0.052c0.021-0.01,0.044-0.019,0.064-0.03c0.017-0.01,0.026-0.015,0.033-0.017 c0.014-0.008,0.023-0.021,0.037-0.028c0.14-0.078,0.269-0.174,0.38-0.285c0.014-0.016,0.024-0.034,0.038-0.048 c0.109-0.119,0.201-0.252,0.271-0.398c0.006-0.01,0.016-0.018,0.021-0.029c0.004-0.008,0.008-0.017,0.011-0.026 c0.002-0.004,0.003-0.006,0.005-0.01C80.627,88.021,80.635,88.002,80.644,87.982z M77.611,84.461L48.805,66.453l32.407-25.202 L77.611,84.461z M46.817,63.709L35.863,23.542l43.818,14.608L46.817,63.709z M84.668,40.542l8.926,5.952l-11.902,29.75 L84.668,40.542z M89.128,39.446L84.53,36.38l-6.129-12.257L89.128,39.446z M79.876,34.645L37.807,20.622L65.854,6.599L79.876,34.645 z M33.268,19.107l-6.485-2.162l23.781-6.487L33.268,19.107z M21.92,18.895l8.67,2.891L10.357,47.798L21.92,18.895z M32.652,24.649 l10.845,39.757L7.351,57.178L32.652,24.649z M43.472,67.857L32.969,92.363L8.462,60.855L43.472,67.857z M46.631,69.09l27.826,17.393 l-38.263,6.959L46.631,69.09z'></path></svg>;
|
||||||
|
};
|
||||||
7
tests/basic.test.js
Normal file
7
tests/basic.test.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const test = require('pico-check');
|
||||||
|
|
||||||
|
test('Just setting up a spot for future tests', (t)=>{
|
||||||
|
t.pass();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = test;
|
||||||
1
tests/test.init.js
Normal file
1
tests/test.init.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
//Set up configs and DB connectiosna nd what not in here
|
||||||
Reference in New Issue
Block a user