mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-27 20:23:08 +00:00
Compare commits
204 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6030134de2 | ||
|
|
7e6f42f062 | ||
|
|
75111acefb | ||
|
|
26bcb3395a | ||
|
|
a826aaffd9 | ||
|
|
8018442f25 | ||
|
|
8e58e5aca9 | ||
|
|
2f82d3875e | ||
|
|
efee8ff05c | ||
|
|
a405c7cfb2 | ||
|
|
dfcb04fd09 | ||
|
|
728277f861 | ||
|
|
0878439750 | ||
|
|
e77532acef | ||
|
|
cd2eb5fdce | ||
|
|
37de888f03 | ||
|
|
1aa79b32d9 | ||
|
|
894e345a44 | ||
|
|
07f249b23e | ||
|
|
baaa82ed34 | ||
|
|
0d0f0d8eb0 | ||
|
|
d77fa0a3dc | ||
|
|
a26c4e2092 | ||
|
|
ca40ec5a2d | ||
|
|
987363ed41 | ||
|
|
7b38bccec1 | ||
|
|
174c2973f7 | ||
|
|
66ca09b36d | ||
|
|
5820564894 | ||
|
|
3dc4c13178 | ||
|
|
537a75b2ab | ||
|
|
a0bc4fddf8 | ||
|
|
25e0a1607a | ||
|
|
68ecf749ea | ||
|
|
10f4759471 | ||
|
|
5ba3f98696 | ||
|
|
95c09ba7ad | ||
|
|
1173af5803 | ||
|
|
924b398768 | ||
|
|
41303e6918 | ||
|
|
f75f60aa1e | ||
|
|
f4cf288f27 | ||
|
|
8abf6abf99 | ||
|
|
95aa803c61 | ||
|
|
47396e5c7e | ||
|
|
7581d155a6 | ||
|
|
cc8e874ad1 | ||
|
|
8b148b81b8 | ||
|
|
063d701a0e | ||
|
|
296b066ed3 | ||
|
|
9fe6fd979b | ||
|
|
70346ffce7 | ||
|
|
c417c1aa0c | ||
|
|
5e04e5dc99 | ||
|
|
6544d63b23 | ||
|
|
5bfa195dc7 | ||
|
|
9b936f42f3 | ||
|
|
5fe7c7a6d8 | ||
|
|
6e86d9c123 | ||
|
|
306e4f024c | ||
|
|
a2c6940be4 | ||
|
|
75f83427e0 | ||
|
|
4be9f4a74a | ||
|
|
9a9e0cc5bc | ||
|
|
62e4997fa9 | ||
|
|
b45ec32e44 | ||
|
|
9fd7586726 | ||
|
|
75f0a9f755 | ||
|
|
a35d684773 | ||
|
|
9b80f17564 | ||
|
|
90a6ac4ef8 | ||
|
|
bd6ba1e497 | ||
|
|
56048ab936 | ||
|
|
1ec0b2fa91 | ||
|
|
fe4a05dc25 | ||
|
|
1f1bd669fe | ||
|
|
63aee2dedf | ||
|
|
e5ffb7c629 | ||
|
|
be783e5f6b | ||
|
|
d96ac0f3ca | ||
|
|
ce5596c489 | ||
|
|
9285e53e55 | ||
|
|
bec417a325 | ||
|
|
1eeb1127b1 | ||
|
|
8e6fccc969 | ||
|
|
a634b76117 | ||
|
|
30942785d1 | ||
|
|
21d3c5bfc8 | ||
|
|
7f639a0824 | ||
|
|
a0ca45ce1c | ||
|
|
4e5cd914f7 | ||
|
|
2d6b89c769 | ||
|
|
6af9dd9325 | ||
|
|
9e14872f06 | ||
|
|
750f5c1330 | ||
|
|
1db24d07bd | ||
|
|
f4f96253c2 | ||
|
|
97cf2d2ce7 | ||
|
|
e61bf22788 | ||
|
|
ccdb6a3cbd | ||
|
|
d400c37b6d | ||
|
|
2f2a1c5146 | ||
|
|
8b3f9ac76a | ||
|
|
6672dff938 | ||
|
|
d7463ec28e | ||
|
|
4dce90ab41 | ||
|
|
5f6f7ec691 | ||
|
|
582deb7bf7 | ||
|
|
8c4a6221e2 | ||
|
|
58c67d7d4d | ||
|
|
9fe52145e6 | ||
|
|
b671161044 | ||
|
|
a439c418ef | ||
|
|
b8effb1ed1 | ||
|
|
e576e6d971 | ||
|
|
9f05556bc5 | ||
|
|
2a0c06cd3d | ||
|
|
dd1264d2e6 | ||
|
|
c49321d590 | ||
|
|
cd2337ff2c | ||
|
|
9eb26a95f3 | ||
|
|
c7ccf6747c | ||
|
|
9a96eebdb1 | ||
|
|
54542a8ec1 | ||
|
|
efddfd31ee | ||
|
|
0690216222 | ||
|
|
9e54fe26d9 | ||
|
|
01e4e11ef7 | ||
|
|
7dc39e962b | ||
|
|
9adf28171e | ||
|
|
152194aba8 | ||
|
|
a107d59d72 | ||
|
|
e6be1ae12f | ||
|
|
460585f9c9 | ||
|
|
143c006298 | ||
|
|
43ec212be9 | ||
|
|
78426135c6 | ||
|
|
b134e11a86 | ||
|
|
888d3faa4c | ||
|
|
ef8784ccf2 | ||
|
|
0a7b538216 | ||
|
|
019383ebdd | ||
|
|
90c695c005 | ||
|
|
6b337b5d69 | ||
|
|
62d70022e7 | ||
|
|
cd454e82ef | ||
|
|
8a7e441724 | ||
|
|
1482501574 | ||
|
|
f22fd73162 | ||
|
|
91c7f45d33 | ||
|
|
34b21703e1 | ||
|
|
73e561beba | ||
|
|
388ae933f8 | ||
|
|
33fd991276 | ||
|
|
e31cb003eb | ||
|
|
46581cfcf5 | ||
|
|
073b547f96 | ||
|
|
3ff736b440 | ||
|
|
ead975b605 | ||
|
|
cd97b22067 | ||
|
|
e38850f807 | ||
|
|
8df4dc56b2 | ||
|
|
235664ec88 | ||
|
|
3e796501e2 | ||
|
|
0d25a972ba | ||
|
|
5969e45087 | ||
|
|
f058814040 | ||
|
|
038c89fc20 | ||
|
|
d9bf164010 | ||
|
|
39d5d5c458 | ||
|
|
d2a9b3f274 | ||
|
|
7ca4e8ffa6 | ||
|
|
60092f404c | ||
|
|
c50de28900 | ||
|
|
6611bc9eff | ||
|
|
802103ff27 | ||
|
|
9950c747da | ||
|
|
2b2b6b2793 | ||
|
|
6474825ffb | ||
|
|
7eb47d7db0 | ||
|
|
b8c55d72a1 | ||
|
|
87af0e8cb7 | ||
|
|
63dcbfa388 | ||
|
|
8a7bffce5d | ||
|
|
715f2f41bb | ||
|
|
b33ef939ed | ||
|
|
00bf12ecb7 | ||
|
|
c993ae9cf5 | ||
|
|
626cba6062 | ||
|
|
14b7d60856 | ||
|
|
5a1041cbb3 | ||
|
|
56bb0e0ad8 | ||
|
|
dce3f224c7 | ||
|
|
01e040d1ab | ||
|
|
fa138499af | ||
|
|
7cc70149da | ||
|
|
2ed9395e29 | ||
|
|
53529d14eb | ||
|
|
7501342e85 | ||
|
|
68fc0f95d1 | ||
|
|
52ff2e41e6 | ||
|
|
e7f1083edb | ||
|
|
6209fcd5cb | ||
|
|
fc3587d6cb |
20
.github/issue_template.md
vendored
20
.github/issue_template.md
vendored
@@ -1,14 +1,16 @@
|
|||||||
**Browser Type/Version**: [Google Ultron v90.01]
|
|
||||||
|
|
||||||
**Operating System**: [GLaDOS v34.5.8]
|
|
||||||
|
|
||||||
**Issue Description**: [The thing won't thing]
|
|
||||||
|
|
||||||
**Markdown code to reproduce**:
|
|
||||||
|
|
||||||
```
|
|
||||||
# thing
|
|
||||||
> thing 2
|
|
||||||
```
|
|
||||||
|
|
||||||
**Related Images** :
|
### Additional Details
|
||||||
|
|
||||||
|
**Share Link** :
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
**Brew code to reproduce** : <details><summary>Click to expand</summary><code><pre>
|
||||||
|
|
||||||
|
PASTE BREW CODE HERE
|
||||||
|
|
||||||
|
</pre></code></details>
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -4,13 +4,13 @@ logs
|
|||||||
|
|
||||||
#Ignore our built files
|
#Ignore our built files
|
||||||
build/*
|
build/*
|
||||||
architecture.json
|
|
||||||
|
|
||||||
# Ignore sensitive stuff
|
# Ignore sensitive stuff
|
||||||
/config/*
|
config/local.json
|
||||||
!/config/default.json
|
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
storage
|
storage
|
||||||
.idea
|
.idea
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
todo.md
|
||||||
29
Dockerfile
29
Dockerfile
@@ -1,29 +0,0 @@
|
|||||||
FROM node:latest
|
|
||||||
|
|
||||||
MAINTAINER David Hudson <jendave@yahoo.com>
|
|
||||||
|
|
||||||
# System update
|
|
||||||
RUN apt-get -q -y update
|
|
||||||
|
|
||||||
RUN apt-get -q -y install npm
|
|
||||||
RUN apt-get -q -y install mongodb
|
|
||||||
|
|
||||||
RUN apt-get clean && rm -r /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
EXPOSE 22
|
|
||||||
EXPOSE 8000
|
|
||||||
|
|
||||||
ADD start.sh /start.sh
|
|
||||||
RUN chmod +x /start.sh
|
|
||||||
|
|
||||||
VOLUME ["/opt/apps"]
|
|
||||||
COPY . /opt/apps/naturalcrit/
|
|
||||||
WORKDIR /opt/apps/naturalcrit/
|
|
||||||
|
|
||||||
RUN npm install
|
|
||||||
RUN npm install -g gulp-cli
|
|
||||||
RUN npm install gulp
|
|
||||||
RUN gulp fresh
|
|
||||||
|
|
||||||
CMD ["/start.sh"]
|
|
||||||
|
|
||||||
46
README.md
46
README.md
@@ -1,33 +1,33 @@
|
|||||||
# NaturalCrit
|
# The Homebrewery
|
||||||
A tool suite for DMs to use for D&D. Check it out [here](http://www.naturalcrit.com).
|
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).
|
||||||
|
|
||||||
|
|
||||||
### Getting started
|
### issues, suggestions, bugs
|
||||||
1. Make sure you have [node](https://nodejs.org/en/)
|
If you run into any issues using The Homebrewery, please submit an issues [here](/issues)
|
||||||
1. Clone down the repo
|
|
||||||
1. In your terminal, head to the repo
|
|
||||||
1. Run `npm install` to get all the dependacies
|
|
||||||
2. Run `npm install -g gulp` to install the gulp build tool
|
|
||||||
1. Run `gulp fresh`, this will compile and build all the needed libraries (this only has to be done once, unless you add more libs)
|
|
||||||
1. Run `gulp` to run the project locally. Should be accessible at `localhost:8000`
|
|
||||||
2. Any changes to files within the proejct will be detected and the propject will automatically re-build
|
|
||||||
|
|
||||||
**Notes:** If you'd like to create and edit homebrews, you'll need to have MongoDB installed and running.
|
|
||||||
|
|
||||||
Have fun!
|
### local dev
|
||||||
|
Homebrewery is open source, so feel free to clone it, tinker with it, or run your own local version.
|
||||||
|
|
||||||
### Docker Image
|
#### pre-reqs
|
||||||
You can use [Docker](https://docs.docker.com) to get up and running with NaturalCrit.
|
1. install [node](https://nodejs.org/en/)
|
||||||
|
1. install [mongodb](https://www.mongodb.com/)
|
||||||
|
|
||||||
1. Install Docker
|
#### getting started
|
||||||
1. Clone the repo
|
1. clone it
|
||||||
1. In the terminal, go to the repo
|
1. `npm install`
|
||||||
1. Build the docker image `docker build -t naturalcrit .`
|
1. `npm build`
|
||||||
1. Run the docker container `docker run -dit -p 8000:8000 naturalcrit`
|
1. `npm start`
|
||||||
1. You can check out the website on your computer on port 8000
|
|
||||||
1. You may have to use `docker-machine env` to get the IP address of your docker instance
|
|
||||||
|
|
||||||
|
#### standalone PHB stylesheet
|
||||||
|
If you just want the stylesheet that is generated to make pages look like they are from the PLayer's Handbook, you have find it [here](https://github.com/stolksdorf/homebrewery/blob/master/phb.standalone.css)
|
||||||
|
|
||||||
|
If you are developing locally and would like to generate your own, follow the above steps and then run `npm run phb`.
|
||||||
|
|
||||||
### changelog
|
### changelog
|
||||||
|
|
||||||
You can check out the changelog [here](https://github.com/stolksdorf/NaturalCrit/blob/master/changelog.md)
|
You can check out the changelog [here](https://github.com/stolksdorf/homebrewery/blob/master/changelog.md)
|
||||||
|
|
||||||
|
### license
|
||||||
|
|
||||||
|
This project is licensed under [MIT](./license)
|
||||||
|
|||||||
141
changelog.md
141
changelog.md
@@ -1,5 +1,146 @@
|
|||||||
# changelog
|
# changelog
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
All brews made previous to the release of v3.0.0 will still render normally.
|
||||||
|
|
||||||
|
### Thursday, 19/01/2017 - v2.7.0
|
||||||
|
- Fixed saving multiple authors and multiple systems on brew metadata (thanks u/PalaNolho re:282)
|
||||||
|
- Adding in line highlight for new pages
|
||||||
|
- Added in a simple brew lookup for admin
|
||||||
|
|
||||||
|
|
||||||
|
### Saturday, 14/01/2017 - v2.7.0
|
||||||
|
- Added a new Render Warning overlay. It detects situations where the brew may not be rendering correctly (wrong browser, browser is zoomed in...) and let's the user know
|
||||||
|
|
||||||
|
|
||||||
|
### Sunday, 25/12/2016 - v2.7.0
|
||||||
|
- Switching over to using Vitreum v4
|
||||||
|
- Removed gulp, all tasks are run through npm scripts
|
||||||
|
- Updating docs for local dev
|
||||||
|
- Removing support for Docker. I have never used it, nor will I ever test for it, so I don't want to continue to explictly support it on this repo. Feel free to make a fork and make it docker-able though :)
|
||||||
|
- Changed icon for the metadata
|
||||||
|
- Made links useable in footer (thanks u/Dustfinger1 re:249)
|
||||||
|
- Added print media queries to remove box shadow on print (thanks u/dmmagic re: 246)
|
||||||
|
- Fixed realtime renderer not functioning if loaded with malformed html on load (thanks u/RattiganIV re:247)
|
||||||
|
- Removed a lot of unused files in shared
|
||||||
|
- vitreum v4 now lets me use codemirror as a pure node dependacy
|
||||||
|
- Moved the brew editor and renderer into shared, and made a new hybrid component for them
|
||||||
|
- Added a line highlighter to lines with new pages
|
||||||
|
|
||||||
|
|
||||||
|
### Saturday, 03/12/2016 - v2.6.0
|
||||||
|
- Added report back to the edit page
|
||||||
|
- Changed metaeditor icon
|
||||||
|
- Added a button to quickly share your brew to reddit :)
|
||||||
|
- Disabled Partial Page Rendering unless your brew hits 75 pages or longer
|
||||||
|
- The brew renderer will now try and use your first page to judge the page size of each of your brews. This allows you now to set landscape and other weird sizes and everthing should work fine :)
|
||||||
|
- UI on the user page improved (thanks u/PalaNolho)
|
||||||
|
- Fixed lists not breaking across columns (thanks u/tyson-nw)
|
||||||
|
- Added a table of contents snippet (thanks u/tullisar)
|
||||||
|
- Added a multicolumn snippet
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Thursday, 01/12/2016
|
||||||
|
- Added in a snippet for a split table
|
||||||
|
- Added an account nav item to new page
|
||||||
|
|
||||||
|
|
||||||
|
### Sunday, 27/11/2016 - v2.5.1
|
||||||
|
- Fixed the column rendering on the new user page. Really should have tested that better
|
||||||
|
- Added a hover tooltip to fully read the brew description
|
||||||
|
- Made the brew items take up only 25% allowing you to view more per row.
|
||||||
|
|
||||||
|
### Wednesday, 23/11/2016 - v2.5.0
|
||||||
|
- Metadata can now be added to brews
|
||||||
|
- Added a metadata editor onto the edit and new pages
|
||||||
|
- Moved deleting a brew into the metadata editor
|
||||||
|
- Added in account middleware
|
||||||
|
- Can now search for brews by a specific author
|
||||||
|
- Editing a brew in anyway while logged in will now add you to the list of authors
|
||||||
|
- Added a new user page to see others published brews, as well as all of your own brews.
|
||||||
|
- Added a new nav item for accessing your profile and logging in
|
||||||
|
|
||||||
|
|
||||||
|
### Monday, 14/11/2016
|
||||||
|
- Updated snippet bar style
|
||||||
|
- You can now print from a new page without saving
|
||||||
|
- Added the ability to use ctrl+p and ctrl+s to print and save respectively.
|
||||||
|
|
||||||
|
### Monday, 07/11/2016
|
||||||
|
- Added final touches to the html validator and updating the rest of the branch
|
||||||
|
- If anyone finds issues with the new HTML validator, please let me know. I hope this will bring a more consistent feel to Homebrewery rendering.
|
||||||
|
|
||||||
|
### Friday, 09/09/2016 - v2.4.0
|
||||||
|
- Adding in a HTML validator that will display warnings whenever you save. This should stop a lot of the issues generated with pages not showing up.
|
||||||
|
|
||||||
|
### Saturday, 20/08/2016 - v2.3.0
|
||||||
|
- Added in a license file
|
||||||
|
- Updated the welcome text
|
||||||
|
- Added in a much better Error page
|
||||||
|
- If you visit a deleted brew, it will now remove it from your recent list. (Thanks u/sIllverback!)
|
||||||
|
- Improved parsing of embedded html text in brews. (Thanks u/com-charizard!)
|
||||||
|
- Added in a new coverpage snippet
|
||||||
|
- Homebrewery will now try and onsert a good title for your brew if you don't provide one
|
||||||
|
- Homebrewery now re-renders properly when you zoom
|
||||||
|
- Fixed the noteblock overlapping into titles (thanks u/dsompura!)
|
||||||
|
- Fixed a bad search route in the admin panel (thanks u/SnappyTom!)
|
||||||
|
|
||||||
|
|
||||||
|
### Friday, 29/07/2016 - v2.2.7
|
||||||
|
- Adding in descriptive note blocks. (Thanks calculuschild!)
|
||||||
|
|
||||||
|
### Thursday, 07/07/2016 - v2.2.6
|
||||||
|
- Added a new nav item on the homepage for accessing both recently viewed and edited brews (thanks [ChosenSeraph!](https://github.com/stolksdorf/homebrewery/issues/147))
|
||||||
|
|
||||||
|
### Friday, 10/06/2016 - v2.2.4
|
||||||
|
- Added an id to each rendered page
|
||||||
|
- Allows adding in hyperlinks to specific pages
|
||||||
|
- Even works after you print to pdf!
|
||||||
|
|
||||||
|
### Tuesday, 07/06/2016 - v2.2.2
|
||||||
|
- Fixed bug with new markdown lexer and aprser not working on print page
|
||||||
|
|
||||||
|
### Sunday, 05/06/2016 - v2.2.1
|
||||||
|
- Adding in a new Class table div block. The old Class table block used weird stacking of HTML elements, resulting is difficult to control behaviour and poor interactiosn with the rest of the page. This new block is much easier to style and work with.
|
||||||
|
- Added in a new wide table snippet
|
||||||
|
- Added in a new auto-incremeting page number snippet (thakns u/Ryrok!)
|
||||||
|
- Lists in monster stat blocks should be fixed now
|
||||||
|
|
||||||
|
|
||||||
|
### Saturday, 04/06/2016 - v2.2.0
|
||||||
|
- MIgrating The Homebrewery over to hombrewery.naturalcrit.com. It know runs on it's own server, with it's own repo separate from the other tools I'm working on. Makes updating and deploying much easier.
|
||||||
|
|
||||||
|
\page
|
||||||
|
|
||||||
|
### Sunday, 29/05/2016 - v2.1.0
|
||||||
|
- Finally added a syntax for doing spell lists. A bit in-depth about why this took so long. Essentially I'm running out of syntax to use in stardard Markdown. There are too many unique elements in the PHB-style to be mapped. I solved this earlier by stacking certain elements together (eg. an `<hr>` before a `blockquote` turns it into moster state block), but those are getting unweildly. I would like to simply wrap these in `div`s with classes, but unfortunately Markdown stops processing when within HTML blocks. To get around this I wrote my own override to the Markdown parser and lexer to process Markdown within a simple div class wrapper. This should open the door for more unique syntaxes in the future. Big step!
|
||||||
|
- Override Ctrl+P (and cmd+P) to launch to the print page. Many people try to just print either the editing or share page to get a PDF. While this dones;t make much sense, I do get a ton of issues about it. So now if you try to do this, it'll just bring you imediately to the print page. Everybody wins!
|
||||||
|
- The onboarding flow has also been confusing a few users (Homepage -> new -> save -> edit page). If you edit the Homepage text now, a Call to Action to save your work will pop-up.
|
||||||
|
- Added a 'Recently Edited' and 'Recently Viewed' nav item to the edit and share page respectively. Each will remember the last 8 items you edited or viewed and when you viewed it. Makes use of the new title attribute of brews to easy navigatation.
|
||||||
|
- Paragraphs now indent properly after lists (thanks u/slitjen!)
|
||||||
|
|
||||||
|
### Friday, 27/05/2016 - v2.0.6
|
||||||
|
- Updated the issue template for (hopefully) better reporting
|
||||||
|
- Added suggestion to use chrome while PDF printing
|
||||||
|
|
||||||
|
### Wednesday, 25/05/2016 -v2.0.5
|
||||||
|
- The class table generators have the proper ability score improvement progression.
|
||||||
|
|
||||||
|
### Tuesday, 24/05/2016 - v2.0.4
|
||||||
|
- Fixed extra wide monster stat blocks sometimes only being one column
|
||||||
|
- The class table generators now follow the proper progression from the PHB (thakns u/IrishBandit)
|
||||||
|
|
||||||
|
### Thursday, 19/05/2016 - v2.0.2
|
||||||
|
|
||||||
|
- No longer server-side pre-render brews, just incase the user entered invalid HTML, it might crahsh the server
|
||||||
|
- Bumped up the allowed entity size for extra-large brew (Thanks for reporting it dickboner93)
|
||||||
|
- Added a little error box when a save fails with a custom link to reporting the issue on github.
|
||||||
|
|
||||||
|
\page
|
||||||
|
|
||||||
### Saturday, 14/05/2016 - v2.0.0 (finally!)
|
### Saturday, 14/05/2016 - v2.0.0 (finally!)
|
||||||
|
|
||||||
I've been working on v2 for a *very* long time. I want to thank you guys for being paitent.
|
I've been working on v2 for a *very* long time. I want to thank you guys for being paitent.
|
||||||
|
|||||||
@@ -1,40 +1,41 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
var Admin = React.createClass({
|
const BrewLookup = require('./brewLookup/brewLookup.jsx');
|
||||||
|
const AdminSearch = require('./adminSearch/adminSearch.jsx');
|
||||||
|
const InvalidBrew = require('./invalidBrew/invalidBrew.jsx');
|
||||||
|
|
||||||
|
const Admin = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
url : "",
|
admin_key : '',
|
||||||
admin_key : "",
|
|
||||||
homebrews : [],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderNavbar : function(){
|
||||||
|
return <Nav.base>
|
||||||
|
<Nav.section>
|
||||||
|
<Nav.item icon='fa-magic' className='homebreweryLogo'>
|
||||||
|
Homebrewery Admin
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.section>
|
||||||
|
</Nav.base>
|
||||||
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
var self = this;
|
return <div className='admin'>
|
||||||
return(
|
{this.renderNavbar()}
|
||||||
<div className='admin'>
|
<main className='content'>
|
||||||
|
<BrewLookup adminKey={this.props.admin_key} />
|
||||||
|
<AdminSearch adminKey={this.props.admin_key} />
|
||||||
|
|
||||||
<header>
|
<div className='dangerZone'>Danger Zone</div>
|
||||||
<div className='container'>
|
|
||||||
<i className='fa fa-rocket' />
|
|
||||||
naturalcrit admin
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className='container'>
|
<InvalidBrew adminKey={this.props.admin_key} />
|
||||||
|
</main>
|
||||||
<a target="_blank" href='https://www.google.com/analytics/web/?hl=en#report/defaultid/a72212009w109843310p114529111/'>Link to Google Analytics</a>
|
</div>
|
||||||
|
|
||||||
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,53 @@
|
|||||||
@import 'naturalcrit/styles/reset.less';
|
|
||||||
@import 'naturalcrit/styles/elements.less';
|
|
||||||
@import 'naturalcrit/styles/animations.less';
|
|
||||||
@import 'naturalcrit/styles/colors.less';
|
|
||||||
@import 'naturalcrit/styles/tooltip.less';
|
|
||||||
|
|
||||||
@import 'font-awesome/css/font-awesome.css';
|
@import 'naturalcrit/styles/core.less';
|
||||||
|
html,body, #reactRoot{
|
||||||
html,body, #reactContainer, .naturalCrit{
|
|
||||||
min-height : 100%;
|
min-height : 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@sidebarWidth : 250px;
|
|
||||||
|
|
||||||
body{
|
body{
|
||||||
background-color : #eee;
|
height : 100%;
|
||||||
font-family : 'Open Sans', sans-serif;
|
|
||||||
color : #4b5055;
|
|
||||||
font-weight : 100;
|
|
||||||
text-rendering : optimizeLegibility;
|
|
||||||
margin : 0;
|
margin : 0;
|
||||||
padding : 0;
|
padding : 0;
|
||||||
height : 100%;
|
background-color : #ddd;
|
||||||
|
font-family : 'Open Sans', sans-serif;
|
||||||
|
font-weight : 100;
|
||||||
|
color : #4b5055;
|
||||||
|
text-rendering : optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
.admin {
|
||||||
.admin{
|
nav {
|
||||||
|
|
||||||
header{
|
|
||||||
background-color : @red;
|
background-color : @red;
|
||||||
font-size: 2em;
|
.navItem{
|
||||||
padding : 20px 0px;
|
background-color : @red;
|
||||||
color : white;
|
}
|
||||||
margin-bottom: 30px;
|
.homebreweryLogo{
|
||||||
i{
|
font-family : CodeBold;
|
||||||
margin-right: 30px;
|
font-size : 12px;
|
||||||
|
color : white;
|
||||||
|
div{
|
||||||
|
margin-top : 2px;
|
||||||
|
margin-bottom : -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
font-size : 2em;
|
||||||
|
font-weight : 800;
|
||||||
|
border-bottom : 1px solid #ddd;
|
||||||
|
}
|
||||||
|
main.content{
|
||||||
|
width : 1000px;
|
||||||
|
margin : 0 auto;
|
||||||
|
padding : 50px 20px;
|
||||||
|
background-color : white;
|
||||||
|
.dangerZone{
|
||||||
|
margin : 30px 0px;
|
||||||
|
padding : 10px 20px;
|
||||||
|
background : repeating-linear-gradient(45deg, @yellow, @yellow 10px, darken(#333, 10%) 10px, darken(#333, 10%) 20px);
|
||||||
|
font-size : 1em;
|
||||||
|
font-weight : 800;
|
||||||
|
color : white;
|
||||||
|
text-transform : uppercase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
86
client/admin/adminSearch/adminSearch.jsx
Normal file
86
client/admin/adminSearch/adminSearch.jsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
const request = require('superagent');
|
||||||
|
|
||||||
|
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||||
|
|
||||||
|
const LIMIT = 10;
|
||||||
|
|
||||||
|
const AdminSearch = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
adminKey : '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
totalBrews : 1,
|
||||||
|
brews: [],
|
||||||
|
|
||||||
|
searching : false,
|
||||||
|
error : null,
|
||||||
|
|
||||||
|
page : 1,
|
||||||
|
searchTerms : ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSearch : function(e){
|
||||||
|
this.setState({
|
||||||
|
searchTerms : e.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handlePage : function(e){
|
||||||
|
this.setState({
|
||||||
|
page : e.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
search : function(){
|
||||||
|
this.setState({ searching : true, error : null });
|
||||||
|
|
||||||
|
request.get(`/api/brew`)
|
||||||
|
.query({
|
||||||
|
terms : this.state.searchTerms,
|
||||||
|
limit : LIMIT,
|
||||||
|
page : this.state.page - 1
|
||||||
|
})
|
||||||
|
.set('x-homebrew-admin', this.props.adminKey)
|
||||||
|
.end((err, res) => {
|
||||||
|
if(err){
|
||||||
|
this.setState({
|
||||||
|
searching : false,
|
||||||
|
error : err && err.toString()
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
this.setState({
|
||||||
|
brews : res.body.brews,
|
||||||
|
totalBrews : res.body.total
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(){
|
||||||
|
return <div className='adminSearch'>
|
||||||
|
<h1>Admin Search</h1>
|
||||||
|
<div className='controls'>
|
||||||
|
<input className='search' type='text' value={this.state.searchTerms} onChange={this.handleSearch} />
|
||||||
|
|
||||||
|
<button onClick={this.search}> <i className='fa fa-search' /> search </button>
|
||||||
|
|
||||||
|
|
||||||
|
<div className='page'>
|
||||||
|
page:
|
||||||
|
<input type='text' value={this.state.page} onChange={this.handlePage} />
|
||||||
|
/ {Math.ceil(this.state.totalBrews / LIMIT)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BrewTable brews={this.state.brews} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = AdminSearch;
|
||||||
17
client/admin/adminSearch/adminSearch.less
Normal file
17
client/admin/adminSearch/adminSearch.less
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
.adminSearch{
|
||||||
|
.controls{
|
||||||
|
margin-bottom : 20px;
|
||||||
|
input.search{
|
||||||
|
height : 33px;
|
||||||
|
padding : 10px;
|
||||||
|
}
|
||||||
|
.page {
|
||||||
|
float : right;
|
||||||
|
font-weight : 800;
|
||||||
|
input{
|
||||||
|
width : 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
client/admin/brewLookup/brewLookup.jsx
Normal file
86
client/admin/brewLookup/brewLookup.jsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||||
|
|
||||||
|
const BrewLookup = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
adminKey : '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
query:'',
|
||||||
|
resultBrew : null,
|
||||||
|
searching : false,
|
||||||
|
error : null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChange : function(e){
|
||||||
|
this.setState({
|
||||||
|
query : e.target.value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
lookup : function(){
|
||||||
|
this.setState({ searching : true, error : null });
|
||||||
|
|
||||||
|
request.get(`/admin/lookup/${this.state.query}`)
|
||||||
|
.set('x-homebrew-admin', this.props.adminKey)
|
||||||
|
.end((err, res) => {
|
||||||
|
this.setState({
|
||||||
|
searching : false,
|
||||||
|
error : err && err.toString(),
|
||||||
|
resultBrew : (err ? null : res.body)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
renderFoundBrew : function(){
|
||||||
|
if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>;
|
||||||
|
if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>;
|
||||||
|
|
||||||
|
return <BrewTable brews={[this.state.resultBrew ]} />
|
||||||
|
|
||||||
|
/*
|
||||||
|
const brew = this.state.resultBrew;
|
||||||
|
return <div className='brewRow'>
|
||||||
|
<div>{brew.title}</div>
|
||||||
|
<div>{brew.authors.join(', ')}</div>
|
||||||
|
<div><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></div>
|
||||||
|
<div><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></div>
|
||||||
|
<div>{Moment(brew.updatedAt).fromNow()}</div>
|
||||||
|
<div>{brew.views}</div>
|
||||||
|
<div>
|
||||||
|
<div className='deleteButton'>
|
||||||
|
<i className='fa fa-trash' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
|
||||||
|
renderError : function(){
|
||||||
|
if(!this.state.error) return;
|
||||||
|
|
||||||
|
return <div className='error'>
|
||||||
|
{this.state.error}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(){
|
||||||
|
return <div className='brewLookup'>
|
||||||
|
<h1>Brew Lookup</h1>
|
||||||
|
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id...' />
|
||||||
|
<button onClick={this.lookup}><i className='fa fa-search'/></button>
|
||||||
|
|
||||||
|
{this.renderFoundBrew()}
|
||||||
|
{this.renderError()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewLookup;
|
||||||
13
client/admin/brewLookup/brewLookup.less
Normal file
13
client/admin/brewLookup/brewLookup.less
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
.brewLookup{
|
||||||
|
height : 200px;
|
||||||
|
input{
|
||||||
|
height : 33px;
|
||||||
|
margin-bottom : 20px;
|
||||||
|
padding : 0px 10px;
|
||||||
|
}
|
||||||
|
.error{
|
||||||
|
font-weight : 800;
|
||||||
|
color : @red;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
client/admin/brewTable/brewTable.jsx
Normal file
54
client/admin/brewTable/brewTable.jsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Moment = require('moment');
|
||||||
|
|
||||||
|
//TODO: Add in delete
|
||||||
|
|
||||||
|
const BrewTable = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brews : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
renderRows : function(){
|
||||||
|
return _.map(this.props.brews, (brew) => {
|
||||||
|
let authors = 'None.';
|
||||||
|
if(brew.authors && brew.authors.length) authors = brew.authors.join(', ');
|
||||||
|
|
||||||
|
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}>
|
||||||
|
<td>{brew.title}</td>
|
||||||
|
<td>{authors}</td>
|
||||||
|
<td><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
|
||||||
|
<td><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
|
||||||
|
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
||||||
|
<td>{brew.views}</td>
|
||||||
|
<td className='deleteButton'>
|
||||||
|
<i className='fa fa-trash' />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
return <table className='brewTable'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Authors</th>
|
||||||
|
<th>Edit Link</th>
|
||||||
|
<th>Share Link</th>
|
||||||
|
<th>Last Updated</th>
|
||||||
|
<th>Views</th>
|
||||||
|
<th>Remove</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{this.renderRows()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewTable;
|
||||||
44
client/admin/brewTable/brewTable.less
Normal file
44
client/admin/brewTable/brewTable.less
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
table.brewTable{
|
||||||
|
th{
|
||||||
|
padding : 10px;
|
||||||
|
background-color : fade(@blue, 20%);
|
||||||
|
font-weight : 800;
|
||||||
|
}
|
||||||
|
tr:nth-child(even){
|
||||||
|
background-color : fade(@green, 10%);
|
||||||
|
}
|
||||||
|
tr.isEmpty{
|
||||||
|
background-color : fade(@red, 30%);
|
||||||
|
}
|
||||||
|
td{
|
||||||
|
min-width : 100px;
|
||||||
|
padding : 10px;
|
||||||
|
text-align : center;
|
||||||
|
|
||||||
|
/*
|
||||||
|
&.preview{
|
||||||
|
position : relative;
|
||||||
|
&:hover{
|
||||||
|
.content{
|
||||||
|
display : block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content{
|
||||||
|
position : absolute;
|
||||||
|
display : none;
|
||||||
|
top : 100%;
|
||||||
|
left : 0px;
|
||||||
|
z-index : 1000;
|
||||||
|
max-height : 500px;
|
||||||
|
width : 300px;
|
||||||
|
padding : 30px;
|
||||||
|
background-color : white;
|
||||||
|
font-family : monospace;
|
||||||
|
text-align : left;
|
||||||
|
pointer-events : none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
var request = require('superagent');
|
|
||||||
|
|
||||||
var Moment = require('moment');
|
|
||||||
|
|
||||||
var HomebrewAdmin = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
admin_key : ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
page: 0,
|
|
||||||
count : 20,
|
|
||||||
brewCache : {},
|
|
||||||
total : 0,
|
|
||||||
|
|
||||||
processingOldBrews : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
fetchBrews : function(page){
|
|
||||||
request.get('/homebrew/api/search')
|
|
||||||
.query({
|
|
||||||
admin_key : this.props.admin_key,
|
|
||||||
count : this.state.count,
|
|
||||||
page : page
|
|
||||||
})
|
|
||||||
.end((err, res)=>{
|
|
||||||
this.state.brewCache[page] = res.body.brews;
|
|
||||||
this.setState({
|
|
||||||
brewCache : this.state.brewCache,
|
|
||||||
total : res.body.total,
|
|
||||||
count : res.body.count
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this.fetchBrews(this.state.page);
|
|
||||||
},
|
|
||||||
|
|
||||||
changePageTo : function(page){
|
|
||||||
if(!this.state.brewCache[page]){
|
|
||||||
this.fetchBrews(page);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
page : page
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
clearInvalidBrews : function(){
|
|
||||||
request.get('/homebrew/api/invalid')
|
|
||||||
.query({admin_key : this.props.admin_key})
|
|
||||||
.end((err, res)=>{
|
|
||||||
if(!confirm("This will remove " + res.body.count + " brews. Are you sure?")) return;
|
|
||||||
request.get('/homebrew/api/invalid')
|
|
||||||
.query({admin_key : this.props.admin_key, do_it : true})
|
|
||||||
.end((err, res)=>{
|
|
||||||
alert("Done!")
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
deleteBrew : function(brewId){
|
|
||||||
if(!confirm("Are you sure you want to delete '" + brewId + "'?")) return;
|
|
||||||
request.get('/homebrew/api/remove/' + brewId)
|
|
||||||
.query({admin_key : this.props.admin_key})
|
|
||||||
.end(function(err, res){
|
|
||||||
window.location.reload();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
handlePageChange : function(dir){
|
|
||||||
this.changePageTo(this.state.page + dir);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderPagnination : function(){
|
|
||||||
var outOf;
|
|
||||||
if(this.state.total){
|
|
||||||
outOf = this.state.page + ' / ' + Math.round(this.state.total/this.state.count);
|
|
||||||
}
|
|
||||||
return <div className='pagnination'>
|
|
||||||
<i className='fa fa-chevron-left' onClick={this.handlePageChange.bind(this, -1)}/>
|
|
||||||
{outOf}
|
|
||||||
<i className='fa fa-chevron-right' onClick={this.handlePageChange.bind(this, 1)}/>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
renderBrews : function(){
|
|
||||||
var brews = this.state.brewCache[this.state.page] || _.times(this.state.count);
|
|
||||||
return _.map(brews, (brew)=>{
|
|
||||||
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.sharedId}>
|
|
||||||
<td><a href={'/homebrew/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
|
|
||||||
<td><a href={'/homebrew/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
|
|
||||||
<td>{Moment(brew.createdAt).fromNow()}</td>
|
|
||||||
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
|
||||||
<td>{Moment(brew.lastViewed).fromNow()}</td>
|
|
||||||
<td>{brew.views}</td>
|
|
||||||
<td>
|
|
||||||
<div className='deleteButton' onClick={this.deleteBrew.bind(this, brew.editId)}>
|
|
||||||
<i className='fa fa-trash' />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
renderBrewTable : function(){
|
|
||||||
return <div className='brewTable'>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Edit Id</th>
|
|
||||||
<th>Share Id</th>
|
|
||||||
<th>Created At</th>
|
|
||||||
<th>Last Updated</th>
|
|
||||||
<th>Last Viewed</th>
|
|
||||||
<th>Views</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.renderBrews()}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
var self = this;
|
|
||||||
return <div className='homebrewAdmin'>
|
|
||||||
<h2>
|
|
||||||
Homebrews - {this.state.total}
|
|
||||||
</h2>
|
|
||||||
{this.renderPagnination()}
|
|
||||||
{this.renderBrewTable()}
|
|
||||||
|
|
||||||
<button className='clearOldButton' onClick={this.clearInvalidBrews}>
|
|
||||||
Clear Old
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = HomebrewAdmin;
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
|
|
||||||
.homebrewAdmin{
|
|
||||||
margin-bottom: 80px;
|
|
||||||
.brewTable{
|
|
||||||
table{
|
|
||||||
|
|
||||||
th{
|
|
||||||
padding : 10px;
|
|
||||||
font-weight : 800;
|
|
||||||
}
|
|
||||||
tr:nth-child(even){
|
|
||||||
background-color : fade(@green, 10%);
|
|
||||||
}
|
|
||||||
tr.isEmpty{
|
|
||||||
background-color : fade(@red, 30%);
|
|
||||||
}
|
|
||||||
td{
|
|
||||||
min-width : 100px;
|
|
||||||
padding : 10px;
|
|
||||||
text-align : center;
|
|
||||||
|
|
||||||
&.preview{
|
|
||||||
position : relative;
|
|
||||||
&:hover{
|
|
||||||
.content{
|
|
||||||
display : block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content{
|
|
||||||
position : absolute;
|
|
||||||
display : none;
|
|
||||||
top : 100%;
|
|
||||||
left : 0px;
|
|
||||||
z-index : 1000;
|
|
||||||
max-height : 500px;
|
|
||||||
width : 300px;
|
|
||||||
padding : 30px;
|
|
||||||
background-color : white;
|
|
||||||
font-family : monospace;
|
|
||||||
text-align : left;
|
|
||||||
pointer-events : none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.deleteButton{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button.clearOldButton{
|
|
||||||
float : right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
client/admin/invalidBrew/invalidBrew.jsx
Normal file
54
client/admin/invalidBrew/invalidBrew.jsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||||
|
|
||||||
|
|
||||||
|
const InvalidBrew = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
adminKey : '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
brews: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInvalid : function(){
|
||||||
|
request.get(`/admin/invalid`)
|
||||||
|
.set('x-homebrew-admin', this.props.adminKey)
|
||||||
|
.end((err, res) => {
|
||||||
|
this.setState({
|
||||||
|
brews : res.body
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeInvalid : function(){
|
||||||
|
if(!this.state.brews.length) return;
|
||||||
|
if(!confirm(`Are you sure you want to remove ${this.state.brews.length} brews`)) return;
|
||||||
|
if(!confirm('Sure you are sure?')) return;
|
||||||
|
|
||||||
|
request.delete(`/admin/invalid`)
|
||||||
|
.set('x-homebrew-admin', this.props.adminKey)
|
||||||
|
.end((err, res) => {
|
||||||
|
console.log(err, res.body);
|
||||||
|
alert('Invalid brews removed!');
|
||||||
|
this.getInvalid();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function(){
|
||||||
|
return <div className='invalidBrew'>
|
||||||
|
<h1>Remove Invalid Brews</h1>
|
||||||
|
<div>This will removes all brews older than 3 days and shorter than a tweet.</div>
|
||||||
|
<button className='get' onClick={this.getInvalid}> Get Invalid Brews</button>
|
||||||
|
<button className='remove' disabled={this.state.brews.length == 0} onClick={this.removeInvalid}> Remove invalid Brews</button>
|
||||||
|
|
||||||
|
<BrewTable brews={this.state.brews} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = InvalidBrew;
|
||||||
5
client/admin/invalidBrew/invalidBrew.less
Normal file
5
client/admin/invalidBrew/invalidBrew.less
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.invalidBrew{
|
||||||
|
button{
|
||||||
|
margin: 10px 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var Markdown = require('marked');
|
|
||||||
|
|
||||||
var PAGE_HEIGHT = 1056 + 30;
|
|
||||||
|
|
||||||
var BrewRenderer = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
text : ''
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
viewablePageNumber: 0,
|
|
||||||
height : 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
totalPages : 0,
|
|
||||||
height : 0,
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this.setState({
|
|
||||||
height : this.refs.main.parentNode.clientHeight
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleScroll : function(e){
|
|
||||||
this.setState({
|
|
||||||
viewablePageNumber : Math.floor(e.target.scrollTop / PAGE_HEIGHT)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//Implement later
|
|
||||||
scrollToPage : function(pageNumber){
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldRender : function(pageText, index){
|
|
||||||
var viewIndex = this.state.viewablePageNumber;
|
|
||||||
if(index == viewIndex - 1) return true;
|
|
||||||
if(index == viewIndex) return true;
|
|
||||||
if(index == viewIndex + 1) return true;
|
|
||||||
|
|
||||||
//Check for style tages
|
|
||||||
if(pageText.indexOf('<style>') !== -1) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderPageInfo : function(){
|
|
||||||
return <div className='pageInfo'>
|
|
||||||
{this.state.viewablePageNumber + 1} / {this.totalPages}
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
renderDummyPage : function(key){
|
|
||||||
return <div className='phb' key={key}>
|
|
||||||
<i className='fa fa-spinner fa-spin' />
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
renderPage : function(pageText, index){
|
|
||||||
return <div className='phb' dangerouslySetInnerHTML={{__html:Markdown(pageText)}} key={index} />
|
|
||||||
},
|
|
||||||
|
|
||||||
renderPages : function(){
|
|
||||||
var pages = this.props.text.split('\\page');
|
|
||||||
this.totalPages = pages.length;
|
|
||||||
|
|
||||||
return _.map(pages, (page, index)=>{
|
|
||||||
if(this.shouldRender(page, index)){
|
|
||||||
return this.renderPage(page, index);
|
|
||||||
}else{
|
|
||||||
return this.renderDummyPage(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='brewRenderer'
|
|
||||||
onScroll={this.handleScroll}
|
|
||||||
ref='main'
|
|
||||||
style={{height : this.state.height}}>
|
|
||||||
|
|
||||||
<div className='pages'>
|
|
||||||
{this.renderPages()}
|
|
||||||
</div>
|
|
||||||
{this.renderPageInfo()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = BrewRenderer;
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
|
||||||
var Snippets = require('./snippets/snippets.js');
|
|
||||||
|
|
||||||
|
|
||||||
var splice = function(str, index, inject){
|
|
||||||
return str.slice(0, index) + inject + str.slice(index);
|
|
||||||
};
|
|
||||||
var execute = function(val){
|
|
||||||
if(_.isFunction(val)) return val();
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var Editor = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
value : "",
|
|
||||||
onChange : function(){}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
cursorPosition : {
|
|
||||||
line : 0,
|
|
||||||
ch : 0
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
var paneHeight = this.refs.main.parentNode.clientHeight;
|
|
||||||
paneHeight -= this.refs.snippetBar.clientHeight + 1;
|
|
||||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.props.onChange(text);
|
|
||||||
},
|
|
||||||
handleCursorActivty : function(curpos){
|
|
||||||
this.cursorPosition = curpos;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSnippetClick : function(injectText){
|
|
||||||
var lines = this.props.value.split('\n');
|
|
||||||
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
|
|
||||||
|
|
||||||
this.handleTextChange(lines.join('\n'));
|
|
||||||
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
|
|
||||||
},
|
|
||||||
|
|
||||||
//Called when there are changes to the editor's dimensions
|
|
||||||
update : function(){
|
|
||||||
this.refs.codeEditor.updateSize();
|
|
||||||
},
|
|
||||||
|
|
||||||
renderSnippetGroups : function(){
|
|
||||||
return _.map(Snippets, (snippetGroup)=>{
|
|
||||||
return <SnippetGroup
|
|
||||||
groupName={snippetGroup.groupName}
|
|
||||||
icon={snippetGroup.icon}
|
|
||||||
snippets={snippetGroup.snippets}
|
|
||||||
key={snippetGroup.groupName}
|
|
||||||
onSnippetClick={this.handleSnippetClick}
|
|
||||||
/>
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return(
|
|
||||||
<div className='editor' ref='main'>
|
|
||||||
<div className='snippetBar' ref='snippetBar'>
|
|
||||||
{this.renderSnippetGroups()}
|
|
||||||
</div>
|
|
||||||
<CodeEditor
|
|
||||||
ref='codeEditor'
|
|
||||||
wrap={true}
|
|
||||||
language='gfm'
|
|
||||||
value={this.props.value}
|
|
||||||
onChange={this.handleTextChange}
|
|
||||||
onCursorActivity={this.handleCursorActivty} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Editor;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var SnippetGroup = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
groupName : '',
|
|
||||||
icon : 'fa-rocket',
|
|
||||||
snippets : [],
|
|
||||||
onSnippetClick : function(){},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleSnippetClick : function(snippet){
|
|
||||||
this.props.onSnippetClick(execute(snippet.gen));
|
|
||||||
},
|
|
||||||
renderSnippets : function(){
|
|
||||||
return _.map(this.props.snippets, (snippet)=>{
|
|
||||||
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
|
|
||||||
<i className={'fa fa-fw ' + snippet.icon} />
|
|
||||||
{snippet.name}
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='snippetGroup'>
|
|
||||||
<div className='text'>
|
|
||||||
<i className={'fa fa-fw ' + this.props.icon} />
|
|
||||||
<span className='groupName'>{this.props.groupName}</span>
|
|
||||||
</div>
|
|
||||||
<div className='dropdown'>
|
|
||||||
{this.renderSnippets()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
|
|
||||||
.editor{
|
|
||||||
position : relative;
|
|
||||||
width : 100%;
|
|
||||||
.snippetBar{
|
|
||||||
display : flex;
|
|
||||||
padding : 5px;
|
|
||||||
background-color : #ddd;
|
|
||||||
align-items : center;
|
|
||||||
.snippetGroup{
|
|
||||||
.animate(background-color);
|
|
||||||
margin : 0px 8px;
|
|
||||||
padding : 3px;
|
|
||||||
font-size : 13px;
|
|
||||||
border-radius : 5px;
|
|
||||||
&:hover, &.selected{
|
|
||||||
background-color : #999;
|
|
||||||
}
|
|
||||||
.text{
|
|
||||||
line-height : 20px;
|
|
||||||
.groupName{
|
|
||||||
margin-left : 6px;
|
|
||||||
font-size : 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover{
|
|
||||||
.dropdown{
|
|
||||||
visibility : visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dropdown{
|
|
||||||
position : absolute;
|
|
||||||
visibility : hidden;
|
|
||||||
z-index : 1000;
|
|
||||||
padding : 5px;
|
|
||||||
background-color : #ddd;
|
|
||||||
.snippet{
|
|
||||||
.animate(background-color);
|
|
||||||
padding : 10px;
|
|
||||||
cursor : pointer;
|
|
||||||
font-size : 10px;
|
|
||||||
i{
|
|
||||||
margin-right: 8px;
|
|
||||||
font-size : 13px;
|
|
||||||
}
|
|
||||||
&:hover{
|
|
||||||
background-color : #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.codeEditor{
|
|
||||||
height : 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
module.exports = function(){
|
|
||||||
|
|
||||||
|
|
||||||
var spellNames = [
|
|
||||||
"Astral Rite of Acne",
|
|
||||||
"Create Acne",
|
|
||||||
"Cursed Ramen Erruption",
|
|
||||||
"Dark Chant of the Dentists",
|
|
||||||
"Erruption of Immaturity",
|
|
||||||
"Flaming Disc of Inconvenience",
|
|
||||||
"Heal Bad Hygene",
|
|
||||||
"Heavenly Transfiguration of the Cream Devil",
|
|
||||||
"Hellish Cage of Mucus",
|
|
||||||
"Irritate Peanut Butter Fairy",
|
|
||||||
"Luminous Erruption of Tea",
|
|
||||||
"Mystic Spell of the Poser",
|
|
||||||
"Sorcerous Enchantment of the Chimneysweep",
|
|
||||||
"Steak Sauce Ray",
|
|
||||||
"Talk to Groupie",
|
|
||||||
"Astonishing Chant of Chocolate",
|
|
||||||
"Astounding Pasta Puddle",
|
|
||||||
"Ball of Annoyance",
|
|
||||||
"Cage of Yarn",
|
|
||||||
"Control Noodles Elemental",
|
|
||||||
"Create Nervousness",
|
|
||||||
"Cure Baldness",
|
|
||||||
"Cursed Ritual of Bad Hair",
|
|
||||||
"Dispell Piles in Dentist",
|
|
||||||
"Eliminate Florists",
|
|
||||||
"Illusionary Transfiguration of the Babysitter",
|
|
||||||
"Necromantic Armor of Salad Dressing",
|
|
||||||
"Occult Transfiguration of Foot Fetish",
|
|
||||||
"Protection from Mucus Giant",
|
|
||||||
"Tinsel Blast",
|
|
||||||
"Alchemical Evocation of the Goths",
|
|
||||||
"Call Fangirl",
|
|
||||||
"Divine Spell of Crossdressing",
|
|
||||||
"Dominate Ramen Giant",
|
|
||||||
"Eliminate Vindictiveness in Gym Teacher",
|
|
||||||
"Extra-Planar Spell of Irritation",
|
|
||||||
"Induce Whining in Babysitter",
|
|
||||||
"Invoke Complaining",
|
|
||||||
"Magical Enchantment of Arrogance",
|
|
||||||
"Occult Globe of Salad Dressing",
|
|
||||||
"Overwhelming Enchantment of the Chocolate Fairy",
|
|
||||||
"Sorcerous Dandruff Globe",
|
|
||||||
"Spiritual Invocation of the Costumers",
|
|
||||||
"Ultimate Rite of the Confetti Angel",
|
|
||||||
"Ultimate Ritual of Mouthwash",
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
var level = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th"];
|
|
||||||
var spellSchools = ["abjuration", "conjuration", "divination", "enchantment", "evocation", "illusion", "necromancy", "transmutation"];
|
|
||||||
|
|
||||||
|
|
||||||
var components = _.sampleSize(["V", "S", "M"], _.random(1,3)).join(', ');
|
|
||||||
if(components.indexOf("M") !== -1){
|
|
||||||
components += " (" + _.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1,3)).join(', ') + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
"#### " + _.sample(spellNames),
|
|
||||||
"*" + _.sample(level) + "-level " + _.sample(spellSchools) + "*",
|
|
||||||
"___",
|
|
||||||
"- **Casting Time:** 1 action",
|
|
||||||
"- **Range:** " + _.sample(["Self", "Touch", "30 feet", "60 feet"]),
|
|
||||||
"- **Components:** " + components,
|
|
||||||
"- **Duration:** " + _.sample(["Until dispelled", "1 round", "Instantaneous", "Concentration, up to 10 minutes", "1 hour"]),
|
|
||||||
"",
|
|
||||||
"A flame, equivalent in brightness to a torch, springs from from an object that you touch. ",
|
|
||||||
"The effect look like a regular flame, but it creates no heat and doesn't use oxygen. ",
|
|
||||||
"A *continual flame* can be covered or hidden but not smothered or quenched.",
|
|
||||||
"\n\n\n"
|
|
||||||
].join('\n');
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,55 +1,66 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
var CreateRouter = require('pico-router').createRouter;
|
const CreateRouter = require('pico-router').createRouter;
|
||||||
|
const BrewActions = require('homebrewery/brew.actions.js');
|
||||||
|
const AccountActions = require('homebrewery/account.actions.js');
|
||||||
|
|
||||||
var HomePage = require('./pages/homePage/homePage.jsx');
|
const HomePage = require('./pages/homePage/homePage.jsx');
|
||||||
var EditPage = require('./pages/editPage/editPage.jsx');
|
const EditPage = require('./pages/editPage/editPage.jsx');
|
||||||
var SharePage = require('./pages/sharePage/sharePage.jsx');
|
const UserPage = require('./pages/userPage/userPage.jsx');
|
||||||
var NewPage = require('./pages/newPage/newPage.jsx');
|
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||||
|
const NewPage = require('./pages/newPage/newPage.jsx');
|
||||||
|
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||||
|
const PrintPage = require('./pages/printPage/printPage.jsx');
|
||||||
|
|
||||||
var Router;
|
let Router;
|
||||||
var Homebrew = React.createClass({
|
const Homebrew = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
url : "",
|
url : '',
|
||||||
welcomeText : "",
|
version : '0.0.0',
|
||||||
changelog : "",
|
loginPath : '',
|
||||||
brew : {
|
|
||||||
title : '',
|
user : undefined,
|
||||||
text : '',
|
brew : undefined,
|
||||||
shareId : null,
|
brews : []
|
||||||
editId : null,
|
|
||||||
createdAt : null,
|
|
||||||
updatedAt : null,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
Router = CreateRouter({
|
BrewActions.init({
|
||||||
'/homebrew/edit/:id' : (args) => {
|
version : this.props.version,
|
||||||
return <EditPage id={args.id} brew={this.props.brew} />
|
brew : this.props.brew
|
||||||
},
|
});
|
||||||
|
AccountActions.init({
|
||||||
|
user : this.props.user,
|
||||||
|
loginPath : this.props.loginPath
|
||||||
|
});
|
||||||
|
|
||||||
'/homebrew/share/:id' : (args) => {
|
Router = CreateRouter({
|
||||||
return <SharePage id={args.id} brew={this.props.brew} />
|
'/edit/:id' : <EditPage />,
|
||||||
|
'/share/:id' : <SharePage />,
|
||||||
|
'/user/:username' : (args) => {
|
||||||
|
return <UserPage
|
||||||
|
username={args.username}
|
||||||
|
brews={this.props.brews}
|
||||||
|
/>
|
||||||
},
|
},
|
||||||
'/homebrew/changelog' : (args) => {
|
'/print/:id' : (args, query) => {
|
||||||
return <SharePage brew={{title : 'Changelog', text : this.props.changelog}} />
|
return <PrintPage brew={this.props.brew} query={query}/>;
|
||||||
},
|
},
|
||||||
'/homebrew/new' : (args) => {
|
'/print' : (args, query) => {
|
||||||
return <NewPage />
|
return <PrintPage query={query}/>;
|
||||||
},
|
},
|
||||||
'/homebrew*' : <HomePage welcomeText={this.props.welcomeText} />,
|
'/new' : <NewPage />,
|
||||||
|
'/changelog' : <SharePage />,
|
||||||
|
'*' : <HomePage />,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render : function(){
|
render : function(){
|
||||||
return(
|
return <div className='homebrew'>
|
||||||
<div className='homebrew'>
|
<Router initialUrl={this.props.url}/>
|
||||||
<Router initialUrl={this.props.url}/>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
|
|
||||||
@import 'naturalcrit/styles/core.less';
|
@import 'naturalcrit/styles/core.less';
|
||||||
.homebrew{
|
.homebrew{
|
||||||
height : 100%;
|
height : 100%;
|
||||||
|
|
||||||
//TODO: Consider making backgroudn color lighter
|
|
||||||
background-color : @steel;
|
|
||||||
.page{
|
.page{
|
||||||
display : flex;
|
display : flex;
|
||||||
height : 100%;
|
height : 100%;
|
||||||
flex-direction : column;
|
background-color : @steel;
|
||||||
|
flex-direction : column;
|
||||||
.content{
|
.content{
|
||||||
position : relative;
|
position : relative;
|
||||||
height : calc(~"100% - 29px"); //Navbar height
|
height : calc(~"100% - 29px"); //Navbar height
|
||||||
|
|||||||
23
client/homebrew/navbar/account.navitem.jsx
Normal file
23
client/homebrew/navbar/account.navitem.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
const Store = require('homebrewery/account.store.js');
|
||||||
|
const Actions = require('homebrewery/account.actions.js');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = function(props){
|
||||||
|
const user = Store.getUser();
|
||||||
|
if(user && user == props.userPage){
|
||||||
|
return <Nav.item onClick={Actions.logout} color='yellow' icon='fa-user-times'>
|
||||||
|
logout
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
if(user){
|
||||||
|
return <Nav.item href={`/user/${user}`} color='yellow' icon='fa-user'>
|
||||||
|
{user}
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
return <Nav.item onClick={Actions.login} color='teal' icon='fa-sign-in'>
|
||||||
|
login
|
||||||
|
</Nav.item>
|
||||||
|
};
|
||||||
9
client/homebrew/navbar/brewTitle.navitem.jsx
Normal file
9
client/homebrew/navbar/brewTitle.navitem.jsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent((props) => {
|
||||||
|
return <Nav.item className='brewTitle'>{props.title}</Nav.item>
|
||||||
|
}, (props) => {
|
||||||
|
return {title : Store.getMetaData().title};
|
||||||
|
})
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
const flux = require('pico-flux')
|
||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
|
||||||
|
const onStoreChange = () => {
|
||||||
|
return {
|
||||||
|
status : Store.getStatus(),
|
||||||
|
errors : Store.getErrors()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContinousSave = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
status : 'ready',
|
||||||
|
errors : undefined
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
flux.actionEmitter.on('dispatch', this.actionHandler);
|
||||||
|
window.onbeforeunload = ()=>{
|
||||||
|
if(this.props.status !== 'ready') return 'You have unsaved changes!';
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
flux.actionEmitter.removeListener('dispatch', this.actionHandler);
|
||||||
|
window.onbeforeunload = function(){};
|
||||||
|
},
|
||||||
|
actionHandler : function(actionType){
|
||||||
|
if(actionType == 'UPDATE_BREW_TEXT' || actionType == 'UPDATE_META'){
|
||||||
|
Actions.pendingSave();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClick : function(){
|
||||||
|
Actions.save();
|
||||||
|
},
|
||||||
|
renderError : function(){
|
||||||
|
let errMsg = '';
|
||||||
|
try{
|
||||||
|
errMsg += this.state.errors.toString() + '\n\n';
|
||||||
|
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
||||||
|
}catch(e){}
|
||||||
|
|
||||||
|
return <Nav.item className='continousSave error' icon="fa-warning">
|
||||||
|
Oops!
|
||||||
|
<div className='errorContainer'>
|
||||||
|
Looks like there was a problem saving. <br />
|
||||||
|
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
||||||
|
here
|
||||||
|
</a>.
|
||||||
|
</div>
|
||||||
|
</Nav.item>
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
if(this.props.status == 'error') return this.renderError();
|
||||||
|
|
||||||
|
if(this.props.status == 'saving'){
|
||||||
|
return <Nav.item className='continousSave' icon="fa-spinner fa-spin">saving...</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status == 'pending'){
|
||||||
|
return <Nav.item className='continousSave' onClick={this.handleClick} color='blue' icon='fa-save'>Save Now</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status == 'ready'){
|
||||||
|
return <Nav.item className='continousSave saved'>saved.</Nav.item>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent(ContinousSave, onStoreChange);
|
||||||
76
client/homebrew/navbar/continousSave.navitem.jsx
Normal file
76
client/homebrew/navbar/continousSave.navitem.jsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
const flux = require('pico-flux')
|
||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
|
||||||
|
const onStoreChange = () => {
|
||||||
|
return {
|
||||||
|
status : Store.getStatus(),
|
||||||
|
errors : Store.getErrors()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ContinousSave = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
status : 'ready',
|
||||||
|
errors : undefined
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
flux.actionEmitter.on('dispatch', this.actionHandler);
|
||||||
|
window.onbeforeunload = ()=>{
|
||||||
|
if(this.props.status !== 'ready') return 'You have unsaved changes!';
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
flux.actionEmitter.removeListener('dispatch', this.actionHandler);
|
||||||
|
window.onbeforeunload = function(){};
|
||||||
|
},
|
||||||
|
actionHandler : function(actionType){
|
||||||
|
if(actionType == 'UPDATE_BREW_TEXT' || actionType == 'UPDATE_META'){
|
||||||
|
Actions.pendingSave();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClick : function(){
|
||||||
|
Actions.save();
|
||||||
|
},
|
||||||
|
renderError : function(){
|
||||||
|
let errMsg = '';
|
||||||
|
try{
|
||||||
|
errMsg += this.state.errors.toString() + '\n\n';
|
||||||
|
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
||||||
|
}catch(e){}
|
||||||
|
|
||||||
|
return <Nav.item className='continousSave error' icon="fa-warning">
|
||||||
|
Oops!
|
||||||
|
<div className='errorContainer'>
|
||||||
|
Looks like there was a problem saving. <br />
|
||||||
|
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
||||||
|
here
|
||||||
|
</a>.
|
||||||
|
</div>
|
||||||
|
</Nav.item>
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
if(this.props.status == 'error') return this.renderError();
|
||||||
|
|
||||||
|
if(this.props.status == 'saving'){
|
||||||
|
return <Nav.item className='continousSave' icon="fa-spinner fa-spin">saving...</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status == 'pending'){
|
||||||
|
return <Nav.item className='continousSave' onClick={this.handleClick} color='blue' icon='fa-save'>Save Now</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status == 'ready'){
|
||||||
|
return <Nav.item className='continousSave saved'>saved.</Nav.item>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent(ContinousSave, onStoreChange);
|
||||||
@@ -2,7 +2,7 @@ var React = require('react');
|
|||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
|
return <Nav.item newTab={true} href='https://github.com/stolksdorf/homebrewery/issues' color='red' icon='fa-bug'>
|
||||||
report issue
|
report issue
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
};
|
};
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
|
||||||
var Navbar = React.createClass({
|
const Navbar = React.createClass({
|
||||||
render : function(){
|
render : function(){
|
||||||
return <Nav.base>
|
return <Nav.base>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<Nav.logo />
|
<Nav.logo />
|
||||||
<Nav.item href='/homebrew' className='homebrewLogo'>
|
<Nav.item href='/' className='homebrewLogo'>
|
||||||
<div>The Homebrewery</div>
|
<div>The Homebrewery</div>
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<Nav.item>v2.0.0</Nav.item>
|
<Nav.item>{`v${Store.getVersion()}`}</Nav.item>
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</Nav.base>
|
</Nav.base>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
@navbarHeight : 28px;
|
||||||
.homebrew nav{
|
.homebrew nav{
|
||||||
.homebrewLogo{
|
.homebrewLogo{
|
||||||
.animate(color);
|
.animate(color);
|
||||||
@@ -13,38 +13,12 @@
|
|||||||
color : @blue;
|
color : @blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.editTitle.navItem{
|
|
||||||
padding : 2px 12px;
|
|
||||||
input{
|
|
||||||
margin : 0;
|
|
||||||
padding : 2px;
|
|
||||||
width : 250px;
|
|
||||||
background-color : #444;
|
|
||||||
font-family : 'Open Sans', sans-serif;
|
|
||||||
font-size : 12px;
|
|
||||||
font-weight : 800;
|
|
||||||
color : white;
|
|
||||||
text-align : center;
|
|
||||||
border : 1px solid @blue;
|
|
||||||
outline : none;
|
|
||||||
}
|
|
||||||
.charCount{
|
|
||||||
display : inline-block;
|
|
||||||
vertical-align : bottom;
|
|
||||||
margin-left : 8px;
|
|
||||||
text-align : right;
|
|
||||||
color : #666;
|
|
||||||
&.max{
|
|
||||||
color : @red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.brewTitle.navItem{
|
.brewTitle.navItem{
|
||||||
font-size : 12px;
|
font-size : 12px;
|
||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
color : white;
|
color : white;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
text-transform: initial;
|
text-transform : initial;
|
||||||
}
|
}
|
||||||
.patreon.navItem{
|
.patreon.navItem{
|
||||||
i{
|
i{
|
||||||
@@ -54,5 +28,105 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.recent.navItem{
|
||||||
|
position : relative;
|
||||||
|
.dropdown{
|
||||||
|
position : absolute;
|
||||||
|
top : 28px;
|
||||||
|
left : 0px;
|
||||||
|
z-index : 10000;
|
||||||
|
width : 100%;
|
||||||
|
h4{
|
||||||
|
display : block;
|
||||||
|
box-sizing : border-box;
|
||||||
|
padding : 5px 0px;
|
||||||
|
background-color : #333;
|
||||||
|
font-size : 0.8em;
|
||||||
|
color : #bbb;
|
||||||
|
text-align : center;
|
||||||
|
border-top : 1px solid #888;
|
||||||
|
&:nth-of-type(1){ background-color: darken(@teal, 20%); }
|
||||||
|
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
|
||||||
|
}
|
||||||
|
.item{
|
||||||
|
.animate(background-color);
|
||||||
|
position : relative;
|
||||||
|
display : block;
|
||||||
|
box-sizing : border-box;
|
||||||
|
padding : 13px 5px;
|
||||||
|
background-color : #333;
|
||||||
|
color : white;
|
||||||
|
text-decoration : none;
|
||||||
|
border-top : 1px solid #888;
|
||||||
|
&:hover{
|
||||||
|
background-color : @blue;
|
||||||
|
}
|
||||||
|
.title{
|
||||||
|
display : inline-block;
|
||||||
|
overflow : hidden;
|
||||||
|
width : 100%;
|
||||||
|
text-overflow : ellipsis;
|
||||||
|
white-space : nowrap;
|
||||||
|
}
|
||||||
|
.time{
|
||||||
|
position : absolute;
|
||||||
|
right : 2px;
|
||||||
|
bottom : 2px;
|
||||||
|
font-size : 0.7em;
|
||||||
|
color : #888;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.warning.navItem{
|
||||||
|
position : relative;
|
||||||
|
background-color : @orange;
|
||||||
|
color : white;
|
||||||
|
&:hover>.dropdown{
|
||||||
|
visibility : visible;
|
||||||
|
}
|
||||||
|
.dropdown{
|
||||||
|
position : absolute;
|
||||||
|
display : block;
|
||||||
|
top : 28px;
|
||||||
|
left : 0px;
|
||||||
|
visibility : hidden;
|
||||||
|
z-index : 10000;
|
||||||
|
box-sizing : border-box;
|
||||||
|
width : 100%;
|
||||||
|
padding : 13px 5px;
|
||||||
|
background-color : #333;
|
||||||
|
text-align : center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.staticSave.navItem{
|
||||||
|
background-color : @orange;
|
||||||
|
&:hover{
|
||||||
|
background-color : @green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.continousSave.navItem{
|
||||||
|
width : 105px;
|
||||||
|
text-align : center;
|
||||||
|
&.saved{
|
||||||
|
cursor : initial;
|
||||||
|
color : #666;
|
||||||
|
}
|
||||||
|
&.error{
|
||||||
|
position : relative;
|
||||||
|
background-color : @red;
|
||||||
|
.errorContainer{
|
||||||
|
position : absolute;
|
||||||
|
top : 29px;
|
||||||
|
left : -20px;
|
||||||
|
z-index : 1000;
|
||||||
|
width : 120px;
|
||||||
|
padding : 8px;
|
||||||
|
background-color : #333;
|
||||||
|
a{
|
||||||
|
color : @teal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
10
client/homebrew/navbar/navitems.js
Normal file
10
client/homebrew/navbar/navitems.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
Account : require('./account.navitem.jsx'),
|
||||||
|
BrewTitle : require('./brewTitle.navitem.jsx'),
|
||||||
|
ContinousSave : require('./continousSave.navitem.jsx'),
|
||||||
|
Issue : require('./issue.navitem.jsx'),
|
||||||
|
Patreon : require('./patreon.navitem.jsx'),
|
||||||
|
Print : require('./print.navitem.jsx'),
|
||||||
|
Recent : require('./recent.navitem.jsx'),
|
||||||
|
StaticSave : require('./staticSave.navitem.jsx'),
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@ var React = require('react');
|
|||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <Nav.item newTab={true} href={'/homebrew/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-print'>
|
return <Nav.item newTab={true} href={'/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-file-pdf-o'>
|
||||||
print
|
get PDF
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
};
|
};
|
||||||
199
client/homebrew/navbar/recent.navitem.jsx
Normal file
199
client/homebrew/navbar/recent.navitem.jsx
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
var Moment = require('moment');
|
||||||
|
|
||||||
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
const VIEW_KEY = 'homebrewery-recently-viewed';
|
||||||
|
const EDIT_KEY = 'homebrewery-recently-edited';
|
||||||
|
|
||||||
|
var BaseItem = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
storageKey : '',
|
||||||
|
text : '',
|
||||||
|
currentBrew:{
|
||||||
|
title : '',
|
||||||
|
id : '',
|
||||||
|
url : ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
showDropdown: false,
|
||||||
|
brews : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
var brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
|
||||||
|
|
||||||
|
brews = _.filter(brews, (brew)=>{
|
||||||
|
return brew.id !== this.props.currentBrew.id;
|
||||||
|
});
|
||||||
|
if(this.props.currentBrew.id){
|
||||||
|
brews.unshift({
|
||||||
|
id : this.props.currentBrew.id,
|
||||||
|
url : this.props.currentBrew.url,
|
||||||
|
title : this.props.currentBrew.title,
|
||||||
|
ts : Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
brews = _.slice(brews, 0, 8);
|
||||||
|
localStorage.setItem(this.props.storageKey, JSON.stringify(brews));
|
||||||
|
this.setState({
|
||||||
|
brews : brews
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDropdown : function(show){
|
||||||
|
this.setState({
|
||||||
|
showDropdown : show
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDropdown : function(){
|
||||||
|
if(!this.state.showDropdown) return null;
|
||||||
|
|
||||||
|
var items = _.map(this.state.brews, (brew)=>{
|
||||||
|
return <a href={brew.url} className='item' key={brew.id} target='_blank'>
|
||||||
|
<span className='title'>{brew.title}</span>
|
||||||
|
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||||
|
</a>
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div className='dropdown'>{items}</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||||
|
onMouseEnter={this.handleDropdown.bind(null, true)}
|
||||||
|
onMouseLeave={this.handleDropdown.bind(null, false)}>
|
||||||
|
{this.props.text}
|
||||||
|
{this.renderDropdown()}
|
||||||
|
</Nav.item>
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
viewed : React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : {
|
||||||
|
title : '',
|
||||||
|
shareId : ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
return <BaseItem text='recently viewed' storageKey={VIEW_KEY}
|
||||||
|
currentBrew={{
|
||||||
|
id : this.props.brew.shareId,
|
||||||
|
title : this.props.brew.title,
|
||||||
|
url : `/share/${this.props.brew.shareId}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
edited : React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : {
|
||||||
|
title : '',
|
||||||
|
editId : ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
return <BaseItem text='recently edited' storageKey={EDIT_KEY}
|
||||||
|
currentBrew={{
|
||||||
|
id : this.props.brew.editId,
|
||||||
|
title : this.props.brew.title,
|
||||||
|
url : `/edit/${this.props.brew.editId}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
both : React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
errorId : null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
showDropdown: false,
|
||||||
|
edit : [],
|
||||||
|
view : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
|
||||||
|
var edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
||||||
|
var viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
|
||||||
|
|
||||||
|
if(this.props.errorId){
|
||||||
|
edited = _.filter(edited, (edit) => {
|
||||||
|
return edit.id !== this.props.errorId;
|
||||||
|
});
|
||||||
|
viewed = _.filter(viewed, (view) => {
|
||||||
|
return view.id !== this.props.errorId;
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem(EDIT_KEY, JSON.stringify(edited));
|
||||||
|
localStorage.setItem(VIEW_KEY, JSON.stringify(viewed));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
edit : edited,
|
||||||
|
view : viewed
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDropdown : function(show){
|
||||||
|
this.setState({
|
||||||
|
showDropdown : show
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDropdown : function(){
|
||||||
|
if(!this.state.showDropdown) return null;
|
||||||
|
|
||||||
|
var makeItems = (brews) => {
|
||||||
|
return _.map(brews, (brew)=>{
|
||||||
|
return <a href={brew.url} className='item' key={brew.id} target='_blank'>
|
||||||
|
<span className='title'>{brew.title}</span>
|
||||||
|
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||||
|
</a>
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className='dropdown'>
|
||||||
|
<h4>edited</h4>
|
||||||
|
{makeItems(this.state.edit)}
|
||||||
|
<h4>viewed</h4>
|
||||||
|
{makeItems(this.state.view)}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||||
|
onMouseEnter={this.handleDropdown.bind(null, true)}
|
||||||
|
onMouseLeave={this.handleDropdown.bind(null, false)}>
|
||||||
|
Recent brews
|
||||||
|
{this.renderDropdown()}
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
//var striptags = require('striptags');
|
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
|
|
||||||
const MAX_URL_SIZE = 2083;
|
|
||||||
const MAIN_URL = "https://www.reddit.com/r/UnearthedArcana/submit?selftext=true"
|
|
||||||
|
|
||||||
|
|
||||||
var RedditShare = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
brew : {
|
|
||||||
title : '',
|
|
||||||
sharedId : '',
|
|
||||||
text : ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getText : function(){
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
handleClick : function(){
|
|
||||||
var url = [
|
|
||||||
MAIN_URL,
|
|
||||||
'title=' + encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!'),
|
|
||||||
|
|
||||||
'text=' + encodeURIComponent(this.props.brew.text)
|
|
||||||
|
|
||||||
|
|
||||||
].join('&');
|
|
||||||
|
|
||||||
window.open(url, '_blank');
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <Nav.item icon='fa-reddit-alien' color='red' onClick={this.handleClick}>
|
|
||||||
share on reddit
|
|
||||||
</Nav.item>
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = RedditShare;
|
|
||||||
37
client/homebrew/navbar/staticSave.navitem.jsx
Normal file
37
client/homebrew/navbar/staticSave.navitem.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
|
||||||
|
const StaticSave = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
status : 'ready'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleClick : function(){
|
||||||
|
Actions.create();
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
if(this.props.status === 'saving'){
|
||||||
|
return <Nav.item icon='fa-spinner fa-spin' className='staticSave'>
|
||||||
|
save...
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
if(this.props.status === 'ready'){
|
||||||
|
return <Nav.item icon='fa-save' className='staticSave' onClick={this.handleClick}>
|
||||||
|
save
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Store.createSmartComponent(StaticSave, ()=>{
|
||||||
|
return {
|
||||||
|
status : Store.getStatus()
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,172 +1,63 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
const cx = require('classnames');
|
||||||
var request = require("superagent");
|
|
||||||
|
|
||||||
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');
|
||||||
|
const Items = require('../../navbar/navitems.js');
|
||||||
|
|
||||||
var EditTitle = require('../../navbar/editTitle.navitem.jsx');
|
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||||
var ReportIssue = require('../../navbar/issue.navitem.jsx');
|
const Utils = require('homebrewery/utils.js');
|
||||||
var PrintLink = require('../../navbar/print.navitem.jsx');
|
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
|
||||||
|
|
||||||
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const EditPage = React.createClass({
|
||||||
var Editor = require('../../editor/editor.jsx');
|
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const SAVE_TIMEOUT = 3000;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var EditPage = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
id : null,
|
|
||||||
brew : {
|
|
||||||
title : '',
|
|
||||||
text : '',
|
|
||||||
shareId : null,
|
|
||||||
editId : null,
|
|
||||||
createdAt : null,
|
|
||||||
updatedAt : null,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
title : this.props.brew.title,
|
|
||||||
text: this.props.brew.text,
|
|
||||||
isSaving : false,
|
|
||||||
isPending : false,
|
|
||||||
errors : null,
|
|
||||||
lastUpdated : this.props.brew.updatedAt
|
|
||||||
};
|
|
||||||
},
|
|
||||||
savedBrew : null,
|
|
||||||
|
|
||||||
componentDidMount: function(){
|
componentDidMount: function(){
|
||||||
this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
window.onbeforeunload = ()=>{
|
|
||||||
if(this.state.isSaving || this.state.isPending){
|
|
||||||
return 'You have unsaved changes!';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
window.onbeforeunload = function(){};
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
|
handleControlKeys : Utils.controlKeys({
|
||||||
handleSplitMove : function(){
|
s : Actions.save,
|
||||||
this.refs.editor.update();
|
p : Actions.print
|
||||||
},
|
}),
|
||||||
|
|
||||||
handleTitleChange : function(title){
|
|
||||||
this.setState({
|
|
||||||
title : title,
|
|
||||||
isPending : true
|
|
||||||
});
|
|
||||||
|
|
||||||
(this.hasChanges() ? this.debounceSave() : this.debounceSave.cancel());
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.setState({
|
|
||||||
text : text,
|
|
||||||
isPending : true
|
|
||||||
});
|
|
||||||
|
|
||||||
(this.hasChanges() ? this.debounceSave() : this.debounceSave.cancel());
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDelete : function(){
|
|
||||||
if(!confirm("are you sure you want to delete this brew?")) return;
|
|
||||||
if(!confirm("are you REALLY sure? You will not be able to recover it")) return;
|
|
||||||
|
|
||||||
request.get('/homebrew/api/remove/' + this.props.brew.editId)
|
|
||||||
.send()
|
|
||||||
.end(function(err, res){
|
|
||||||
window.location.href = '/homebrew';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
hasChanges : function(){
|
|
||||||
if(this.savedBrew){
|
|
||||||
if(this.state.text !== this.savedBrew.text) return true;
|
|
||||||
if(this.state.title !== this.savedBrew.title) return true;
|
|
||||||
}else{
|
|
||||||
if(this.state.text !== this.props.brew.text) return true;
|
|
||||||
if(this.state.title !== this.props.brew.title) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
save : function(){
|
|
||||||
this.debounceSave.cancel();
|
|
||||||
this.setState({
|
|
||||||
isSaving : true
|
|
||||||
});
|
|
||||||
|
|
||||||
request
|
|
||||||
.put('/homebrew/api/update/' + this.props.brew.editId)
|
|
||||||
.send({
|
|
||||||
text : this.state.text,
|
|
||||||
title : this.state.title
|
|
||||||
})
|
|
||||||
.end((err, res) => {
|
|
||||||
this.savedBrew = res.body;
|
|
||||||
this.setState({
|
|
||||||
isPending : false,
|
|
||||||
isSaving : false,
|
|
||||||
lastUpdated : res.body.updatedAt
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
renderSaveButton : function(){
|
|
||||||
if(this.state.isSaving){
|
|
||||||
return <Nav.item className='save' icon="fa-spinner fa-spin">saving...</Nav.item>
|
|
||||||
}
|
|
||||||
if(!this.state.isPending && !this.state.isSaving){
|
|
||||||
return <Nav.item className='save saved'>saved.</Nav.item>
|
|
||||||
}
|
|
||||||
if(this.state.isPending && this.hasChanges()){
|
|
||||||
return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderNavbar : function(){
|
|
||||||
return <Navbar>
|
|
||||||
<Nav.section>
|
|
||||||
<EditTitle title={this.state.title} onChange={this.handleTitleChange} />
|
|
||||||
</Nav.section>
|
|
||||||
<Nav.section>
|
|
||||||
{this.renderSaveButton()}
|
|
||||||
<Nav.item newTab={true} href={'/homebrew/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'>
|
|
||||||
Share
|
|
||||||
</Nav.item>
|
|
||||||
<PrintLink shareId={this.props.brew.shareId} />
|
|
||||||
<Nav.item color='red' icon='fa-trash' onClick={this.handleDelete}>
|
|
||||||
Delete
|
|
||||||
</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='editPage page'>
|
return <div className='editPage page'>
|
||||||
{this.renderNavbar()}
|
<SmartNav />
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
<BrewInterface />
|
||||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
|
||||||
<BrewRenderer text={this.state.text} />
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const SmartNav = Store.createSmartComponent(React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render : function(){
|
||||||
|
return <Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<Items.BrewTitle />
|
||||||
|
</Nav.section>
|
||||||
|
<Nav.section>
|
||||||
|
<Items.ContinousSave />
|
||||||
|
<Items.Issue />
|
||||||
|
<Nav.item newTab={true} href={'/share/' + Store.getBrew().shareId} color='teal' icon='fa-share-alt'>
|
||||||
|
Share
|
||||||
|
</Nav.item>
|
||||||
|
<Items.Print />
|
||||||
|
<Items.Account />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
}
|
||||||
|
}), ()=>{
|
||||||
|
return {brew : Store.getBrew()}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = EditPage;
|
module.exports = EditPage;
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
|
|
||||||
.editPage{
|
.editPage{
|
||||||
|
|
||||||
.navItem.save{
|
|
||||||
width : 75px;
|
|
||||||
text-align: center;
|
|
||||||
&.saved{
|
|
||||||
color : #666;
|
|
||||||
cursor : initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
46
client/homebrew/pages/errorPage/errorPage.jsx
Normal file
46
client/homebrew/pages/errorPage/errorPage.jsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
var Navbar = require('../../navbar/navbar.jsx');
|
||||||
|
var PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||||
|
var IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||||
|
var RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||||
|
|
||||||
|
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
|
var ErrorPage = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
ver : '0.0.0',
|
||||||
|
errorId: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
text : '# Oops \n We could not find a brew with that id. **Sorry!**',
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='errorPage page'>
|
||||||
|
<Navbar ver={this.props.ver}>
|
||||||
|
<Nav.section>
|
||||||
|
<Nav.item className='errorTitle'>
|
||||||
|
Crit Fail!
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.section>
|
||||||
|
|
||||||
|
<Nav.section>
|
||||||
|
<PatreonNavItem />
|
||||||
|
<IssueNavItem />
|
||||||
|
<RecentNavItem.both errorId={this.props.errorId} />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
|
||||||
|
<div className='content'>
|
||||||
|
<BrewRenderer text={this.text} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ErrorPage;
|
||||||
5
client/homebrew/pages/errorPage/errorPage.less
Normal file
5
client/homebrew/pages/errorPage/errorPage.less
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.errorPage{
|
||||||
|
.errorTitle{
|
||||||
|
background-color: @orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
client/homebrew/pages/hijackPrint.js
Normal file
12
client/homebrew/pages/hijackPrint.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//TODO: Depricate
|
||||||
|
|
||||||
|
module.exports = function(shareId){
|
||||||
|
return function(event){
|
||||||
|
event = event || window.event;
|
||||||
|
if((event.ctrlKey || event.metaKey) && event.keyCode == 80){
|
||||||
|
var win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank');
|
||||||
|
win.focus();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,50 +1,41 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
var Navbar = require('../../navbar/navbar.jsx');
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
var PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||||
|
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||||
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||||
|
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||||
|
|
||||||
|
|
||||||
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||||
var Editor = require('../../editor/editor.jsx');
|
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
//
|
||||||
|
|
||||||
var HomePage = React.createClass({
|
const HomePage = React.createClass({
|
||||||
getDefaultProps: function() {
|
handleSave : function(){
|
||||||
return {
|
Actions.saveNew();
|
||||||
welcomeText : ""
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
text: this.props.welcomeText
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleSplitMove : function(){
|
|
||||||
this.refs.editor.update();
|
|
||||||
},
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.setState({
|
|
||||||
text : text
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderNavbar : function(){
|
renderNavbar : function(){
|
||||||
return <Navbar>
|
return <Navbar>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<PatreonNavItem />
|
<PatreonNavItem />
|
||||||
<Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
|
<IssueNavItem />
|
||||||
report issue
|
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
|
||||||
</Nav.item>
|
|
||||||
<Nav.item newTab={true} href='/homebrew/changelog' color='purple' icon='fa-file-text-o'>
|
|
||||||
Changelog
|
Changelog
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<Nav.item href='/homebrew/new' color='green' icon='fa-external-link'>
|
<RecentNavItem.both />
|
||||||
|
<AccountNavItem />
|
||||||
|
{/*}
|
||||||
|
<Nav.item href='/new' color='green' icon='fa-external-link'>
|
||||||
New Brew
|
New Brew
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
|
*/}
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
},
|
},
|
||||||
@@ -52,15 +43,17 @@ var HomePage = React.createClass({
|
|||||||
render : function(){
|
render : function(){
|
||||||
return <div className='homePage page'>
|
return <div className='homePage page'>
|
||||||
{this.renderNavbar()}
|
{this.renderNavbar()}
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
<BrewInterface />
|
||||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
|
||||||
<BrewRenderer text={this.state.text} />
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href='/homebrew/new' className='floatingNewButton'>
|
<div className={cx('floatingSaveButton', {
|
||||||
|
//show : Store.getBrewText() !== this.props.welcomeText
|
||||||
|
})} onClick={this.handleSave}>
|
||||||
|
Save current <i className='fa fa-save' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href='/new' className='floatingNewButton'>
|
||||||
Create your own <i className='fa fa-magic' />
|
Create your own <i className='fa fa-magic' />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
|
|
||||||
.homePage{
|
.homePage{
|
||||||
|
|
||||||
|
|
||||||
position : relative;
|
position : relative;
|
||||||
a.floatingNewButton{
|
a.floatingNewButton{
|
||||||
.animate(background-color);
|
.animate(background-color);
|
||||||
@@ -10,6 +7,7 @@
|
|||||||
right : 70px;
|
right : 70px;
|
||||||
bottom : 70px;
|
bottom : 70px;
|
||||||
z-index : 100;
|
z-index : 100;
|
||||||
|
z-index : 5001;
|
||||||
padding : 1em;
|
padding : 1em;
|
||||||
background-color : @orange;
|
background-color : @orange;
|
||||||
font-size : 1.5em;
|
font-size : 1.5em;
|
||||||
@@ -20,4 +18,26 @@
|
|||||||
background-color : darken(@orange, 20%);
|
background-color : darken(@orange, 20%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.floatingSaveButton{
|
||||||
|
.animateAll();
|
||||||
|
position : absolute;
|
||||||
|
display : block;
|
||||||
|
right : 200px;
|
||||||
|
bottom : 90px;
|
||||||
|
z-index : 100;
|
||||||
|
z-index : 5000;
|
||||||
|
padding : 0.8em;
|
||||||
|
cursor : pointer;
|
||||||
|
background-color : @blue;
|
||||||
|
font-size : 0.8em;
|
||||||
|
color : white;
|
||||||
|
text-decoration : none;
|
||||||
|
box-shadow : 3px 3px 15px black;
|
||||||
|
&:hover{
|
||||||
|
background-color : darken(@blue, 20%);
|
||||||
|
}
|
||||||
|
&.show{
|
||||||
|
right : 350px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,127 +1,59 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
|
||||||
var request = require("superagent");
|
|
||||||
|
|
||||||
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 EditTitle = require('../../navbar/editTitle.navitem.jsx');
|
const Items = require('../../navbar/navitems.js');
|
||||||
|
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
|
||||||
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||||
var Editor = require('../../editor/editor.jsx');
|
const Utils = require('homebrewery/utils.js');
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
const KEY = 'naturalCrit-homebrew-new';
|
|
||||||
|
|
||||||
var NewPage = React.createClass({
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
title : 'My Awesome Brew v99',
|
|
||||||
text: '',
|
|
||||||
isSaving : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
const KEY = 'homebrewery-new';
|
||||||
|
|
||||||
|
const NewPage = React.createClass({
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
var storage = localStorage.getItem(KEY);
|
try{
|
||||||
if(storage){
|
const storedBrew = JSON.parse(localStorage.getItem(KEY));
|
||||||
this.setState({
|
if(storedBrew && storedBrew.text) Actions.setBrew(storedBrew);
|
||||||
text : storage
|
}catch(e){}
|
||||||
})
|
Store.updateEmitter.on('change', this.saveToLocal);
|
||||||
}
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
window.onbeforeunload = (e)=>{
|
|
||||||
if(this.state.text == '') return;
|
|
||||||
return "Your homebrew isn't saved. Are you sure you want to leave?";
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
window.onbeforeunload = function(){};
|
Store.updateEmitter.removeListener('change', this.saveToLocal);
|
||||||
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSplitMove : function(){
|
saveToLocal : function(){
|
||||||
this.refs.editor.update();
|
localStorage.setItem(KEY, JSON.stringify(Store.getBrew()));
|
||||||
},
|
|
||||||
|
|
||||||
handleTitleChange : function(title){
|
|
||||||
this.setState({
|
|
||||||
title : title
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.setState({
|
|
||||||
text : text
|
|
||||||
});
|
|
||||||
localStorage.setItem(KEY, text);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSave : function(){
|
|
||||||
this.setState({
|
|
||||||
isSaving : true
|
|
||||||
});
|
|
||||||
request.post('/homebrew/api')
|
|
||||||
.send({
|
|
||||||
title : this.state.title,
|
|
||||||
text : this.state.text
|
|
||||||
})
|
|
||||||
.end((err, res)=>{
|
|
||||||
|
|
||||||
if(err){
|
|
||||||
this.setState({
|
|
||||||
isSaving : false
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.onbeforeunload = function(){};
|
|
||||||
var brew = res.body;
|
|
||||||
localStorage.removeItem(KEY);
|
|
||||||
window.location = '/homebrew/edit/' + brew.editId;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
renderSaveButton : function(){
|
|
||||||
if(this.state.isSaving){
|
|
||||||
return <Nav.item icon='fa-spinner fa-spin' className='saveButton'>
|
|
||||||
save...
|
|
||||||
</Nav.item>
|
|
||||||
}else{
|
|
||||||
return <Nav.item icon='fa-save' className='saveButton' onClick={this.handleSave}>
|
|
||||||
save
|
|
||||||
</Nav.item>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
renderNavbar : function(){
|
|
||||||
return <Navbar>
|
|
||||||
<Nav.section>
|
|
||||||
<EditTitle title={this.state.title} onChange={this.handleTitleChange} />
|
|
||||||
</Nav.section>
|
|
||||||
|
|
||||||
<Nav.section>
|
|
||||||
{this.renderSaveButton()}
|
|
||||||
<Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
|
|
||||||
report issue
|
|
||||||
</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
},
|
},
|
||||||
|
handleControlKeys : Utils.controlKeys({
|
||||||
|
s : Actions.saveNew,
|
||||||
|
p : Actions.localPrint
|
||||||
|
}),
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='newPage page'>
|
return <div className='newPage page'>
|
||||||
{this.renderNavbar()}
|
<Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<Items.BrewTitle />
|
||||||
|
</Nav.section>
|
||||||
|
|
||||||
|
<Nav.section>
|
||||||
|
<Items.StaticSave />
|
||||||
|
<Nav.item color='purple' icon='fa-file-pdf-o' onClick={Actions.localPrint}>
|
||||||
|
get PDF
|
||||||
|
</Nav.item>
|
||||||
|
<Items.Issue />
|
||||||
|
<Items.Account />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
<BrewInterface />
|
||||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
|
||||||
<BrewRenderer text={this.state.text} />
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
.newPage{
|
.newPage{
|
||||||
|
|
||||||
.saveButton{
|
|
||||||
background-color: @orange;
|
|
||||||
&:hover{
|
|
||||||
background-color: @green;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
43
client/homebrew/pages/printPage/printPage.jsx
Normal file
43
client/homebrew/pages/printPage/printPage.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
const Markdown = require('homebrewery/markdown.js');
|
||||||
|
|
||||||
|
const PrintPage = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
query : {},
|
||||||
|
brew : {
|
||||||
|
text : '',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
brewText: this.props.brew.text
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
if(this.props.query.local){
|
||||||
|
this.setState({ brewText : localStorage.getItem(this.props.query.local)});
|
||||||
|
}
|
||||||
|
if(this.props.query.dialog) window.print();
|
||||||
|
},
|
||||||
|
renderPages : function(){
|
||||||
|
return _.map(this.state.brewText.split('\\page'), (page, index) => {
|
||||||
|
return <div
|
||||||
|
className='phb'
|
||||||
|
id={`p${index + 1}`}
|
||||||
|
dangerouslySetInnerHTML={{__html:Markdown.render(page)}}
|
||||||
|
key={index} />;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div>
|
||||||
|
{this.renderPages()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = PrintPage;
|
||||||
3
client/homebrew/pages/printPage/printPage.less
Normal file
3
client/homebrew/pages/printPage/printPage.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.printPage{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
var Navbar = require('../../navbar/navbar.jsx');
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
|
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||||
|
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||||
|
//const RecentlyViewed = require('../../navbar/recent.navitem.jsx').viewed;
|
||||||
|
const Account = require('../../navbar/account.navitem.jsx');
|
||||||
|
|
||||||
var PrintLink = require('../../navbar/print.navitem.jsx');
|
const BrewRenderer = require('homebrewery/brewRenderer/brewRenderer.jsx');
|
||||||
|
const Utils = require('homebrewery/utils.js');
|
||||||
|
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const Actions = require('homebrewery/brew.actions.js');
|
||||||
|
const Store = require('homebrewery/brew.store.js');
|
||||||
|
|
||||||
var SharePage = React.createClass({
|
const SharePage = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
brew : {
|
brew : {
|
||||||
@@ -23,23 +29,35 @@ var SharePage = React.createClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
|
},
|
||||||
|
handleControlKeys : Utils.controlKeys({
|
||||||
|
p : Actions.print
|
||||||
|
}),
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
|
const brew = Store.getBrew();
|
||||||
return <div className='sharePage page'>
|
return <div className='sharePage page'>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
<Nav.item className='brewTitle'>{brew.title}</Nav.item>
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
|
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<PrintLink shareId={this.props.brew.shareId} />
|
<ReportIssue />
|
||||||
<Nav.item href={'/homebrew/source/' + this.props.brew.shareId} color='teal' icon='fa-code'>
|
<PrintLink shareId={brew.shareId} />
|
||||||
|
<Nav.item href={'/source/' + brew.shareId} color='teal' icon='fa-code'>
|
||||||
source
|
source
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
|
<Account />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<BrewRenderer text={this.props.brew.text} />
|
<BrewRenderer brewText={brew.text} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
55
client/homebrew/pages/userPage/brewItem/brewItem.jsx
Normal file
55
client/homebrew/pages/userPage/brewItem/brewItem.jsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
const BrewItem = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : {
|
||||||
|
title : '',
|
||||||
|
description : '',
|
||||||
|
|
||||||
|
authors : []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
renderEditLink: function(){
|
||||||
|
if(!this.props.brew.editId) return;
|
||||||
|
|
||||||
|
return <a href={`/edit/${this.props.brew.editId}`} target='_blank'>
|
||||||
|
<i className='fa fa-pencil' />
|
||||||
|
</a>
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
const brew = this.props.brew;
|
||||||
|
return <div className='brewItem'>
|
||||||
|
<h2>{brew.title}</h2>
|
||||||
|
<p className='description' >{brew.description}</p>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div className='info'>
|
||||||
|
<span>
|
||||||
|
<i className='fa fa-user' /> {brew.authors.join(', ')}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<i className='fa fa-eye' /> {brew.views}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<i className='fa fa-refresh' /> {moment(brew.updatedAt).fromNow()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='links'>
|
||||||
|
<a href={`/share/${brew.shareId}`} target='_blank'>
|
||||||
|
<i className='fa fa-share-alt' />
|
||||||
|
</a>
|
||||||
|
{this.renderEditLink()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewItem;
|
||||||
60
client/homebrew/pages/userPage/brewItem/brewItem.less
Normal file
60
client/homebrew/pages/userPage/brewItem/brewItem.less
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
.brewItem{
|
||||||
|
position : relative;
|
||||||
|
display : inline-block;
|
||||||
|
vertical-align : top;
|
||||||
|
box-sizing : border-box;
|
||||||
|
box-sizing : border-box;
|
||||||
|
overflow : hidden;
|
||||||
|
width : 48%;
|
||||||
|
margin-right : 15px;
|
||||||
|
margin-bottom : 15px;
|
||||||
|
padding : 5px 15px 5px 8px;
|
||||||
|
padding-right : 15px;
|
||||||
|
border : 1px solid #c9ad6a;
|
||||||
|
border-radius : 5px;
|
||||||
|
-webkit-column-break-inside : avoid;
|
||||||
|
page-break-inside : avoid;
|
||||||
|
break-inside : avoid;
|
||||||
|
h4{
|
||||||
|
margin-bottom : 5px;
|
||||||
|
font-size : 2.2em;
|
||||||
|
}
|
||||||
|
.info{
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-size : 1.2em;
|
||||||
|
&>span{
|
||||||
|
margin-right : 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
.links{
|
||||||
|
opacity : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:nth-child(2n + 1){
|
||||||
|
margin-right : 0px;
|
||||||
|
}
|
||||||
|
.links{
|
||||||
|
.animate(opacity);
|
||||||
|
position : absolute;
|
||||||
|
top : 0px;
|
||||||
|
right : 0px;
|
||||||
|
height : 100%;
|
||||||
|
width : 2em;
|
||||||
|
opacity : 0;
|
||||||
|
background-color : fade(black, 60%);
|
||||||
|
text-align : center;
|
||||||
|
a{
|
||||||
|
.animate(opacity);
|
||||||
|
display : block;
|
||||||
|
margin : 8px 0px;
|
||||||
|
opacity : 0.6;
|
||||||
|
font-size : 1.3em;
|
||||||
|
color : white;
|
||||||
|
&:hover{
|
||||||
|
opacity : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
client/homebrew/pages/userPage/userPage.jsx
Normal file
76
client/homebrew/pages/userPage/userPage.jsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
|
|
||||||
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||||
|
const Account = require('../../navbar/account.navitem.jsx');
|
||||||
|
const BrewItem = require('./brewItem/brewItem.jsx');
|
||||||
|
|
||||||
|
const brew = {
|
||||||
|
title : 'SUPER Long title woah now',
|
||||||
|
authors : []
|
||||||
|
}
|
||||||
|
|
||||||
|
const BREWS = _.times(25, ()=>{ return brew});
|
||||||
|
|
||||||
|
|
||||||
|
const UserPage = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
username : '',
|
||||||
|
brews : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
renderBrews : function(brews){
|
||||||
|
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
|
||||||
|
|
||||||
|
const sortedBrews = _.sortBy(brews, (brew)=>{ return brew.title; });
|
||||||
|
|
||||||
|
return _.map(sortedBrews, (brew, idx) => {
|
||||||
|
return <BrewItem brew={brew} key={idx}/>
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getSortedBrews : function(){
|
||||||
|
return _.groupBy(this.props.brews, (brew)=>{
|
||||||
|
return (brew.published ? 'published' : 'private')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPrivateBrews : function(privateBrews){
|
||||||
|
if(!privateBrews || !privateBrews.length) return;
|
||||||
|
|
||||||
|
return [
|
||||||
|
<h1>{this.props.username}'s unpublished brews</h1>,
|
||||||
|
this.renderBrews(privateBrews)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
const brews = this.getSortedBrews();
|
||||||
|
console.log('user brews', brews);
|
||||||
|
|
||||||
|
return <div className='userPage page'>
|
||||||
|
<Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<RecentNavItem.both />
|
||||||
|
<Account userPage={this.props.username} />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
|
||||||
|
<div className='content'>
|
||||||
|
<div className='phb'>
|
||||||
|
<h1>{this.props.username}'s brews</h1>
|
||||||
|
{this.renderBrews(brews.published)}
|
||||||
|
{this.renderPrivateBrews(brews.private)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = UserPage;
|
||||||
33
client/homebrew/pages/userPage/userPage.less
Normal file
33
client/homebrew/pages/userPage/userPage.less
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
.noColumns(){
|
||||||
|
column-count : auto;
|
||||||
|
column-fill : auto;
|
||||||
|
column-gap : auto;
|
||||||
|
column-width : auto;
|
||||||
|
-webkit-column-count : auto;
|
||||||
|
-moz-column-count : auto;
|
||||||
|
-webkit-column-width : auto;
|
||||||
|
-moz-column-width : auto;
|
||||||
|
-webkit-column-gap : auto;
|
||||||
|
-moz-column-gap : auto;
|
||||||
|
}
|
||||||
|
.userPage{
|
||||||
|
.content{
|
||||||
|
overflow-y : scroll;
|
||||||
|
.phb{
|
||||||
|
.noColumns();
|
||||||
|
height : auto;
|
||||||
|
min-height : 279.4mm;
|
||||||
|
margin : 20px auto;
|
||||||
|
&::after{
|
||||||
|
display : none;
|
||||||
|
}
|
||||||
|
.noBrews{
|
||||||
|
margin : 10px 0px;
|
||||||
|
font-size : 1.3em;
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,3 +4,4 @@
|
|||||||
@redTriangleImage: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAAeCAYAAACR82geAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4zjOaXUAAAAt5JREFUaEPtWTtoVEEUHf8iaEjMZt/ME6Jx3X1zlwgavyAICoL4aRWFIFYWAUURLZQo+GkUTGHc92Y2q2AKI0oKLbQIKRS0UPxCilgEFIKCTYIfyBrPi4NImGKJyWZ3Xw4cpth9b84c7ty59w3TaXdAS3FXEz8VkLtNp5YuZjNgoTGj/1KRGNEkXoEqkM4RmLamlbG55u/RwXhj7BRDMKw3kPyKkmKflk69ebxyYTeiAJL4CLO6AynOaI/v8Buqq8wrKwPWRU+Ef7bgOy3dm4p4i0456/wmNs9MU36wLnKySO4wzHqCyLoGsw4EyfiKUcZmmalLG9YFTSVJDMKkB0ryc1np7OxsrKo2UkoLVvHFJLl5GNWHpH5bS35UUXxDWyKxwMibPljFTjdJfMP2ewbDrgee06xWxhIoGWYbycWBVViJMQvCqM/go4D4Be25u/wkr4X8qctXNiFlwjwiqx9RdQfj8ZznbvI5X2SW9f+wTFi+JPEdfIHaysd4SKdEqnXrBKt26wQVQrMFv+IE7MlKcakjLfbkKOYUVDLYXljhzOMEHEBU3UMfeDJLfIu1cbY8GDmqtPiJyHoDaph1OJOqa5wxBgyNCUi8DY3pgDEqWbc6csaotPsLUTEAE+7rdES30t/kS7wHJlzGSbU349Xy6CVfEj9gwksk1yDKxzV6LPEBlXAXxhPaE5v9pogVeOGWQAR8AR/DhIuBJ3ZnE04M8iPWEqCJRIJ8jmhohynNimKJLsbmGMnFgVVYMTn22cHtgxGdgcePZWR8Y66+fqGRN32wip1KkhgEHwaSnw8/VClaVmOklBas4ieL5A7jlHiK4qlNEz/YvqquIXqfNkmMoIJ8j+R4C2a0+ElnfSux+Waa8oN1kQUwS+ITzOhWnjir05G9PhFDMKEX7ftV1Av7b6Tiy83jlYvxJmArhPdDrzHq8Io24/G1Ub2iHbvURzN1GuP2tkTNEvNThMHYbySiA8IawOrpAAAAAElFTkSuQmCC);
|
@redTriangleImage: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAAeCAYAAACR82geAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4zjOaXUAAAAt5JREFUaEPtWTtoVEEUHf8iaEjMZt/ME6Jx3X1zlwgavyAICoL4aRWFIFYWAUURLZQo+GkUTGHc92Y2q2AKI0oKLbQIKRS0UPxCilgEFIKCTYIfyBrPi4NImGKJyWZ3Xw4cpth9b84c7ty59w3TaXdAS3FXEz8VkLtNp5YuZjNgoTGj/1KRGNEkXoEqkM4RmLamlbG55u/RwXhj7BRDMKw3kPyKkmKflk69ebxyYTeiAJL4CLO6AynOaI/v8Buqq8wrKwPWRU+Ef7bgOy3dm4p4i0456/wmNs9MU36wLnKySO4wzHqCyLoGsw4EyfiKUcZmmalLG9YFTSVJDMKkB0ryc1np7OxsrKo2UkoLVvHFJLl5GNWHpH5bS35UUXxDWyKxwMibPljFTjdJfMP2ewbDrgee06xWxhIoGWYbycWBVViJMQvCqM/go4D4Be25u/wkr4X8qctXNiFlwjwiqx9RdQfj8ZznbvI5X2SW9f+wTFi+JPEdfIHaysd4SKdEqnXrBKt26wQVQrMFv+IE7MlKcakjLfbkKOYUVDLYXljhzOMEHEBU3UMfeDJLfIu1cbY8GDmqtPiJyHoDaph1OJOqa5wxBgyNCUi8DY3pgDEqWbc6csaotPsLUTEAE+7rdES30t/kS7wHJlzGSbU349Xy6CVfEj9gwksk1yDKxzV6LPEBlXAXxhPaE5v9pogVeOGWQAR8AR/DhIuBJ3ZnE04M8iPWEqCJRIJ8jmhohynNimKJLsbmGMnFgVVYMTn22cHtgxGdgcePZWR8Y66+fqGRN32wip1KkhgEHwaSnw8/VClaVmOklBas4ieL5A7jlHiK4qlNEz/YvqquIXqfNkmMoIJ8j+R4C2a0+ElnfSux+Waa8oN1kQUwS+ITzOhWnjir05G9PhFDMKEX7ftV1Av7b6Tiy83jlYvxJmArhPdDrzHq8Io24/G1Ub2iHbvURzN1GuP2tkTNEvNThMHYbySiA8IawOrpAAAAAElFTkSuQmCC);
|
||||||
@monsterBorderImage: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA8CAMAAADG+c2+AAAACVBMVEX///8AAADmmihtJc8lAAAAAXRSTlMAQObYZgAAACxJREFUeAHt2EEBACAAwkCwf2hL8NK7AAuwdCw9U5Og4FgeAAAAAK7IBxPjAl3qBs+hfAnLAAAAAElFTkSuQmCC);
|
@monsterBorderImage: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA8CAMAAADG+c2+AAAACVBMVEX///8AAADmmihtJc8lAAAAAXRSTlMAQObYZgAAACxJREFUeAHt2EEBACAAwkCwf2hL8NK7AAuwdCw9U5Og4FgeAAAAAK7IBxPjAl3qBs+hfAnLAAAAAElFTkSuQmCC);
|
||||||
@noteBorderImage: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA8CAMAAADG+c2+AAAANlBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHL6OuAAAAEXRSTlMABAwYJDRIXHSLo7fL2+fz+6go4VUAAAB6SURBVHgB7dg5DsJQAANRZ9+Xuf9lQUKKEHT5LlL4HWB6W5d61m1zrT/dhm5j6/RjPCkJco761ixQFoSl0aXfKQ+y9/qoJnAEYar01q64gqytNBz4ghyDMPMHnyYiIiIiIgIzYeYfj/Z56x3g7ovAf2L4bxb/EWS/ql7LZCDx/Ry3RwAAAABJRU5ErkJggg==);
|
@noteBorderImage: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA8CAMAAADG+c2+AAAANlBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHL6OuAAAAEXRSTlMABAwYJDRIXHSLo7fL2+fz+6go4VUAAAB6SURBVHgB7dg5DsJQAANRZ9+Xuf9lQUKKEHT5LlL4HWB6W5d61m1zrT/dhm5j6/RjPCkJco761ixQFoSl0aXfKQ+y9/qoJnAEYar01q64gqytNBz4ghyDMPMHnyYiIiIiIgIzYeYfj/Z56x3g7ovAf2L4bxb/EWS/ql7LZCDx/Ry3RwAAAABJRU5ErkJggg==);
|
||||||
|
@descriptiveBoxImage : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAA8CAMAAADG+c2+AAAAM1BMVEUAAAAAAAAAAAAkAAA1GwA0GgA5GgA5GwA4HgA7HgA5HgA7HgA7HwA7HgA8HgA7HgA8HwDn6RpAAAAAEHRSTlMAAQcHMDExQ1ZW1dXY3t7+02zZCAAAAKNJREFUWMPt2LEOwyAMBNCkSXGpKfb/f20G3AGUiWOJdNlueYrt6di2J3xHrmZF9mVCdnd3T/NgCO+ItUWdB0P4RrQWfR4chOVggUcu/cgCH0X6o+ynuivg3QjIAm8FggQJEiRIkCBBggQJEoTApO56An15EFIrVgJ4vaAtlnlwEKKbGrLATlgOxg9XeORfv9IMH+Xzb7tSzGo+gL7chNcjXpUulIwXVrjWS+0AAAAASUVORK5CYII=");
|
||||||
31
client/homebrew/phbStyle/phb.depricated.less
Normal file
31
client/homebrew/phbStyle/phb.depricated.less
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
.phb{
|
||||||
|
//Double hr for full width elements
|
||||||
|
hr+hr+blockquote{
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
}
|
||||||
|
|
||||||
|
//*****************************
|
||||||
|
// * CLASS TABLE
|
||||||
|
// *****************************/
|
||||||
|
hr+table{
|
||||||
|
margin-top : -5px;
|
||||||
|
margin-bottom : 50px;
|
||||||
|
padding-top : 10px;
|
||||||
|
border-collapse : separate;
|
||||||
|
background-color : white;
|
||||||
|
border : initial;
|
||||||
|
border-style : solid;
|
||||||
|
border-image-outset : 37px 17px;
|
||||||
|
border-image-repeat : round;
|
||||||
|
border-image-slice : 150 200 150 200;
|
||||||
|
border-image-source : @frameBorderImage;
|
||||||
|
border-image-width : 47px;
|
||||||
|
}
|
||||||
|
h5+hr+table{
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
@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';
|
||||||
|
@import (less) './client/homebrew/phbStyle/phb.depricated.less';
|
||||||
//Colors
|
//Colors
|
||||||
@background : #EEE5CE;
|
@background : #EEE5CE;
|
||||||
@noteGreen : #e0e5c1;
|
@noteGreen : #e0e5c1;
|
||||||
@@ -10,6 +11,12 @@
|
|||||||
@headerText : #58180D;
|
@headerText : #58180D;
|
||||||
@monsterStatBackground : #FDF1DC;
|
@monsterStatBackground : #FDF1DC;
|
||||||
@page { margin: 0; }
|
@page { margin: 0; }
|
||||||
|
body {
|
||||||
|
counter-reset : phb-page-numbers;
|
||||||
|
}
|
||||||
|
*{
|
||||||
|
-webkit-print-color-adjust : exact;
|
||||||
|
}
|
||||||
.useSansSerif(){
|
.useSansSerif(){
|
||||||
font-family : ScalySans;
|
font-family : ScalySans;
|
||||||
em{
|
em{
|
||||||
@@ -22,20 +29,21 @@
|
|||||||
letter-spacing : -0.02em;
|
letter-spacing : -0.02em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.useColumns(){
|
.useColumns(@multiplier : 1){
|
||||||
column-count : 2;
|
column-count : 2;
|
||||||
column-fill : auto;
|
column-fill : auto;
|
||||||
column-gap : 1cm;
|
column-gap : 1cm;
|
||||||
column-width : 8cm;
|
column-width : 8cm * @multiplier;
|
||||||
-webkit-column-count : 2;
|
-webkit-column-count : 2;
|
||||||
-moz-column-count : 2;
|
-moz-column-count : 2;
|
||||||
-webkit-column-width : 8cm;
|
-webkit-column-width : 8cm * @multiplier;
|
||||||
-moz-column-width : 8cm;
|
-moz-column-width : 8cm * @multiplier;
|
||||||
-webkit-column-gap : 1cm;
|
-webkit-column-gap : 1cm;
|
||||||
-moz-column-gap : 1cm;
|
-moz-column-gap : 1cm;
|
||||||
}
|
}
|
||||||
.phb{
|
.phb{
|
||||||
.useColumns();
|
.useColumns();
|
||||||
|
counter-increment : phb-page-numbers;
|
||||||
position : relative;
|
position : relative;
|
||||||
z-index : 15;
|
z-index : 15;
|
||||||
box-sizing : border-box;
|
box-sizing : border-box;
|
||||||
@@ -58,23 +66,26 @@
|
|||||||
padding-bottom : 0.8em;
|
padding-bottom : 0.8em;
|
||||||
line-height : 1.3em;
|
line-height : 1.3em;
|
||||||
&+p{
|
&+p{
|
||||||
margin-top : -0.8em;
|
margin-top : -0.8em;
|
||||||
text-indent : 1em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ul{
|
ul{
|
||||||
margin-bottom : 0.8em;
|
margin-bottom : 0.8em;
|
||||||
|
padding-left : 1.4em;
|
||||||
line-height : 1.3em;
|
line-height : 1.3em;
|
||||||
list-style-position : outside;
|
list-style-position : outside;
|
||||||
list-style-type : disc;
|
list-style-type : disc;
|
||||||
padding-left: 1.4em;
|
|
||||||
}
|
}
|
||||||
ol{
|
ol{
|
||||||
margin-bottom : 0.8em;
|
margin-bottom : 0.8em;
|
||||||
|
padding-left : 1.4em;
|
||||||
line-height : 1.3em;
|
line-height : 1.3em;
|
||||||
list-style-position : outside;
|
list-style-position : outside;
|
||||||
list-style-type : decimal;
|
list-style-type : decimal;
|
||||||
padding-left: 1.4em;
|
}
|
||||||
|
//Indents after p or lists
|
||||||
|
p+p, ul+p, ol+p{
|
||||||
|
text-indent : 1em;
|
||||||
}
|
}
|
||||||
img{
|
img{
|
||||||
z-index : -1;
|
z-index : -1;
|
||||||
@@ -169,23 +180,23 @@
|
|||||||
// *****************************/
|
// *****************************/
|
||||||
blockquote{
|
blockquote{
|
||||||
.useSansSerif();
|
.useSansSerif();
|
||||||
box-sizing : border-box;
|
box-sizing : border-box;
|
||||||
margin-bottom : 1em;
|
margin-bottom : 1em;
|
||||||
padding : 5px 10px;
|
padding : 5px 10px;
|
||||||
background-color : @noteGreen;
|
background-color : @noteGreen;
|
||||||
border-style: solid;
|
border-style : solid;
|
||||||
border-width: 11px;
|
border-width : 11px;
|
||||||
border-image: @noteBorderImage 11;
|
border-image : @noteBorderImage 11;
|
||||||
border-image-outset: 9px 0px;
|
border-image-outset : 9px 0px;
|
||||||
box-shadow : 1px 4px 14px #888;
|
box-shadow : 1px 4px 14px #888;
|
||||||
p, ul{
|
p, ul{
|
||||||
font-size : 0.352cm;
|
font-size : 0.352cm;
|
||||||
line-height : 1.1em;
|
line-height : 1.1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//If a note starts a column, give it space at the top to render border
|
//If a note starts a column, give it space at the top to render border
|
||||||
pre+blockquote{
|
pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote {
|
||||||
margin-top: 11px;
|
margin-top : 13px;
|
||||||
}
|
}
|
||||||
//*****************************
|
//*****************************
|
||||||
// * MONSTER STAT BLOCK
|
// * MONSTER STAT BLOCK
|
||||||
@@ -209,21 +220,21 @@
|
|||||||
font-weight : 400;
|
font-weight : 400;
|
||||||
border-bottom : 1px solid @headerText;
|
border-bottom : 1px solid @headerText;
|
||||||
}
|
}
|
||||||
|
hr+ul{
|
||||||
|
color : @headerText;
|
||||||
|
}
|
||||||
ul{
|
ul{
|
||||||
.useSansSerif();
|
.useSansSerif();
|
||||||
padding-left : 1em;
|
padding-left : 1em;
|
||||||
font-size : 0.352cm;
|
font-size : 0.352cm;
|
||||||
color : @headerText;
|
|
||||||
text-indent : -1em;
|
|
||||||
list-style-type : none;
|
|
||||||
}
|
}
|
||||||
// Monster Ability table
|
// Monster Ability table
|
||||||
hr+table{
|
hr+table{
|
||||||
margin : 0;
|
margin : 0;
|
||||||
column-span : 1;
|
column-span : 1;
|
||||||
background-color : transparent;
|
background-color : transparent;
|
||||||
|
border-style : none;
|
||||||
border-image : none;
|
border-image : none;
|
||||||
border-style : none;
|
|
||||||
-webkit-column-span : 1;
|
-webkit-column-span : 1;
|
||||||
tbody{
|
tbody{
|
||||||
tr:nth-child(odd), tr:nth-child(even){
|
tr:nth-child(odd), tr:nth-child(even){
|
||||||
@@ -251,29 +262,7 @@
|
|||||||
}
|
}
|
||||||
//Full Width
|
//Full Width
|
||||||
hr+hr+blockquote{
|
hr+hr+blockquote{
|
||||||
.useColumns();
|
.useColumns(0.96);
|
||||||
}
|
|
||||||
//*****************************
|
|
||||||
// * CLASS TABLE
|
|
||||||
// *****************************/
|
|
||||||
hr+table{
|
|
||||||
margin-top : -5px;
|
|
||||||
margin-bottom : 50px;
|
|
||||||
padding-top : 10px;
|
|
||||||
border-collapse : separate;
|
|
||||||
background-color : white;
|
|
||||||
border : initial;
|
|
||||||
border-style : solid;
|
|
||||||
border-image-outset : 37px 17px;
|
|
||||||
border-image-repeat : round;
|
|
||||||
border-image-slice : 150 200 150 200;
|
|
||||||
border-image-source : @frameBorderImage;
|
|
||||||
border-image-width : 47px;
|
|
||||||
}
|
|
||||||
h5+hr+table{
|
|
||||||
column-span : all;
|
|
||||||
-webkit-column-span : all;
|
|
||||||
-moz-column-span : all;
|
|
||||||
}
|
}
|
||||||
//*****************************
|
//*****************************
|
||||||
// * FOOTER
|
// * FOOTER
|
||||||
@@ -283,6 +272,7 @@
|
|||||||
position : absolute;
|
position : absolute;
|
||||||
bottom : 0px;
|
bottom : 0px;
|
||||||
left : 0px;
|
left : 0px;
|
||||||
|
z-index : 100;
|
||||||
height : 50px;
|
height : 50px;
|
||||||
width : 100%;
|
width : 100%;
|
||||||
background-image : @footerAccentImage;
|
background-image : @footerAccentImage;
|
||||||
@@ -308,11 +298,15 @@
|
|||||||
font-size : 0.9em;
|
font-size : 0.9em;
|
||||||
color : #c9ad6a;
|
color : #c9ad6a;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
|
&.auto::after {
|
||||||
|
content : counter(phb-page-numbers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.footnote{
|
.footnote{
|
||||||
position : absolute;
|
position : absolute;
|
||||||
right : 80px;
|
right : 80px;
|
||||||
bottom : 32px;
|
bottom : 32px;
|
||||||
|
z-index : 150;
|
||||||
width : 200px;
|
width : 200px;
|
||||||
font-size : 0.8em;
|
font-size : 0.8em;
|
||||||
color : #c9ad6a;
|
color : #c9ad6a;
|
||||||
@@ -332,21 +326,15 @@
|
|||||||
text-indent : -1em;
|
text-indent : -1em;
|
||||||
list-style-type : none;
|
list-style-type : none;
|
||||||
}
|
}
|
||||||
//Double hr for full width elements
|
|
||||||
hr+hr+blockquote{
|
|
||||||
column-span : all;
|
|
||||||
-webkit-column-span : all;
|
|
||||||
-moz-column-span : all;
|
|
||||||
}
|
|
||||||
//Column Break
|
//Column Break
|
||||||
pre{
|
pre, code{
|
||||||
visibility : hidden;
|
visibility : hidden;
|
||||||
-webkit-column-break-after : always;
|
-webkit-column-break-after : always;
|
||||||
break-after : always;
|
break-after : always;
|
||||||
-moz-column-break-after : always;
|
-moz-column-break-after : always;
|
||||||
}
|
}
|
||||||
//Avoid breaking up
|
//Avoid breaking up
|
||||||
p,ul,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;
|
column-break-inside : avoid;
|
||||||
@@ -365,6 +353,35 @@
|
|||||||
margin-bottom : 0px;
|
margin-bottom : 0px;
|
||||||
margin-left : 1.5em;
|
margin-left : 1.5em;
|
||||||
}
|
}
|
||||||
|
li{
|
||||||
|
-webkit-column-break-inside : avoid;
|
||||||
|
column-break-inside : avoid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * SPELL LIST
|
||||||
|
// *****************************/
|
||||||
|
.phb .spellList{
|
||||||
|
.useSansSerif();
|
||||||
|
column-count : 4;
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
ul+h5{
|
||||||
|
margin-top : 15px;
|
||||||
|
}
|
||||||
|
p, ul{
|
||||||
|
font-size : 0.352cm;
|
||||||
|
line-height : 1.3em;
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
margin-bottom : 0.5em;
|
||||||
|
padding-left : 1em;
|
||||||
|
text-indent : -1em;
|
||||||
|
list-style-type : none;
|
||||||
|
-webkit-column-break-inside : auto;
|
||||||
|
column-break-inside : auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//*****************************
|
//*****************************
|
||||||
// * PRINT
|
// * PRINT
|
||||||
@@ -374,3 +391,90 @@
|
|||||||
box-shadow : none;
|
box-shadow : none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media print {
|
||||||
|
.phb .descriptive, .phb blockquote{
|
||||||
|
box-shadow : none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * WIDE
|
||||||
|
// *****************************/
|
||||||
|
.phb .wide{
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * CLASS TABLE
|
||||||
|
// *****************************/
|
||||||
|
.phb .classTable{
|
||||||
|
margin-top : 25px;
|
||||||
|
margin-bottom : 40px;
|
||||||
|
border-collapse : separate;
|
||||||
|
background-color : white;
|
||||||
|
border : initial;
|
||||||
|
border-style : solid;
|
||||||
|
border-image-outset : 25px 17px;
|
||||||
|
border-image-repeat : round;
|
||||||
|
border-image-slice : 150 200 150 200;
|
||||||
|
border-image-source : @frameBorderImage;
|
||||||
|
border-image-width : 47px;
|
||||||
|
h5{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * CLASS TABLE
|
||||||
|
// *****************************/
|
||||||
|
.phb .descriptive{
|
||||||
|
display : block-inline;
|
||||||
|
margin-bottom : 1em;
|
||||||
|
background-color : #faf7ea;
|
||||||
|
font-family : ScalySans;
|
||||||
|
border-style : solid;
|
||||||
|
border-width : 7px;
|
||||||
|
border-image : @descriptiveBoxImage 12 round;
|
||||||
|
border-image-outset : 4px;
|
||||||
|
box-shadow : 0px 0px 6px #faf7ea;
|
||||||
|
p{
|
||||||
|
display : block;
|
||||||
|
padding-bottom : 0px;
|
||||||
|
line-height : 1.5em;
|
||||||
|
}
|
||||||
|
p + p {
|
||||||
|
padding-top : .8em;
|
||||||
|
}
|
||||||
|
em {
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-weight : 800;
|
||||||
|
letter-spacing : -0.02em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.phb pre+.descriptive{
|
||||||
|
margin-top : 8px;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * TABLE OF CONTENTS
|
||||||
|
// *****************************/
|
||||||
|
.phb .toc{
|
||||||
|
-webkit-column-break-inside : avoid;
|
||||||
|
column-break-inside : avoid;
|
||||||
|
a{
|
||||||
|
color : black;
|
||||||
|
text-decoration : none;
|
||||||
|
&:hover{
|
||||||
|
text-decoration : underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
padding-left : 0;
|
||||||
|
list-style-type : none;
|
||||||
|
}
|
||||||
|
&>ul>li{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var Router = require('pico-router');
|
|
||||||
|
|
||||||
var NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
|
||||||
var HomebrewIcon = require('naturalcrit/svg/homebrew.svg.jsx');
|
|
||||||
|
|
||||||
var Main = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
tools : [
|
|
||||||
{
|
|
||||||
id : 'homebrew',
|
|
||||||
path : '/homebrew',
|
|
||||||
name : 'The Homebrewery',
|
|
||||||
icon : <HomebrewIcon />,
|
|
||||||
desc : 'Make authentic-looking 5e homebrews using Markdown',
|
|
||||||
|
|
||||||
show : true,
|
|
||||||
beta : false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id : 'homebrew2',
|
|
||||||
path : '/homebrew',
|
|
||||||
name : 'The Homebrewery',
|
|
||||||
icon : <HomebrewIcon />,
|
|
||||||
desc : 'Make authentic-looking 5e homebrews using Markdown',
|
|
||||||
|
|
||||||
show : false,
|
|
||||||
beta : true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id : 'homebrewfg2',
|
|
||||||
path : '/homebrew',
|
|
||||||
name : 'The Homebrewery',
|
|
||||||
icon : <HomebrewIcon />,
|
|
||||||
desc : 'Make authentic-looking 5e homebrews using Markdown',
|
|
||||||
|
|
||||||
show : false,
|
|
||||||
beta : false
|
|
||||||
}
|
|
||||||
|
|
||||||
]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
renderTool : function(tool){
|
|
||||||
if(!tool.show) return null;
|
|
||||||
|
|
||||||
return <a href={tool.path} className={cx('tool', tool.id, {beta : tool.beta})} key={tool.id}>
|
|
||||||
<div className='content'>
|
|
||||||
{tool.icon}
|
|
||||||
<h2>{tool.name}</h2>
|
|
||||||
<p>{tool.desc}</p>
|
|
||||||
</div>
|
|
||||||
</a>;
|
|
||||||
},
|
|
||||||
|
|
||||||
renderTools : function(){
|
|
||||||
return _.map(this.props.tools, (tool)=>{
|
|
||||||
return this.renderTool(tool);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='main'>
|
|
||||||
<div className='top'>
|
|
||||||
<div className='logo'>
|
|
||||||
<NaturalCritIcon />
|
|
||||||
<span className='name'>
|
|
||||||
Natural
|
|
||||||
<span className='crit'>Crit</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p>Top-tier tools for the discerning DM</p>
|
|
||||||
</div>
|
|
||||||
<div className='tools'>
|
|
||||||
{this.renderTools()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Main;
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
@import 'naturalcrit/styles/core.less';
|
|
||||||
.main{
|
|
||||||
height : 100vh;
|
|
||||||
background-color : white;
|
|
||||||
.top{
|
|
||||||
.fadeInTop(1s);
|
|
||||||
.delay(0.5);
|
|
||||||
margin-bottom : 100px;
|
|
||||||
padding-top : 100px;
|
|
||||||
text-align : center;
|
|
||||||
.logo{
|
|
||||||
font-size : 4em;
|
|
||||||
color : black;
|
|
||||||
svg{
|
|
||||||
height : .9em;
|
|
||||||
margin-right : .2em;
|
|
||||||
cursor : pointer;
|
|
||||||
fill : black;
|
|
||||||
}
|
|
||||||
.name{
|
|
||||||
font-family : 'CodeLight';
|
|
||||||
.crit{
|
|
||||||
font-family : 'CodeBold';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
margin-top : 10px;
|
|
||||||
font-size : 1.3em;
|
|
||||||
font-style : italic;
|
|
||||||
color : @grey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tools{
|
|
||||||
width : 100%;
|
|
||||||
text-align : center;
|
|
||||||
.tool{
|
|
||||||
.sequentialDelay(0.5s, 1s);
|
|
||||||
.fadeInDown(1s);
|
|
||||||
.keep();
|
|
||||||
display : inline-block;
|
|
||||||
cursor : pointer;
|
|
||||||
opacity : 0;
|
|
||||||
color : black;
|
|
||||||
text-align : center;
|
|
||||||
text-decoration : none;
|
|
||||||
&+.tool{
|
|
||||||
border-left : 1px solid #666;
|
|
||||||
}
|
|
||||||
.content{
|
|
||||||
.addSketch(360px);
|
|
||||||
.animateAll(0.5s);
|
|
||||||
position : relative;
|
|
||||||
width : 320px;
|
|
||||||
padding : 35px;
|
|
||||||
&:hover{
|
|
||||||
svg, h2{
|
|
||||||
.transform(scale(1.3));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h2{
|
|
||||||
.animateAll(0.5s);
|
|
||||||
font-family : 'CodeBold';
|
|
||||||
font-size : 2em;
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
max-width : 300px;
|
|
||||||
margin : 20px auto;
|
|
||||||
line-height : 1.5em;
|
|
||||||
}
|
|
||||||
svg{
|
|
||||||
.animateAll(0.5s);
|
|
||||||
height : 10em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content:hover{
|
|
||||||
background-color : fade(@teal, 20%);
|
|
||||||
}
|
|
||||||
//Beta styles
|
|
||||||
&.beta{
|
|
||||||
cursor : initial;
|
|
||||||
.content{
|
|
||||||
&:hover{
|
|
||||||
svg, h2{
|
|
||||||
.transform(scale(1.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
svg, h2{
|
|
||||||
opacity : 0.3;
|
|
||||||
}
|
|
||||||
&:after{
|
|
||||||
.animateAll();
|
|
||||||
content : "beta!";
|
|
||||||
position : absolute;
|
|
||||||
display : block;
|
|
||||||
top : 120px;
|
|
||||||
left : 0px;
|
|
||||||
width : 100%;
|
|
||||||
padding : 10px 0px;
|
|
||||||
//opacity : 0;
|
|
||||||
background-color : fade(@grey, 50%);
|
|
||||||
font-size : 2em;
|
|
||||||
font-weight : 800;
|
|
||||||
text-align : center;
|
|
||||||
text-transform : uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.addSketch(@length, @color : black){
|
|
||||||
path, line, polyline, circle, rect, polygon {
|
|
||||||
.sketch(@length, @color, 4s);
|
|
||||||
stroke-dasharray : @length;
|
|
||||||
stroke-dashoffset : 0px;
|
|
||||||
stroke : @color;
|
|
||||||
stroke-width : 0.5px;
|
|
||||||
fill : @color;
|
|
||||||
//.animateAll(3s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing){
|
|
||||||
.createAnimation(sketch, @duration, @easing);
|
|
||||||
.sketchKeyFrames(){
|
|
||||||
0% { stroke-dashoffset : @length; fill: transparent;}
|
|
||||||
50% { stroke-dashoffset : @length; fill: transparent;}
|
|
||||||
80% { stroke-dashoffset : 0px; fill: transparent;}
|
|
||||||
100% { stroke-dashoffset : 0px; fill:@color;}
|
|
||||||
}
|
|
||||||
@-webkit-keyframes sketch {.sketchKeyFrames();}
|
|
||||||
@-moz-keyframes sketch {.sketchKeyFrames();}
|
|
||||||
@-ms-keyframes sketch {.sketchKeyFrames();}
|
|
||||||
@-o-keyframes sketch {.sketchKeyFrames();}
|
|
||||||
@keyframes sketch {.sketchKeyFrames();}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script>global=window</script>
|
|
||||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.6.2/css/font-awesome.min.css" rel="stylesheet" />
|
|
||||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
|
||||||
<link rel="icon" href="/assets/main/favicon.ico" type="image/x-icon" />
|
|
||||||
{{=vitreum.css}}
|
|
||||||
{{=vitreum.globals}}
|
|
||||||
<title>Natural Crit - D&D Tools</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="reactContainer">{{=vitreum.component}}</div>
|
|
||||||
</body>
|
|
||||||
{{=vitreum.libs}}
|
|
||||||
{{=vitreum.js}}
|
|
||||||
{{=vitreum.reactRender}}
|
|
||||||
|
|
||||||
{{? vitreum.inProduction}}
|
|
||||||
<script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','http://www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-72212009-1', 'auto');
|
|
||||||
ga('send', 'pageview');
|
|
||||||
</script>
|
|
||||||
{{?}}
|
|
||||||
</html>
|
|
||||||
21
client/template.js
Normal file
21
client/template.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module.exports = function(vitreum){
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||||
|
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||||
|
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
||||||
|
<title>The Homebrewery - NaturalCrit</title>
|
||||||
|
${vitreum.head}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main id="reactRoot">${vitreum.body}</main>
|
||||||
|
</body>
|
||||||
|
${vitreum.js}
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
|
|
||||||
var Navbar = React.createClass({
|
|
||||||
render : function(){
|
|
||||||
return <Nav.base>
|
|
||||||
<Nav.section>
|
|
||||||
<Nav.logo />
|
|
||||||
<Nav.item href='/tpk' className='tpkLogo'>
|
|
||||||
<div>Total Player Knolling</div>
|
|
||||||
</Nav.item>
|
|
||||||
<Nav.item>v0.0.0</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
{this.props.children}
|
|
||||||
</Nav.base>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Navbar;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
|
|
||||||
.tpk nav{
|
|
||||||
.tpkLogo{
|
|
||||||
.animate(color);
|
|
||||||
font-family : CodeBold;
|
|
||||||
font-size : 12px;
|
|
||||||
color : white;
|
|
||||||
div{
|
|
||||||
margin-top : 2px;
|
|
||||||
margin-bottom : -2px;
|
|
||||||
}
|
|
||||||
&:hover{
|
|
||||||
color : @teal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
|
||||||
|
|
||||||
var SheetEditor = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
value : "",
|
|
||||||
onChange : function(){}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
cursorPosition : {
|
|
||||||
line : 0,
|
|
||||||
ch : 0
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
var paneHeight = this.refs.main.parentNode.clientHeight;
|
|
||||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.props.onChange(text);
|
|
||||||
},
|
|
||||||
handleCursorActivty : function(curpos){
|
|
||||||
this.cursorPosition = curpos;
|
|
||||||
},
|
|
||||||
|
|
||||||
//Called when there are changes to the editor's dimensions
|
|
||||||
update : function(){
|
|
||||||
this.refs.codeEditor.updateSize();
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='sheetEditor' ref='main'>
|
|
||||||
<CodeEditor
|
|
||||||
ref='codeEditor'
|
|
||||||
wrap={true}
|
|
||||||
language='jsx'
|
|
||||||
value={this.props.value}
|
|
||||||
onChange={this.handleTextChange}
|
|
||||||
onCursorActivity={this.handleCursorActivty} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = SheetEditor;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.COM{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var utils = require('../utils');
|
|
||||||
|
|
||||||
var Box = React.createClass({
|
|
||||||
mixins : [utils],
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
//name : 'box',
|
|
||||||
defaultData : {},
|
|
||||||
|
|
||||||
id : '',
|
|
||||||
title : '',
|
|
||||||
label : '',
|
|
||||||
shadow : false,
|
|
||||||
border : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleChange : function(newData){
|
|
||||||
this.updateData(newData);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderChildren : function(){
|
|
||||||
return React.Children.map(this.props.children, (child)=>{
|
|
||||||
if(!React.isValidElement(child)) return null;
|
|
||||||
return React.cloneElement(child, {
|
|
||||||
onChange : this.handleChange,
|
|
||||||
data : this.data()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
renderTitle : function(){
|
|
||||||
if(this.props.title) return <h5 className='title'>{this.props.title}</h5>
|
|
||||||
},
|
|
||||||
renderLabel : function(){
|
|
||||||
if(this.props.label) return <h5 className='label'>{this.props.label}</h5>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className={cx('box', this.props.className, {
|
|
||||||
shadow : this.props.shadow,
|
|
||||||
border : this.props.border
|
|
||||||
})}>
|
|
||||||
{this.renderTitle()}
|
|
||||||
{this.renderChildren()}
|
|
||||||
{this.renderLabel()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Box;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
.box{
|
|
||||||
position : relative;
|
|
||||||
padding : 10px;
|
|
||||||
margin: 10px;
|
|
||||||
|
|
||||||
&.border{
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
&.shadow{
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
h5{
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size : 0.6em;
|
|
||||||
text-align: center;
|
|
||||||
width : 100%;
|
|
||||||
font-weight: 800;
|
|
||||||
|
|
||||||
&.title{
|
|
||||||
margin-top: -5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
&.label{
|
|
||||||
margin-bottom: -5px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
TextInput : require('./textInput/textInput.jsx'),
|
|
||||||
PlayerInfo : require('./playerInfo/playerInfo.jsx'),
|
|
||||||
|
|
||||||
SkillList : require('./skillList/skillList.jsx'),
|
|
||||||
Skill : require('./skill/skill.jsx'),
|
|
||||||
|
|
||||||
//ShadowBox : require('./shadowBox/shadowBox.jsx'),
|
|
||||||
//BorderBox : require('./borderBox/borderBox.jsx'),
|
|
||||||
|
|
||||||
|
|
||||||
Box : require('./box/box.jsx')
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var TextInput = require('../textInput/textInput.jsx');
|
|
||||||
var Box = require('../box/box.jsx');
|
|
||||||
|
|
||||||
var PlayerInfo = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
title: "player info",
|
|
||||||
border : true
|
|
||||||
};
|
|
||||||
},
|
|
||||||
render : function(){
|
|
||||||
return <Box className='playerInfo' {...this.props} >
|
|
||||||
<TextInput label="Name" placeholder="name" />
|
|
||||||
<TextInput label="Class" />
|
|
||||||
<TextInput label="Race" />
|
|
||||||
{this.props.children}
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = PlayerInfo;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.playerInfo{
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var utils = require('../utils');
|
|
||||||
|
|
||||||
var Skill = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
name : 'skill',
|
|
||||||
defaultData : {
|
|
||||||
prof : false,
|
|
||||||
expert : false,
|
|
||||||
val : ''
|
|
||||||
},
|
|
||||||
|
|
||||||
id : '',
|
|
||||||
label : '',
|
|
||||||
sublabel : '',
|
|
||||||
showExpert : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
id : utils.id,
|
|
||||||
data : utils.data,
|
|
||||||
updateData : utils.updateData,
|
|
||||||
|
|
||||||
handleToggleProf : function(){
|
|
||||||
this.updateData({
|
|
||||||
prof : !this.data().prof
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleToggleExpert : function(){
|
|
||||||
this.updateData({
|
|
||||||
expert : !this.data().expert
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleValChange : function(e){
|
|
||||||
console.log('yo');
|
|
||||||
this.updateData({
|
|
||||||
val : e.target.value
|
|
||||||
})
|
|
||||||
},
|
|
||||||
renderExpert : function(){
|
|
||||||
if(this.props.showExpert){
|
|
||||||
return <input type="radio" className='expertToggle' onChange={this.handleToggleExpert} checked={this.data().expert} />
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render : function(){
|
|
||||||
return <div className='skill'>
|
|
||||||
{this.renderExpert()}
|
|
||||||
<input type="radio" className='skillToggle' onChange={this.handleToggleProf} checked={this.data().prof} />
|
|
||||||
<input type='text' onChange={this.handleValChange} value={this.data().val} />
|
|
||||||
<label>
|
|
||||||
{this.props.label}
|
|
||||||
<small>{this.props.sublabel}</small>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Skill;
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
|
|
||||||
.skill{
|
|
||||||
position : relative;
|
|
||||||
padding-left : 15px;
|
|
||||||
input[type="radio"]{
|
|
||||||
margin : 0px;
|
|
||||||
}
|
|
||||||
.expertToggle{
|
|
||||||
position : absolute;
|
|
||||||
top : 1px;
|
|
||||||
left : 0px;
|
|
||||||
}
|
|
||||||
input[type="text"]{
|
|
||||||
width : 25px;
|
|
||||||
margin-left : 10px;
|
|
||||||
background-color : transparent;
|
|
||||||
text-align : center;
|
|
||||||
border : none;
|
|
||||||
border-bottom : 1px solid black;
|
|
||||||
outline : none;
|
|
||||||
&:focus{
|
|
||||||
background-color : #ddd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
label{
|
|
||||||
margin-left : 10px;
|
|
||||||
font-size : 0.8em;
|
|
||||||
small{
|
|
||||||
margin-left : 5px;
|
|
||||||
font-size : 0.8em;
|
|
||||||
color : #999;
|
|
||||||
text-transform : uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var Skill = require('../skill/skill.jsx');
|
|
||||||
var Box = require('../box/box.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
var skill_list = [
|
|
||||||
{name : 'Acrobatics', stat : 'Dex'},
|
|
||||||
{name : 'Animal Handling', stat : 'Wis'},
|
|
||||||
{name : 'Arcana', stat : 'Int'},
|
|
||||||
{name : 'Athletics', stat : 'Str'},
|
|
||||||
{name : 'Deception', stat : 'Cha'},
|
|
||||||
{name : 'History', stat : 'Int'},
|
|
||||||
{name : 'Insight', stat : 'Wis'},
|
|
||||||
{name : 'Intimidation', stat : 'Cha'},
|
|
||||||
{name : 'Investigation', stat : 'Int'},
|
|
||||||
{name : 'Medicine', stat : 'Wis'},
|
|
||||||
{name : 'Nature', stat : 'Int'},
|
|
||||||
{name : 'Perception', stat : 'Wis'},
|
|
||||||
{name : 'Performance', stat : 'Cha'},
|
|
||||||
{name : 'Persuasion', stat : 'Cha'},
|
|
||||||
{name : 'Religion', stat : 'Int'},
|
|
||||||
{name : 'Sleight of Hand', stat : 'Dex'},
|
|
||||||
{name : 'Stealth', stat : 'Dex'},
|
|
||||||
{name : 'Survival', stat : 'Wis'}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
var SkillList = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
name : 'skills',
|
|
||||||
|
|
||||||
//title : 'Skills',
|
|
||||||
shadow : true,
|
|
||||||
border : false,
|
|
||||||
showExpert : false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
renderSkills : function(){
|
|
||||||
return _.map(skill_list, (skill)=>{
|
|
||||||
return <Skill
|
|
||||||
label={skill.name}
|
|
||||||
sublabel={'(' + skill.stat + ')'}
|
|
||||||
showExpert={this.props.showExpert} />
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <Box className='skillList' {...this.props}>
|
|
||||||
{this.renderSkills()}
|
|
||||||
{this.props.children}
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = SkillList;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.COM{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var utils = require('../utils');
|
|
||||||
|
|
||||||
var TextInput = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
name : 'text',
|
|
||||||
defaultData : '',
|
|
||||||
|
|
||||||
id : '',
|
|
||||||
label : '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
id : utils.id,
|
|
||||||
data : utils.data,
|
|
||||||
updateData : utils.updateData,
|
|
||||||
|
|
||||||
handleChange : function(e){
|
|
||||||
this.updateData(e.target.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderLabel : function(){
|
|
||||||
if(this.props.label) return <label htmlFor={this.id()}>{this.props.label}</label>
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='textInput'>
|
|
||||||
{this.renderLabel()}
|
|
||||||
<input
|
|
||||||
id={this.id()}
|
|
||||||
type='text'
|
|
||||||
onChange={this.handleChange}
|
|
||||||
value={this.data()}
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = TextInput;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
.textInput{
|
|
||||||
label{
|
|
||||||
display: inline-block;
|
|
||||||
width : 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
id : function(){
|
|
||||||
if(this.props.id) return this.props.id;
|
|
||||||
if(this.props.label) return _.snakeCase(this.props.label);
|
|
||||||
if(this.props.title) return _.snakeCase(this.props.title);
|
|
||||||
return this.props.name;
|
|
||||||
},
|
|
||||||
data : function(){
|
|
||||||
if(!this.id()) return this.props.data || this.props.defaultData;
|
|
||||||
if(this.props.data && this.props.data[this.id()]) return this.props.data[this.id()];
|
|
||||||
return this.props.defaultData;
|
|
||||||
},
|
|
||||||
updateData : function(val){
|
|
||||||
if(typeof this.props.onChange !== 'function') throw "No onChange handler set";
|
|
||||||
|
|
||||||
var newVal = val;
|
|
||||||
|
|
||||||
//Clone the data if it's an object to avoid mutation bugs
|
|
||||||
if(_.isObject(val)) newVal = _.extend({}, this.data(), val);
|
|
||||||
|
|
||||||
if(this.id()){
|
|
||||||
this.props.onChange({
|
|
||||||
[this.id()] : newVal
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
//If the box has no id, don't add it to the chain
|
|
||||||
this.props.onChange(newVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var jsx2json = require('naturalcrit/jsx-parser');
|
|
||||||
|
|
||||||
var Parts = require('./parts');
|
|
||||||
|
|
||||||
|
|
||||||
var SheetRenderer = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
code : '',
|
|
||||||
characterData : {},
|
|
||||||
onChange : function(){},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
renderElement : function(node, key){
|
|
||||||
if(!node.tag) return null;
|
|
||||||
|
|
||||||
if(!Parts[node.tag]) throw 'Could Not Find Element: ' + node.tag
|
|
||||||
|
|
||||||
return React.createElement(
|
|
||||||
Parts[node.tag],
|
|
||||||
{key : key, ...node.props},
|
|
||||||
...this.renderChildren(node.children))
|
|
||||||
},
|
|
||||||
renderChildren : function(nodes){
|
|
||||||
return _.map(nodes, (node, index)=>{
|
|
||||||
if(_.isString(node)) return node;
|
|
||||||
return this.renderElement(node, index);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
renderSheet : function(){
|
|
||||||
try{
|
|
||||||
var nodes = jsx2json(this.props.code);
|
|
||||||
nodes = _.map(nodes, (node)=>{
|
|
||||||
node.props.data = this.props.characterData;
|
|
||||||
node.props.onChange = (newData)=>{
|
|
||||||
this.props.onChange(_.extend(this.props.characterData, newData));
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
})
|
|
||||||
return this.renderChildren(nodes);
|
|
||||||
}catch(e){
|
|
||||||
return <div>Error bruh {e.toString()}</div>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='sheetRenderer'>
|
|
||||||
|
|
||||||
<h2>Character Sheet</h2>
|
|
||||||
|
|
||||||
<div className='sheetContainer' ref='sheetContainer'>
|
|
||||||
{this.renderSheet()}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = SheetRenderer;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
<Temp text="cool">yo test <a href="google.com">link</a> </Temp>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
.sheetRenderer{
|
|
||||||
|
|
||||||
padding-right : 10px;
|
|
||||||
|
|
||||||
|
|
||||||
.sheetContainer{
|
|
||||||
background-color: white;
|
|
||||||
padding : 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
var React = require('react');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var cx = require('classnames');
|
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
|
||||||
var Navbar = require('./navbar/navbar.jsx');
|
|
||||||
|
|
||||||
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
|
||||||
var SheetEditor = require('./sheetEditor/sheetEditor.jsx');
|
|
||||||
var SheetRenderer = require('./sheetRenderer/sheetRenderer.jsx');
|
|
||||||
|
|
||||||
|
|
||||||
const SPLATSHEET_TEMPLATE = 'splatsheet_template';
|
|
||||||
const SPLATSHEET_DATA = 'splatsheet_data';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var TPK = React.createClass({
|
|
||||||
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
sheetCode: "<Box>\n\t<TextInput label='test' />\n</Box>",
|
|
||||||
|
|
||||||
sheetData : {}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
//remove later
|
|
||||||
componentDidMount: function() {
|
|
||||||
this.setState({
|
|
||||||
sheetCode : localStorage.getItem(SPLATSHEET_TEMPLATE) || this.state.sheetCode,
|
|
||||||
sheetData : JSON.parse(localStorage.getItem(SPLATSHEET_DATA)) || this.state.sheetData
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSplitMove : function(){
|
|
||||||
this.refs.editor.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCodeChange : function(code){
|
|
||||||
this.setState({
|
|
||||||
sheetCode : code
|
|
||||||
});
|
|
||||||
|
|
||||||
localStorage.setItem(SPLATSHEET_TEMPLATE, code);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleDataChange : function(data){
|
|
||||||
this.setState({
|
|
||||||
sheetData : JSON.parse(JSON.stringify(data)),
|
|
||||||
});
|
|
||||||
localStorage.setItem(SPLATSHEET_DATA, JSON.stringify(data));
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
|
||||||
return <div className='tpk page'>
|
|
||||||
<Navbar>
|
|
||||||
<Nav.section>
|
|
||||||
<Nav.item>
|
|
||||||
yo dawg
|
|
||||||
</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
</Navbar>
|
|
||||||
<div className='content'>
|
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
|
||||||
<SheetEditor value={this.state.sheetCode} onChange={this.handleCodeChange} ref='editor' />
|
|
||||||
<SheetRenderer
|
|
||||||
code={this.state.sheetCode}
|
|
||||||
characterData={this.state.sheetData}
|
|
||||||
onChange={this.handleDataChange} />
|
|
||||||
</SplitPane>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = TPK;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
@import 'naturalcrit/styles/core.less';
|
|
||||||
.tpk{
|
|
||||||
height : 100%;
|
|
||||||
background-color : @steel;
|
|
||||||
.page{
|
|
||||||
display : flex;
|
|
||||||
height : 100%;
|
|
||||||
flex-direction : column;
|
|
||||||
.content{
|
|
||||||
position : relative;
|
|
||||||
//height : calc(~"100% - 29px"); //Navbar height
|
|
||||||
|
|
||||||
height : 100%;
|
|
||||||
|
|
||||||
flex : auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
config/default.json
Normal file
10
config/default.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"log_level" : "info",
|
||||||
|
"login_path" : "/dev/login",
|
||||||
|
"jwt_secret" : "secretsecret",
|
||||||
|
"admin" : {
|
||||||
|
"user" : "admin",
|
||||||
|
"pass" : "password",
|
||||||
|
"key" : "adminadminadmin"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
config/production.json
Normal file
4
config/production.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"login_path" : "http://naturalcrit.com/login",
|
||||||
|
"log_level" : "warn"
|
||||||
|
}
|
||||||
4
config/staging.json
Normal file
4
config/staging.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"login_path" : "http://staging.naturalcrit.com/login",
|
||||||
|
"log_level" : "trace"
|
||||||
|
}
|
||||||
53
gulpfile.js
53
gulpfile.js
@@ -1,53 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
var vitreumTasks = require("vitreum/tasks");
|
|
||||||
var gulp = require("gulp");
|
|
||||||
|
|
||||||
|
|
||||||
var gulp = vitreumTasks(gulp, {
|
|
||||||
entryPoints: [
|
|
||||||
'./client/main',
|
|
||||||
'./client/homebrew',
|
|
||||||
'./client/tpk',
|
|
||||||
'./client/admin'
|
|
||||||
],
|
|
||||||
|
|
||||||
DEV: true,
|
|
||||||
buildPath: "./build/",
|
|
||||||
pageTemplate: "./client/template.dot",
|
|
||||||
projectModules: ["./shared/naturalcrit","./shared/codemirror"],
|
|
||||||
additionalRequirePaths : ['./shared', './node_modules'],
|
|
||||||
assetExts: ["*.svg", "*.png", "*.jpg", "*.pdf", "*.eot", "*.otf", "*.woff", "*.woff2", "*.ico", "*.ttf"],
|
|
||||||
serverWatchPaths: ["server"],
|
|
||||||
serverScript: "server.js",
|
|
||||||
libs: [
|
|
||||||
"react",
|
|
||||||
"react-dom",
|
|
||||||
"lodash",
|
|
||||||
"classnames",
|
|
||||||
|
|
||||||
//From ./shared
|
|
||||||
"codemirror",
|
|
||||||
"codemirror/mode/gfm/gfm.js",
|
|
||||||
'codemirror/mode/javascript/javascript.js',
|
|
||||||
'codemirror/mode/jsx/jsx.js',
|
|
||||||
|
|
||||||
"moment",
|
|
||||||
"superagent",
|
|
||||||
"marked",
|
|
||||||
"pico-router",
|
|
||||||
"pico-flux"
|
|
||||||
],
|
|
||||||
clientLibs: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var rename = require('gulp-rename');
|
|
||||||
var less = require('gulp-less');
|
|
||||||
gulp.task('phb', function(){
|
|
||||||
gulp.src('./client/homebrew/phbStyle/phb.style.less')
|
|
||||||
.pipe(less())
|
|
||||||
.pipe(rename('phb.standalone.css'))
|
|
||||||
.pipe(gulp.dest('./'));
|
|
||||||
})
|
|
||||||
|
|
||||||
21
license
Normal file
21
license
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 Scott Tolksdorf
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
47
package.json
47
package.json
@@ -1,31 +1,52 @@
|
|||||||
{
|
{
|
||||||
"name": "naturalcrit",
|
"name": "homebrewery",
|
||||||
"description": "D&D Tools for the discerning DM",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "gulp prod",
|
"dev": "node scripts/dev.js",
|
||||||
"start": "node server.js"
|
"quick": "node scripts/quick.js",
|
||||||
|
"build": "node scripts/build.js",
|
||||||
|
"phb": "node scripts/phb.js",
|
||||||
|
"populate": "node scripts/populate.js",
|
||||||
|
"prod": "set NODE_ENV=production&& npm run build",
|
||||||
|
"postinstall": "npm run build",
|
||||||
|
"start": "node server.js",
|
||||||
|
"test": "mocha test",
|
||||||
|
"test:dev": "nodemon -x mocha test || exit 0"
|
||||||
},
|
},
|
||||||
"author": "stolksdorf",
|
"author": "stolksdorf",
|
||||||
"license": "BSD-2-Clause",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-module-path": "^1.0.4",
|
|
||||||
"basic-auth": "^1.0.3",
|
"basic-auth": "^1.0.3",
|
||||||
"body-parser": "^1.14.2",
|
"body-parser": "^1.14.2",
|
||||||
"classnames": "^2.2.0",
|
"classnames": "^2.2.0",
|
||||||
|
"codemirror": "^5.22.0",
|
||||||
|
"cookie-parser": "^1.4.3",
|
||||||
|
"egads": "^1.0.1",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"gulp": "^3.9.0",
|
"jwt-simple": "^0.5.1",
|
||||||
"lodash": "^4.11.2",
|
"lodash": "^4.17.3",
|
||||||
|
"loglevel": "^1.4.1",
|
||||||
"marked": "^0.3.5",
|
"marked": "^0.3.5",
|
||||||
"moment": "^2.11.0",
|
"moment": "^2.11.0",
|
||||||
"mongoose": "^4.3.3",
|
"mongoose": "^4.3.3",
|
||||||
"pico-flux": "^1.1.0",
|
"nconf": "^0.8.4",
|
||||||
|
"pico-flux": "^2.1.2",
|
||||||
"pico-router": "^1.1.0",
|
"pico-router": "^1.1.0",
|
||||||
"react": "^15.0.2",
|
"react": "^15.4.1",
|
||||||
"react-dom": "^15.0.2",
|
"react-dom": "^15.4.1",
|
||||||
"shortid": "^2.2.4",
|
"shortid": "^2.2.4",
|
||||||
"striptags": "^2.1.1",
|
"striptags": "^2.1.1",
|
||||||
"superagent": "^1.6.1",
|
"superagent": "^1.6.1",
|
||||||
"vitreum": "^3.2.1"
|
"vitreum": "^4.0.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"app-module-path": "^2.1.0",
|
||||||
|
"chai": "^3.5.0",
|
||||||
|
"chai-as-promised": "^6.0.0",
|
||||||
|
"chai-subset": "^1.4.0",
|
||||||
|
"mocha": "^3.2.0",
|
||||||
|
"supertest": "^2.0.1",
|
||||||
|
"supertest-as-promised": "^4.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
20
scripts/build.js
Normal file
20
scripts/build.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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);
|
||||||
21
scripts/dev.js
Normal file
21
scripts/dev.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const label = 'dev';
|
||||||
|
console.time(label);
|
||||||
|
|
||||||
|
const jsx = require('vitreum/steps/jsx.watch.js').partial;
|
||||||
|
const less = require('vitreum/steps/less.watch.js').partial;
|
||||||
|
const assets = require('vitreum/steps/assets.watch.js').partial;
|
||||||
|
const server = require('vitreum/steps/server.watch.js').partial;
|
||||||
|
const livereload = require('vitreum/steps/livereload.js').partial;
|
||||||
|
|
||||||
|
const Proj = require('./project.json');
|
||||||
|
|
||||||
|
Promise.resolve()
|
||||||
|
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, './shared'))
|
||||||
|
.then(less('homebrew', './shared'))
|
||||||
|
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
|
||||||
|
.then(less('admin', './shared'))
|
||||||
|
.then(assets(Proj.assets, ['./shared', './client']))
|
||||||
|
.then(livereload())
|
||||||
|
.then(server('./server.js', ['server']))
|
||||||
|
.then(console.timeEnd.bind(console, label))
|
||||||
|
.catch(console.error)
|
||||||
10
scripts/phb.js
Normal file
10
scripts/phb.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const less = require('less');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
less.render(fs.readFileSync('./client/homebrew/phbStyle/phb.style.less', 'utf8'), {compress : true})
|
||||||
|
.then((output) => {
|
||||||
|
fs.writeFileSync('./phb.standalone.css', output.css);
|
||||||
|
console.log('phb.standalone.css created!');
|
||||||
|
}, (err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
22
scripts/populate.js
Normal file
22
scripts/populate.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//Populates the DB with a bunch of brews for UI testing
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const DB = require('../server/db.js');
|
||||||
|
const BrewData = require('../server/brew.data.js');
|
||||||
|
const BrewGen = require('../test/brew.gen.js');
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(DB.connect)
|
||||||
|
.then(BrewData.removeAll)
|
||||||
|
.then(() => {
|
||||||
|
console.log('Adding random brews...');
|
||||||
|
return BrewGen.populateDB(BrewGen.random(50));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('Adding specific brews...');
|
||||||
|
return BrewGen.populateDB(BrewGen.static());
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return DB.close();
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
19
scripts/project.json
Normal file
19
scripts/project.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"assets": ["*.png","*.otf", "*.ico"],
|
||||||
|
"shared":[
|
||||||
|
],
|
||||||
|
"libs" : [
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"lodash",
|
||||||
|
"classnames",
|
||||||
|
"codemirror",
|
||||||
|
"codemirror/mode/gfm/gfm.js",
|
||||||
|
"codemirror/mode/javascript/javascript.js",
|
||||||
|
"moment",
|
||||||
|
"superagent",
|
||||||
|
"marked",
|
||||||
|
"pico-router",
|
||||||
|
"pico-flux"
|
||||||
|
]
|
||||||
|
}
|
||||||
17
scripts/quick.js
Normal file
17
scripts/quick.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const label = 'quick';
|
||||||
|
console.time(label);
|
||||||
|
|
||||||
|
const jsx = require('vitreum/steps/jsx.js').partial;
|
||||||
|
const less = require('vitreum/steps/less.js').partial;
|
||||||
|
const server = require('vitreum/steps/server.watch.js').partial;
|
||||||
|
|
||||||
|
const Proj = require('./project.json');
|
||||||
|
|
||||||
|
Promise.resolve()
|
||||||
|
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
|
||||||
|
.then(less('homebrew', ['./shared']))
|
||||||
|
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
|
||||||
|
.then(less('admin', ['./shared']))
|
||||||
|
.then(server('./server.js', ['server']))
|
||||||
|
.then(console.timeEnd.bind(console, label))
|
||||||
|
.catch(console.error);
|
||||||
92
server.js
92
server.js
@@ -1,69 +1,35 @@
|
|||||||
'use strict';
|
const config = require('nconf')
|
||||||
var _ = require('lodash');
|
.argv()
|
||||||
require('app-module-path').addPath('./shared');
|
.env({ lowerCase: true })
|
||||||
var vitreumRender = require('vitreum/render');
|
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
|
||||||
var bodyParser = require('body-parser')
|
.file('defaults', { file: 'config/default.json' });
|
||||||
var express = require("express");
|
|
||||||
var app = express();
|
|
||||||
app.use(express.static(__dirname + '/build'));
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
|
|
||||||
//Mongoose
|
const log = require('loglevel');
|
||||||
var mongoose = require('mongoose');
|
log.setLevel(config.get('log_level'));
|
||||||
var mongoUri = process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || 'mongodb://localhost/naturalcrit';
|
|
||||||
mongoose.connect(mongoUri);
|
|
||||||
mongoose.connection.on('error', function(){
|
|
||||||
console.log(">>>ERROR: Run Mongodb.exe ya goof!");
|
|
||||||
});
|
|
||||||
|
|
||||||
//Admin route
|
//DB
|
||||||
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
require('./server/db.js').connect();
|
||||||
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password';
|
|
||||||
process.env.ADMIN_KEY = process.env.ADMIN_KEY || 'admin_key';
|
//Server
|
||||||
var auth = require('basic-auth');
|
const app = require('./server/app.js');
|
||||||
app.get('/admin', function(req, res){
|
|
||||||
var credentials = auth(req)
|
/*
|
||||||
if (!credentials || credentials.name !== process.env.ADMIN_USER || credentials.pass !== process.env.ADMIN_PASS) {
|
app.use((req, res, next) => {
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="example"')
|
log.debug('---------------------------');
|
||||||
return res.status(401).send('Access denied')
|
log.debug(req.method, req.path);
|
||||||
|
|
||||||
|
if (req.params) {
|
||||||
|
log.debug('req params', req.params);
|
||||||
}
|
}
|
||||||
vitreumRender({
|
if (req.query) {
|
||||||
page: './build/admin/bundle.dot',
|
log.debug('req query', req.query);
|
||||||
prerenderWith : './client/admin/admin.jsx',
|
}
|
||||||
clearRequireCache : !process.env.PRODUCTION,
|
|
||||||
initialProps: {
|
next();
|
||||||
url: req.originalUrl,
|
|
||||||
admin_key : process.env.ADMIN_KEY,
|
|
||||||
},
|
|
||||||
}, function (err, page) {
|
|
||||||
return res.send(page)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 8000;
|
||||||
//Populate homebrew routes
|
const httpServer = app.listen(PORT, () => {
|
||||||
app = require('./server/homebrew.api.js')(app);
|
log.info(`server on port:${PORT}`);
|
||||||
app = require('./server/homebrew.server.js')(app);
|
|
||||||
|
|
||||||
//Populate TPK routes
|
|
||||||
app = require('./server/tpk.server.js')(app);
|
|
||||||
|
|
||||||
|
|
||||||
app.get('*', function (req, res) {
|
|
||||||
vitreumRender({
|
|
||||||
page: './build/main/bundle.dot',
|
|
||||||
globals:{},
|
|
||||||
prerenderWith : './client/main/main.jsx',
|
|
||||||
initialProps: {
|
|
||||||
url: req.originalUrl
|
|
||||||
},
|
|
||||||
clearRequireCache : !process.env.PRODUCTION,
|
|
||||||
}, function (err, page) {
|
|
||||||
return res.send(page)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var port = process.env.PORT || 8000;
|
|
||||||
app.listen(port);
|
|
||||||
console.log('Listening on localhost:' + port);
|
|
||||||
60
server/admin.routes.js
Normal file
60
server/admin.routes.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const router = require('express').Router();
|
||||||
|
const vitreumRender = require('vitreum/steps/render');
|
||||||
|
const templateFn = require('../client/template.js');
|
||||||
|
const config = require('nconf');
|
||||||
|
const Moment = require('moment');
|
||||||
|
|
||||||
|
const mw = require('./middleware.js');
|
||||||
|
const BrewData = require('./brew.data.js');
|
||||||
|
|
||||||
|
|
||||||
|
const getInvalidBrewQuery = ()=>{
|
||||||
|
return BrewData.model.find({
|
||||||
|
'$where' : "this.text.length < 140",
|
||||||
|
createdAt: {
|
||||||
|
$lt: Moment().subtract(3, 'days').toDate()
|
||||||
|
}
|
||||||
|
}).select({ text : false });
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/admin', mw.adminLogin, (req, res, next) => {
|
||||||
|
return vitreumRender('admin', templateFn, {
|
||||||
|
url : req.originalUrl,
|
||||||
|
admin_key : config.get('admin:key')
|
||||||
|
})
|
||||||
|
.then((page) => {
|
||||||
|
return res.send(page)
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
|
||||||
|
router.get('/admin/invalid', mw.adminOnly, (req, res, next)=>{
|
||||||
|
getInvalidBrewQuery().exec()
|
||||||
|
.then((brews) => {
|
||||||
|
return res.json(brews);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
router.delete('/admin/invalid', mw.adminOnly, (req, res, next)=>{
|
||||||
|
getInvalidBrewQuery().remove()
|
||||||
|
.then(()=>{
|
||||||
|
return res.status(200).send();
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/admin/lookup/:search', mw.adminOnly, (req, res, next) => {
|
||||||
|
BrewData.get({ $or:[
|
||||||
|
//Searches for partial matches on either edit or share
|
||||||
|
{editId : { "$regex": req.params.search, "$options": "i" }},
|
||||||
|
{shareId : { "$regex": req.params.search, "$options": "i" }},
|
||||||
|
]})
|
||||||
|
.then((brews) => {
|
||||||
|
return res.json(brews);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
28
server/app.js
Normal file
28
server/app.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const config = require('nconf');
|
||||||
|
const express = require("express");
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(express.static(__dirname + '/../build'));
|
||||||
|
app.use(require('body-parser').json({limit: '25mb'}));
|
||||||
|
app.use(require('cookie-parser')());
|
||||||
|
|
||||||
|
|
||||||
|
//Middleware
|
||||||
|
const mw = require('./middleware.js');
|
||||||
|
app.use(mw.account);
|
||||||
|
app.use(mw.admin);
|
||||||
|
|
||||||
|
|
||||||
|
//Routes
|
||||||
|
app.use(require('./brew.api.js'));
|
||||||
|
app.use(require('./interface.routes.js'));
|
||||||
|
app.use(require('./admin.routes.js'));
|
||||||
|
|
||||||
|
if(config.get('NODE_ENV') !== 'staging' && config.get('NODE_ENV') !== 'production'){
|
||||||
|
app.use(require('./dev.routes.js'));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Error Handler
|
||||||
|
app.use(require('./error.js').expressHandler);
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
68
server/brew.api.js
Normal file
68
server/brew.api.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const router = require('express').Router();
|
||||||
|
|
||||||
|
const BrewData = require('./brew.data.js');
|
||||||
|
const mw = require('./middleware.js');
|
||||||
|
|
||||||
|
//Search
|
||||||
|
router.get('/api/brew', (req, res, next) => {
|
||||||
|
const opts = _.pick(req.query, ['limit', 'sort', 'page']);
|
||||||
|
|
||||||
|
BrewData.termSearch(req.query.terms, opts, req.admin)
|
||||||
|
.then((result) => {
|
||||||
|
return res.status(200).json(result);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
//User
|
||||||
|
router.get('/api/user/:username', (req, res, next) => {
|
||||||
|
const fullAccess = req.admin ||
|
||||||
|
!!(req.account && req.params.username == req.account.username);
|
||||||
|
|
||||||
|
BrewData.userSearch(req.params.username, fullAccess)
|
||||||
|
.then((result) => {
|
||||||
|
return res.status(200).json(result);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Get
|
||||||
|
router.get('/api/brew/:shareId', mw.viewBrew, (req, res, next) => {
|
||||||
|
return res.json(req.brew.toJSON());
|
||||||
|
});
|
||||||
|
|
||||||
|
//Create
|
||||||
|
router.post('/api/brew', (req, res, next)=>{
|
||||||
|
const brew = req.body;
|
||||||
|
if(req.account) brew.authors = [req.account.username];
|
||||||
|
BrewData.create(brew)
|
||||||
|
.then((brew) => {
|
||||||
|
return res.json(brew.toJSON());
|
||||||
|
})
|
||||||
|
.catch(next)
|
||||||
|
});
|
||||||
|
|
||||||
|
//Update
|
||||||
|
router.put('/api/brew/:editId', mw.loadBrew, (req, res, next)=>{
|
||||||
|
const brew = req.body || {};
|
||||||
|
if(req.account){
|
||||||
|
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
||||||
|
}
|
||||||
|
BrewData.update(req.params.editId, brew)
|
||||||
|
.then((brew) => {
|
||||||
|
return res.json(brew.toJSON());
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Delete
|
||||||
|
router.delete('/api/brew/:editId', mw.loadBrew, (req, res, next) => {
|
||||||
|
BrewData.remove(req.params.editId)
|
||||||
|
.then(()=>{
|
||||||
|
return res.sendStatus(200);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
100
server/brew.data.js
Normal file
100
server/brew.data.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const shortid = require('shortid');
|
||||||
|
const mongoose = require('./db.js').instance;
|
||||||
|
|
||||||
|
const Error = require('./error.js');
|
||||||
|
const utils = require('./utils.js');
|
||||||
|
|
||||||
|
const BrewSchema = mongoose.Schema({
|
||||||
|
shareId : {type : String, default: shortid.generate, index: { unique: true }},
|
||||||
|
editId : {type : String, default: shortid.generate, index: { unique: true }},
|
||||||
|
|
||||||
|
text : {type : String, default : ""},
|
||||||
|
|
||||||
|
title : {type : String, default : ""},
|
||||||
|
description : {type : String, default : ""},
|
||||||
|
tags : {type : String, default : ""},
|
||||||
|
systems : [String],
|
||||||
|
authors : [String],
|
||||||
|
published : {type : Boolean, default : false},
|
||||||
|
|
||||||
|
createdAt : { type: Date, default: Date.now },
|
||||||
|
updatedAt : { type: Date, default: Date.now},
|
||||||
|
lastViewed : { type: Date, default: Date.now},
|
||||||
|
views : {type:Number, default:0},
|
||||||
|
version : {type: Number, default:1}
|
||||||
|
}, {
|
||||||
|
versionKey: false,
|
||||||
|
toJSON : {
|
||||||
|
transform: (doc, ret, options) => {
|
||||||
|
delete ret._id;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Index these fields for fast text searching
|
||||||
|
BrewSchema.index({ title: "text", description: "text" });
|
||||||
|
|
||||||
|
BrewSchema.methods.increaseView = function(){
|
||||||
|
this.views = this.views + 1;
|
||||||
|
return this.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const Brew = mongoose.model('Brew', BrewSchema);
|
||||||
|
const BrewData = {
|
||||||
|
schema : BrewSchema,
|
||||||
|
model : Brew,
|
||||||
|
|
||||||
|
get : (query) => {
|
||||||
|
return Brew.findOne(query).exec()
|
||||||
|
.then((brew) => {
|
||||||
|
if(!brew) throw Error.noBrew();
|
||||||
|
return brew;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
create : (brew) => {
|
||||||
|
delete brew.shareId;
|
||||||
|
delete brew.editId;
|
||||||
|
if(!brew.title) brew.title = utils.getGoodBrewTitle(brew.text);
|
||||||
|
return (new Brew(brew)).save();
|
||||||
|
},
|
||||||
|
update : (editId, newBrew) => {
|
||||||
|
return BrewData.get({ editId })
|
||||||
|
.then((brew) => {
|
||||||
|
delete newBrew.shareId;
|
||||||
|
delete newBrew.editId;
|
||||||
|
brew = _.merge(brew, newBrew, { updatedAt : Date.now() });
|
||||||
|
|
||||||
|
brew.markModified('authors');
|
||||||
|
brew.markModified('systems');
|
||||||
|
return brew.save();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
remove : (editId) => {
|
||||||
|
return Brew.find({ editId }).remove().exec();
|
||||||
|
},
|
||||||
|
removeAll : ()=>{
|
||||||
|
return Brew.find({}).remove().exec();
|
||||||
|
},
|
||||||
|
|
||||||
|
//////// Special
|
||||||
|
|
||||||
|
getByShare : (shareId) => {
|
||||||
|
return BrewData.get({ shareId : shareId})
|
||||||
|
.then((brew) => {
|
||||||
|
brew.increaseView();
|
||||||
|
const brewJSON = brew.toJSON();
|
||||||
|
delete brewJSON.editId;
|
||||||
|
return brewJSON;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getByEdit : (editId) => {
|
||||||
|
return BrewData.get({ editId });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const BrewSearch = require('./brew.search.js')(Brew);
|
||||||
|
|
||||||
|
module.exports = _.merge(BrewData, BrewSearch);
|
||||||
66
server/brew.search.js
Normal file
66
server/brew.search.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
module.exports = (Brew) => {
|
||||||
|
const cmds = {
|
||||||
|
termSearch : (terms='', opts, fullAccess) => {
|
||||||
|
let query = {};
|
||||||
|
if(terms){
|
||||||
|
query = {$text: {
|
||||||
|
//Wrap terms in quotes to perform an AND operation
|
||||||
|
$search: _.map(terms.split(' '), (term)=>{
|
||||||
|
return `\"${term}\"`;
|
||||||
|
}).join(' '),
|
||||||
|
$caseSensitive : false
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
return cmds.search(query, opts, fullAccess);
|
||||||
|
},
|
||||||
|
|
||||||
|
userSearch : (username, fullAccess) => {
|
||||||
|
const query = {
|
||||||
|
authors : username
|
||||||
|
};
|
||||||
|
|
||||||
|
return cmds.search(query, {}, fullAccess);
|
||||||
|
},
|
||||||
|
|
||||||
|
search : (queryObj={}, options={}, fullAccess = true) => {
|
||||||
|
const opts = _.merge({
|
||||||
|
limit : 25,
|
||||||
|
page : 0,
|
||||||
|
sort : {}
|
||||||
|
}, options);
|
||||||
|
opts.limit = _.toNumber(opts.limit);
|
||||||
|
opts.page = _.toNumber(opts.page);
|
||||||
|
|
||||||
|
let filter = {
|
||||||
|
text : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!fullAccess){
|
||||||
|
filter.editId = 0;
|
||||||
|
queryObj.published = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchQuery = Brew
|
||||||
|
.find(queryObj)
|
||||||
|
.sort(opts.sort)
|
||||||
|
.select(filter)
|
||||||
|
.limit(opts.limit)
|
||||||
|
.skip(opts.page * opts.limit)
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
const countQuery = Brew.count(queryObj).exec();
|
||||||
|
|
||||||
|
return Promise.all([searchQuery, countQuery])
|
||||||
|
.then((result) => {
|
||||||
|
return {
|
||||||
|
brews : result[0],
|
||||||
|
total : result[1]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return cmds;
|
||||||
|
};
|
||||||
36
server/db.js
Normal file
36
server/db.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const log = require('loglevel');
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
mongoose.Promise = Promise;
|
||||||
|
|
||||||
|
const dbPath = process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/homebrewery';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
connect : ()=>{
|
||||||
|
return new Promise((resolve, reject)=>{
|
||||||
|
if(mongoose.connection.readyState == 1){
|
||||||
|
log.warn('DB already connected');
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
mongoose.connect(dbPath,
|
||||||
|
(err) => {
|
||||||
|
if(err){
|
||||||
|
log.error('Error : Could not connect to a Mongo Database.');
|
||||||
|
log.error(' If you are running locally, make sure mongodb.exe is running.');
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
log.info('DB connected.');
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
close : ()=>{
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
mongoose.connection.close(()=>{
|
||||||
|
log.info('DB connection closed.');
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
instance : mongoose
|
||||||
|
}
|
||||||
29
server/dev.routes.js
Normal file
29
server/dev.routes.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const router = require('express').Router();
|
||||||
|
const jwt = require('jwt-simple');
|
||||||
|
const auth = require('basic-auth');
|
||||||
|
const config = require('nconf');
|
||||||
|
|
||||||
|
if(process.env.NODE_ENV == 'production') throw 'Loading dev routes in prod. ABORT ABORT';
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/dev/login', (req, res, next) => {
|
||||||
|
const user = req.query.user;
|
||||||
|
if(!user){
|
||||||
|
return res.send(`
|
||||||
|
<html>
|
||||||
|
<body>dev login</body>
|
||||||
|
<script>
|
||||||
|
var user = prompt('enter username');
|
||||||
|
if(user) window.location = '/dev/login?user=' + encodeURIComponent(user);
|
||||||
|
</script></html>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
res.cookie('nc_session', jwt.encode({username : req.query.user}, config.get('jwt_secret')));
|
||||||
|
return res.redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user