mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-27 07:23:09 +00:00
Compare commits
164 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
643f6f933d | ||
|
|
a1140f75d8 | ||
|
|
b68c6a4ad2 | ||
|
|
6e0f042b42 | ||
|
|
c47d492ed3 | ||
|
|
18852932e8 | ||
|
|
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)
|
||||||
|
|||||||
150
changelog.md
150
changelog.md
@@ -1,5 +1,155 @@
|
|||||||
# changelog
|
# changelog
|
||||||
|
|
||||||
|
### Saturday, 22/04/217 - v2.7.4
|
||||||
|
- Give ability to hide the render warning notification
|
||||||
|
|
||||||
|
|
||||||
|
### Friday, 03/03/2017 - v2.7.3
|
||||||
|
- Increasing the range on the Partial Page Rendering for a quick-fix for it getting out of sync on long brews.
|
||||||
|
|
||||||
|
### Saturday, 18/02/2017 - v2.7.2
|
||||||
|
- Adding ability to delete a brew from the user page, incase the user creates a brew that makes the edit page unrender-able. (re:309)
|
||||||
|
|
||||||
|
## BIG NEWS
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ var Admin = React.createClass({
|
|||||||
|
|
||||||
<div className='container'>
|
<div className='container'>
|
||||||
|
|
||||||
<a target="_blank" href='https://www.google.com/analytics/web/?hl=en#report/defaultid/a72212009w109843310p114529111/'>Link to Google Analytics</a>
|
|
||||||
|
|
||||||
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
67
client/admin/homebrewAdmin/brewLookup/brewLookup.jsx
Normal file
67
client/admin/homebrewAdmin/brewLookup/brewLookup.jsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const request = require('superagent');
|
||||||
|
const Moment = require('moment');
|
||||||
|
|
||||||
|
|
||||||
|
const BrewLookup = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
adminKey : '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
query:'',
|
||||||
|
resultBrew : null,
|
||||||
|
searching : false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChange : function(e){
|
||||||
|
this.setState({
|
||||||
|
query : e.target.value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
lookup : function(){
|
||||||
|
this.setState({ searching : true });
|
||||||
|
|
||||||
|
request.get(`/admin/lookup/${this.state.query}`)
|
||||||
|
.query({ admin_key : this.props.adminKey })
|
||||||
|
.end((err, res) => {
|
||||||
|
this.setState({
|
||||||
|
searching : false,
|
||||||
|
resultBrew : (err ? null : res.body)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
renderFoundBrew : function(){
|
||||||
|
if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>;
|
||||||
|
if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>;
|
||||||
|
|
||||||
|
const brew = this.state.resultBrew;
|
||||||
|
return <div className='brewRow'>
|
||||||
|
<div>{brew.title}</div>
|
||||||
|
<div>{brew.authors.join(', ')}</div>
|
||||||
|
<div><a href={'/edit/' + brew.editId} target='_blank'>/edit/{brew.editId}</a></div>
|
||||||
|
<div><a href={'/share/' + brew.shareId} target='_blank'>/share/{brew.shareId}</a></div>
|
||||||
|
<div>{Moment(brew.updatedAt).fromNow()}</div>
|
||||||
|
<div>{brew.views}</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function(){
|
||||||
|
return <div className='brewLookup'>
|
||||||
|
<h1>Brew Lookup</h1>
|
||||||
|
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id...' />
|
||||||
|
<button onClick={this.lookup}><i className='fa fa-search'/></button>
|
||||||
|
|
||||||
|
{this.renderFoundBrew()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewLookup;
|
||||||
8
client/admin/homebrewAdmin/brewLookup/brewLookup.less
Normal file
8
client/admin/homebrewAdmin/brewLookup/brewLookup.less
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.brewLookup{
|
||||||
|
height : 200px;
|
||||||
|
input{
|
||||||
|
height : 33px;
|
||||||
|
padding : 0px 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
72
client/admin/homebrewAdmin/brewSearch.jsx
Normal file
72
client/admin/homebrewAdmin/brewSearch.jsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var request = require('superagent');
|
||||||
|
|
||||||
|
var BrewSearch = React.createClass({
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
admin_key : ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
searchTerm: '',
|
||||||
|
brew : null,
|
||||||
|
searching : false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
search : function(){
|
||||||
|
this.setState({
|
||||||
|
searching : true
|
||||||
|
});
|
||||||
|
|
||||||
|
request.get('/homebrew/api/search?id=' + this.state.searchTerm)
|
||||||
|
.query({
|
||||||
|
admin_key : this.props.admin_key,
|
||||||
|
})
|
||||||
|
.end((err, res)=>{
|
||||||
|
console.log(err, res, res.body.brews[0]);
|
||||||
|
this.setState({
|
||||||
|
brew : res.body.brews[0],
|
||||||
|
|
||||||
|
searching : false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChange : function(e){
|
||||||
|
this.setState({
|
||||||
|
searchTerm : e.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSearchClick : function(){
|
||||||
|
this.search();
|
||||||
|
},
|
||||||
|
|
||||||
|
renderBrew : function(){
|
||||||
|
if(!this.state.brew) return null;
|
||||||
|
return <div className='brew'>
|
||||||
|
<div>Edit id : {this.state.brew.editId}</div>
|
||||||
|
<div>Share id : {this.state.brew.shareId}</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='search'>
|
||||||
|
<input type='text' value={this.state.searchTerm} onChange={this.handleChange} />
|
||||||
|
|
||||||
|
<button onClick={this.handleSearchClick}>Search</button>
|
||||||
|
|
||||||
|
{this.renderBrew()}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BrewSearch;
|
||||||
@@ -5,6 +5,11 @@ var request = require('superagent');
|
|||||||
|
|
||||||
var Moment = require('moment');
|
var Moment = require('moment');
|
||||||
|
|
||||||
|
var BrewSearch = require('./brewSearch.jsx');
|
||||||
|
|
||||||
|
var BrewLookup = require('./brewLookup/brewLookup.jsx');
|
||||||
|
|
||||||
|
|
||||||
var HomebrewAdmin = React.createClass({
|
var HomebrewAdmin = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
@@ -25,13 +30,14 @@ var HomebrewAdmin = React.createClass({
|
|||||||
|
|
||||||
|
|
||||||
fetchBrews : function(page){
|
fetchBrews : function(page){
|
||||||
request.get('/homebrew/api/search')
|
request.get('/api/search')
|
||||||
.query({
|
.query({
|
||||||
admin_key : this.props.admin_key,
|
admin_key : this.props.admin_key,
|
||||||
count : this.state.count,
|
count : this.state.count,
|
||||||
page : page
|
page : page
|
||||||
})
|
})
|
||||||
.end((err, res)=>{
|
.end((err, res)=>{
|
||||||
|
if(err || !res.body || !res.body.brews) return;
|
||||||
this.state.brewCache[page] = res.body.brews;
|
this.state.brewCache[page] = res.body.brews;
|
||||||
this.setState({
|
this.setState({
|
||||||
brewCache : this.state.brewCache,
|
brewCache : this.state.brewCache,
|
||||||
@@ -56,11 +62,11 @@ var HomebrewAdmin = React.createClass({
|
|||||||
|
|
||||||
|
|
||||||
clearInvalidBrews : function(){
|
clearInvalidBrews : function(){
|
||||||
request.get('/homebrew/api/invalid')
|
request.get('/api/invalid')
|
||||||
.query({admin_key : this.props.admin_key})
|
.query({admin_key : this.props.admin_key})
|
||||||
.end((err, res)=>{
|
.end((err, res)=>{
|
||||||
if(!confirm("This will remove " + res.body.count + " brews. Are you sure?")) return;
|
if(!confirm("This will remove " + res.body.count + " brews. Are you sure?")) return;
|
||||||
request.get('/homebrew/api/invalid')
|
request.get('/api/invalid')
|
||||||
.query({admin_key : this.props.admin_key, do_it : true})
|
.query({admin_key : this.props.admin_key, do_it : true})
|
||||||
.end((err, res)=>{
|
.end((err, res)=>{
|
||||||
alert("Done!")
|
alert("Done!")
|
||||||
@@ -71,7 +77,7 @@ var HomebrewAdmin = React.createClass({
|
|||||||
|
|
||||||
deleteBrew : function(brewId){
|
deleteBrew : function(brewId){
|
||||||
if(!confirm("Are you sure you want to delete '" + brewId + "'?")) return;
|
if(!confirm("Are you sure you want to delete '" + brewId + "'?")) return;
|
||||||
request.get('/homebrew/api/remove/' + brewId)
|
request.get('/api/remove/' + brewId)
|
||||||
.query({admin_key : this.props.admin_key})
|
.query({admin_key : this.props.admin_key})
|
||||||
.end(function(err, res){
|
.end(function(err, res){
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
@@ -82,6 +88,7 @@ var HomebrewAdmin = React.createClass({
|
|||||||
this.changePageTo(this.state.page + dir);
|
this.changePageTo(this.state.page + dir);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
renderPagnination : function(){
|
renderPagnination : function(){
|
||||||
var outOf;
|
var outOf;
|
||||||
if(this.state.total){
|
if(this.state.total){
|
||||||
@@ -98,9 +105,9 @@ var HomebrewAdmin = React.createClass({
|
|||||||
renderBrews : function(){
|
renderBrews : function(){
|
||||||
var brews = this.state.brewCache[this.state.page] || _.times(this.state.count);
|
var brews = this.state.brewCache[this.state.page] || _.times(this.state.count);
|
||||||
return _.map(brews, (brew)=>{
|
return _.map(brews, (brew)=>{
|
||||||
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.sharedId}>
|
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}>
|
||||||
<td><a href={'/homebrew/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
|
<td><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
|
||||||
<td><a href={'/homebrew/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
|
<td><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
|
||||||
<td>{Moment(brew.createdAt).fromNow()}</td>
|
<td>{Moment(brew.createdAt).fromNow()}</td>
|
||||||
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
||||||
<td>{Moment(brew.lastViewed).fromNow()}</td>
|
<td>{Moment(brew.lastViewed).fromNow()}</td>
|
||||||
@@ -137,15 +144,27 @@ var HomebrewAdmin = React.createClass({
|
|||||||
render : function(){
|
render : function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
return <div className='homebrewAdmin'>
|
return <div className='homebrewAdmin'>
|
||||||
|
|
||||||
|
<BrewLookup adminKey={this.props.admin_key} />
|
||||||
|
|
||||||
|
{/*
|
||||||
<h2>
|
<h2>
|
||||||
Homebrews - {this.state.total}
|
Homebrews - {this.state.total}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{this.renderPagnination()}
|
{this.renderPagnination()}
|
||||||
{this.renderBrewTable()}
|
{this.renderBrewTable()}
|
||||||
|
|
||||||
<button className='clearOldButton' onClick={this.clearInvalidBrews}>
|
<button className='clearOldButton' onClick={this.clearInvalidBrews}>
|
||||||
Clear Old
|
Clear Old
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<BrewSearch admin_key={this.props.admin_key} />
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,45 +1,89 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
var Markdown = require('marked');
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
|
const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||||
|
|
||||||
var PAGE_HEIGHT = 1056 + 30;
|
//TODO: move to the brew renderer
|
||||||
|
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx')
|
||||||
|
|
||||||
var BrewRenderer = React.createClass({
|
const PAGE_HEIGHT = 1056;
|
||||||
|
const PPR_THRESHOLD = 50;
|
||||||
|
|
||||||
|
const BrewRenderer = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
text : ''
|
text : '',
|
||||||
|
errors : []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
const pages = this.props.text.split('\\page');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
viewablePageNumber: 0,
|
viewablePageNumber: 0,
|
||||||
height : 0
|
height : 0,
|
||||||
|
isMounted : false,
|
||||||
|
|
||||||
|
usePPR : true,
|
||||||
|
|
||||||
|
pages : pages,
|
||||||
|
usePPR : pages.length >= PPR_THRESHOLD,
|
||||||
|
|
||||||
|
errors : []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
totalPages : 0,
|
|
||||||
height : 0,
|
height : 0,
|
||||||
|
pageHeight : PAGE_HEIGHT,
|
||||||
|
lastRender : <div></div>,
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
this.updateSize();
|
||||||
|
window.addEventListener("resize", this.updateSize);
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
window.removeEventListener("resize", this.updateSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
||||||
|
|
||||||
|
const pages = nextProps.text.split('\\page');
|
||||||
this.setState({
|
this.setState({
|
||||||
height : this.refs.main.parentNode.clientHeight
|
pages : pages,
|
||||||
|
usePPR : pages.length >= PPR_THRESHOLD
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSize : function() {
|
||||||
|
setTimeout(()=>{
|
||||||
|
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
height : this.refs.main.parentNode.clientHeight,
|
||||||
|
isMounted : true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleScroll : function(e){
|
handleScroll : function(e){
|
||||||
this.setState({
|
this.setState({
|
||||||
viewablePageNumber : Math.floor(e.target.scrollTop / PAGE_HEIGHT)
|
viewablePageNumber : Math.floor(e.target.scrollTop / this.pageHeight)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
//Implement later
|
|
||||||
scrollToPage : function(pageNumber){
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldRender : function(pageText, index){
|
shouldRender : function(pageText, index){
|
||||||
|
if(!this.state.isMounted) return false;
|
||||||
|
|
||||||
var viewIndex = this.state.viewablePageNumber;
|
var viewIndex = this.state.viewablePageNumber;
|
||||||
|
if(index == viewIndex - 3) return true;
|
||||||
|
if(index == viewIndex - 2) return true;
|
||||||
if(index == viewIndex - 1) return true;
|
if(index == viewIndex - 1) return true;
|
||||||
if(index == viewIndex) return true;
|
if(index == viewIndex) return true;
|
||||||
if(index == viewIndex + 1) return true;
|
if(index == viewIndex + 1) return true;
|
||||||
|
if(index == viewIndex + 2) return true;
|
||||||
|
if(index == viewIndex + 3) return true;
|
||||||
|
|
||||||
//Check for style tages
|
//Check for style tages
|
||||||
if(pageText.indexOf('<style>') !== -1) return true;
|
if(pageText.indexOf('<style>') !== -1) return true;
|
||||||
@@ -49,31 +93,43 @@ var BrewRenderer = React.createClass({
|
|||||||
|
|
||||||
renderPageInfo : function(){
|
renderPageInfo : function(){
|
||||||
return <div className='pageInfo'>
|
return <div className='pageInfo'>
|
||||||
{this.state.viewablePageNumber + 1} / {this.totalPages}
|
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
|
||||||
</div>
|
</div>
|
||||||
},
|
},
|
||||||
|
|
||||||
renderDummyPage : function(key){
|
renderPPRmsg : function(){
|
||||||
return <div className='phb' key={key}>
|
if(!this.state.usePPR) return;
|
||||||
|
|
||||||
|
return <div className='ppr_msg'>
|
||||||
|
Partial Page Renderer enabled, because your brew is so large. May effect rendering.
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDummyPage : function(index){
|
||||||
|
return <div className='phb' id={`p${index + 1}`} key={index}>
|
||||||
<i className='fa fa-spinner fa-spin' />
|
<i className='fa fa-spinner fa-spin' />
|
||||||
</div>
|
</div>
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPage : function(pageText, index){
|
renderPage : function(pageText, index){
|
||||||
return <div className='phb' dangerouslySetInnerHTML={{__html:Markdown(pageText)}} key={index} />
|
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{__html:Markdown.render(pageText)}} key={index} />
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPages : function(){
|
renderPages : function(){
|
||||||
var pages = this.props.text.split('\\page');
|
if(this.state.usePPR){
|
||||||
this.totalPages = pages.length;
|
return _.map(this.state.pages, (page, index)=>{
|
||||||
|
|
||||||
return _.map(pages, (page, index)=>{
|
|
||||||
if(this.shouldRender(page, index)){
|
if(this.shouldRender(page, index)){
|
||||||
return this.renderPage(page, index);
|
return this.renderPage(page, index);
|
||||||
}else{
|
}else{
|
||||||
return this.renderDummyPage(index);
|
return this.renderDummyPage(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if(this.props.errors && this.props.errors.length) return this.lastRender;
|
||||||
|
this.lastRender = _.map(this.state.pages, (page, index)=>{
|
||||||
|
return this.renderPage(page, index);
|
||||||
|
});
|
||||||
|
return this.lastRender;
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
@@ -82,10 +138,14 @@ var BrewRenderer = React.createClass({
|
|||||||
ref='main'
|
ref='main'
|
||||||
style={{height : this.state.height}}>
|
style={{height : this.state.height}}>
|
||||||
|
|
||||||
<div className='pages'>
|
<ErrorBar errors={this.props.errors} />
|
||||||
|
<RenderWarnings />
|
||||||
|
|
||||||
|
<div className='pages' ref='pages'>
|
||||||
{this.renderPages()}
|
{this.renderPages()}
|
||||||
</div>
|
</div>
|
||||||
{this.renderPageInfo()}
|
{this.renderPageInfo()}
|
||||||
|
{this.renderPPRmsg()}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,17 @@
|
|||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
color : white;
|
color : white;
|
||||||
}
|
}
|
||||||
|
.ppr_msg{
|
||||||
|
position : absolute;
|
||||||
|
left : 0px;
|
||||||
|
bottom : 0;
|
||||||
|
z-index : 1000;
|
||||||
|
padding : 8px 10px;
|
||||||
|
background-color : #333;
|
||||||
|
font-size : 10px;
|
||||||
|
font-weight : 800;
|
||||||
|
color : white;
|
||||||
|
}
|
||||||
.pages{
|
.pages{
|
||||||
margin : 30px 0px;
|
margin : 30px 0px;
|
||||||
&>.phb{
|
&>.phb{
|
||||||
|
|||||||
73
client/homebrew/brewRenderer/errorBar/errorBar.jsx
Normal file
73
client/homebrew/brewRenderer/errorBar/errorBar.jsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var _ = require('lodash');
|
||||||
|
var cx = require('classnames');
|
||||||
|
|
||||||
|
var ErrorBar = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
errors : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
hasOpenError : false,
|
||||||
|
hasCloseError : false,
|
||||||
|
hasMatchError : false,
|
||||||
|
|
||||||
|
renderErrors : function(){
|
||||||
|
this.hasOpenError = false;
|
||||||
|
this.hasCloseError = false;
|
||||||
|
this.hasMatchError = false;
|
||||||
|
|
||||||
|
|
||||||
|
var errors = _.map(this.props.errors, (err, idx) => {
|
||||||
|
if(err.id == 'OPEN') this.hasOpenError = true;
|
||||||
|
if(err.id == 'CLOSE') this.hasCloseError = true;
|
||||||
|
if(err.id == 'MISMATCH') this.hasMatchError = true;
|
||||||
|
return <li key={idx}>
|
||||||
|
Line {err.line} : {err.text}, '{err.type}' tag
|
||||||
|
</li>
|
||||||
|
});
|
||||||
|
|
||||||
|
return <ul>{errors}</ul>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderProtip : function(){
|
||||||
|
var msg = [];
|
||||||
|
if(this.hasOpenError){
|
||||||
|
msg.push(<div>
|
||||||
|
An unmatched opening tag means there's an opened tag that isn't closed, you need to close a tag, like this {'</div>'}. Make sure to match types!
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.hasCloseError){
|
||||||
|
msg.push(<div>
|
||||||
|
An unmatched closing tag means you closed a tag without opening it. Either remove it, you check to where you think you opened it.
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.hasMatchError){
|
||||||
|
msg.push(<div>
|
||||||
|
A type mismatch means you closed a tag, but the last open tag was a different type.
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
return <div className='protips'>
|
||||||
|
<h4>Protips!</h4>
|
||||||
|
{msg}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
if(!this.props.errors.length) return null;
|
||||||
|
|
||||||
|
return <div className='errorBar'>
|
||||||
|
<i className='fa fa-exclamation-triangle' />
|
||||||
|
<h3> There are HTML errors in your markup</h3>
|
||||||
|
<small>If these aren't fixed your brew will not render properly when you print it to PDF or share it</small>
|
||||||
|
{this.renderErrors()}
|
||||||
|
<hr />
|
||||||
|
{this.renderProtip()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ErrorBar;
|
||||||
60
client/homebrew/brewRenderer/errorBar/errorBar.less
Normal file
60
client/homebrew/brewRenderer/errorBar/errorBar.less
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
.errorBar{
|
||||||
|
position : absolute;
|
||||||
|
z-index : 10000;
|
||||||
|
box-sizing : border-box;
|
||||||
|
width : 100%;
|
||||||
|
margin-right : 13px;
|
||||||
|
padding : 20px;
|
||||||
|
padding-bottom : 10px;
|
||||||
|
padding-left : 100px;
|
||||||
|
background-color : @red;
|
||||||
|
color : white;
|
||||||
|
i{
|
||||||
|
position : absolute;
|
||||||
|
left : 30px;
|
||||||
|
opacity : 0.8;
|
||||||
|
font-size : 3em;
|
||||||
|
}
|
||||||
|
h3{
|
||||||
|
font-size : 1.1em;
|
||||||
|
font-weight : 800;
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
margin-top : 15px;
|
||||||
|
font-size : 0.8em;
|
||||||
|
list-style-position : inside;
|
||||||
|
list-style-type : disc;
|
||||||
|
li{
|
||||||
|
line-height : 1.6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hr{
|
||||||
|
box-sizing : border-box;
|
||||||
|
height : 2px;
|
||||||
|
width : 150%;
|
||||||
|
margin-top : 25px;
|
||||||
|
margin-bottom : 15px;
|
||||||
|
margin-left : -100px;
|
||||||
|
background-color : darken(@red, 8%);
|
||||||
|
border : none;
|
||||||
|
}
|
||||||
|
small{
|
||||||
|
font-size: 0.6em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.protips{
|
||||||
|
margin-left : -80px;
|
||||||
|
font-size : 0.6em;
|
||||||
|
&>div{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
line-height : 1.2em;
|
||||||
|
}
|
||||||
|
h4{
|
||||||
|
opacity : 0.8;
|
||||||
|
font-weight : 800;
|
||||||
|
line-height : 1.5em;
|
||||||
|
text-transform : uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,31 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
var CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
|
||||||
var Snippets = require('./snippets/snippets.js');
|
const SnippetBar = require('./snippetbar/snippetbar.jsx');
|
||||||
|
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
|
||||||
|
|
||||||
|
|
||||||
var splice = function(str, index, inject){
|
const splice = function(str, index, inject){
|
||||||
return str.slice(0, index) + inject + str.slice(index);
|
return str.slice(0, index) + inject + str.slice(index);
|
||||||
};
|
};
|
||||||
var execute = function(val){
|
|
||||||
if(_.isFunction(val)) return val();
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const SNIPPETBAR_HEIGHT = 25;
|
||||||
|
|
||||||
var Editor = React.createClass({
|
const Editor = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
value : "",
|
value : '',
|
||||||
onChange : function(){}
|
onChange : ()=>{},
|
||||||
|
|
||||||
|
metadata : {},
|
||||||
|
onMetadataChange : ()=>{},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
showMetadataEditor: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
cursorPosition : {
|
cursorPosition : {
|
||||||
@@ -28,8 +34,17 @@ var Editor = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
var paneHeight = this.refs.main.parentNode.clientHeight;
|
this.updateEditorSize();
|
||||||
paneHeight -= this.refs.snippetBar.clientHeight + 1;
|
this.highlightPageLines();
|
||||||
|
window.addEventListener("resize", this.updateEditorSize);
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
window.removeEventListener("resize", this.updateEditorSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateEditorSize : function() {
|
||||||
|
let paneHeight = this.refs.main.parentNode.clientHeight;
|
||||||
|
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
||||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -39,38 +54,70 @@ var Editor = React.createClass({
|
|||||||
handleCursorActivty : function(curpos){
|
handleCursorActivty : function(curpos){
|
||||||
this.cursorPosition = curpos;
|
this.cursorPosition = curpos;
|
||||||
},
|
},
|
||||||
|
handleInject : function(injectText){
|
||||||
handleSnippetClick : function(injectText){
|
const lines = this.props.value.split('\n');
|
||||||
var lines = this.props.value.split('\n');
|
|
||||||
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
|
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
|
||||||
|
|
||||||
this.handleTextChange(lines.join('\n'));
|
this.handleTextChange(lines.join('\n'));
|
||||||
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
|
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
|
||||||
},
|
},
|
||||||
|
handgleToggle : function(){
|
||||||
|
this.setState({
|
||||||
|
showMetadataEditor : !this.state.showMetadataEditor
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentPage : function(){
|
||||||
|
const lines = this.props.value.split('\n').slice(0, this.cursorPosition.line + 1);
|
||||||
|
return _.reduce(lines, (r, line) => {
|
||||||
|
if(line.indexOf('\\page') !== -1) r++;
|
||||||
|
return r;
|
||||||
|
}, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
highlightPageLines : function(){
|
||||||
|
if(!this.refs.codeEditor) return;
|
||||||
|
const codeMirror = this.refs.codeEditor.codeMirror;
|
||||||
|
|
||||||
|
const lineNumbers = _.reduce(this.props.value.split('\n'), (r, line, lineNumber)=>{
|
||||||
|
if(line.indexOf('\\page') !== -1){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||||
|
r.push(lineNumber);
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}, []);
|
||||||
|
return lineNumbers
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
brewJump : function(){
|
||||||
|
const currentPage = this.getCurrentPage();
|
||||||
|
window.location.hash = 'p' + currentPage;
|
||||||
|
},
|
||||||
|
|
||||||
//Called when there are changes to the editor's dimensions
|
//Called when there are changes to the editor's dimensions
|
||||||
update : function(){
|
update : function(){
|
||||||
this.refs.codeEditor.updateSize();
|
this.refs.codeEditor.updateSize();
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSnippetGroups : function(){
|
renderMetadataEditor : function(){
|
||||||
return _.map(Snippets, (snippetGroup)=>{
|
if(!this.state.showMetadataEditor) return;
|
||||||
return <SnippetGroup
|
return <MetadataEditor
|
||||||
groupName={snippetGroup.groupName}
|
metadata={this.props.metadata}
|
||||||
icon={snippetGroup.icon}
|
onChange={this.props.onMetadataChange}
|
||||||
snippets={snippetGroup.snippets}
|
|
||||||
key={snippetGroup.groupName}
|
|
||||||
onSnippetClick={this.handleSnippetClick}
|
|
||||||
/>
|
/>
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
|
this.highlightPageLines();
|
||||||
return(
|
return(
|
||||||
<div className='editor' ref='main'>
|
<div className='editor' ref='main'>
|
||||||
<div className='snippetBar' ref='snippetBar'>
|
<SnippetBar
|
||||||
{this.renderSnippetGroups()}
|
brew={this.props.value}
|
||||||
</div>
|
onInject={this.handleInject}
|
||||||
|
onToggle={this.handgleToggle}
|
||||||
|
showmeta={this.state.showMetadataEditor} />
|
||||||
|
{this.renderMetadataEditor()}
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
ref='codeEditor'
|
ref='codeEditor'
|
||||||
wrap={true}
|
wrap={true}
|
||||||
@@ -78,6 +125,12 @@ var Editor = React.createClass({
|
|||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
onChange={this.handleTextChange}
|
onChange={this.handleTextChange}
|
||||||
onCursorActivity={this.handleCursorActivty} />
|
onCursorActivity={this.handleCursorActivty} />
|
||||||
|
|
||||||
|
{/*
|
||||||
|
<div className='brewJump' onClick={this.brewJump}>
|
||||||
|
<i className='fa fa-arrow-right' />
|
||||||
|
</div>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -90,40 +143,3 @@ 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>
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -2,55 +2,28 @@
|
|||||||
.editor{
|
.editor{
|
||||||
position : relative;
|
position : relative;
|
||||||
width : 100%;
|
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{
|
.codeEditor{
|
||||||
height : 100%;
|
height : 100%;
|
||||||
|
.pageLine{
|
||||||
|
background-color : fade(#333, 15%);
|
||||||
|
border-bottom : #333 solid 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brewJump{
|
||||||
|
position: absolute;
|
||||||
|
background-color: @teal;
|
||||||
|
cursor: pointer;
|
||||||
|
width : 30px;
|
||||||
|
height : 30px;
|
||||||
|
display : flex;
|
||||||
|
align-items : center;
|
||||||
|
bottom : 20px;
|
||||||
|
right : 20px;
|
||||||
|
z-index: 1000000;
|
||||||
|
justify-content:center;
|
||||||
|
.tooltipLeft("Jump to brew page");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
175
client/homebrew/editor/metadataEditor/metadataEditor.jsx
Normal file
175
client/homebrew/editor/metadataEditor/metadataEditor.jsx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
const request = require("superagent");
|
||||||
|
|
||||||
|
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder']
|
||||||
|
|
||||||
|
const MetadataEditor = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
editId : null,
|
||||||
|
title : '',
|
||||||
|
description : '',
|
||||||
|
tags : '',
|
||||||
|
published : false,
|
||||||
|
authors : [],
|
||||||
|
systems : []
|
||||||
|
},
|
||||||
|
onChange : ()=>{}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFieldChange : function(name, e){
|
||||||
|
this.props.onChange(_.merge({}, this.props.metadata, {
|
||||||
|
[name] : e.target.value
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
handleSystem : function(system, e){
|
||||||
|
if(e.target.checked){
|
||||||
|
this.props.metadata.systems.push(system);
|
||||||
|
}else{
|
||||||
|
this.props.metadata.systems = _.without(this.props.metadata.systems, system);
|
||||||
|
}
|
||||||
|
this.props.onChange(this.props.metadata);
|
||||||
|
},
|
||||||
|
handlePublish : function(val){
|
||||||
|
this.props.onChange(_.merge({}, this.props.metadata, {
|
||||||
|
published : val
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
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('/api/remove/' + this.props.metadata.editId)
|
||||||
|
.send()
|
||||||
|
.end(function(err, res){
|
||||||
|
window.location.href = '/';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getRedditLink : function(){
|
||||||
|
const meta = this.props.metadata;
|
||||||
|
const title = `${meta.title} [${meta.systems.join(' ')}]`;
|
||||||
|
const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
|
||||||
|
|
||||||
|
**[Homebrewery Link](http://homebrewery.naturalcrit.com/share/${meta.shareId})**`;
|
||||||
|
|
||||||
|
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSystems : function(){
|
||||||
|
return _.map(SYSTEMS, (val)=>{
|
||||||
|
return <label key={val}>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={_.includes(this.props.metadata.systems, val)}
|
||||||
|
onChange={this.handleSystem.bind(null, val)} />
|
||||||
|
{val}
|
||||||
|
</label>
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renderPublish : function(){
|
||||||
|
if(this.props.metadata.published){
|
||||||
|
return <button className='unpublish' onClick={this.handlePublish.bind(null, false)}>
|
||||||
|
<i className='fa fa-ban' /> unpublish
|
||||||
|
</button>
|
||||||
|
}else{
|
||||||
|
return <button className='publish' onClick={this.handlePublish.bind(null, true)}>
|
||||||
|
<i className='fa fa-globe' /> publish
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDelete : function(){
|
||||||
|
if(!this.props.metadata.editId) return;
|
||||||
|
|
||||||
|
return <div className='field delete'>
|
||||||
|
<label>delete</label>
|
||||||
|
<div className='value'>
|
||||||
|
<button className='publish' onClick={this.handleDelete}>
|
||||||
|
<i className='fa fa-trash' /> delete brew
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderAuthors : function(){
|
||||||
|
let text = 'None.';
|
||||||
|
if(this.props.metadata.authors.length){
|
||||||
|
text = this.props.metadata.authors.join(', ');
|
||||||
|
}
|
||||||
|
return <div className='field authors'>
|
||||||
|
<label>authors</label>
|
||||||
|
<div className='value'>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShareToReddit : function(){
|
||||||
|
if(!this.props.metadata.shareId) return;
|
||||||
|
|
||||||
|
return <div className='field reddit'>
|
||||||
|
<label>reddit</label>
|
||||||
|
<div className='value'>
|
||||||
|
<a href={this.getRedditLink()} target='_blank'>
|
||||||
|
<button className='publish'>
|
||||||
|
<i className='fa fa-reddit-alien' /> share to reddit
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='metadataEditor'>
|
||||||
|
<div className='field title'>
|
||||||
|
<label>title</label>
|
||||||
|
<input type='text' className='value'
|
||||||
|
value={this.props.metadata.title}
|
||||||
|
onChange={this.handleFieldChange.bind(null, 'title')} />
|
||||||
|
</div>
|
||||||
|
<div className='field description'>
|
||||||
|
<label>description</label>
|
||||||
|
<textarea value={this.props.metadata.description} className='value'
|
||||||
|
onChange={this.handleFieldChange.bind(null, 'description')} />
|
||||||
|
</div>
|
||||||
|
{/*}
|
||||||
|
<div className='field tags'>
|
||||||
|
<label>tags</label>
|
||||||
|
<textarea value={this.props.metadata.tags}
|
||||||
|
onChange={this.handleFieldChange.bind(null, 'tags')} />
|
||||||
|
</div>
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<div className='field systems'>
|
||||||
|
<label>systems</label>
|
||||||
|
<div className='value'>
|
||||||
|
{this.renderSystems()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.renderAuthors()}
|
||||||
|
|
||||||
|
<div className='field publish'>
|
||||||
|
<label>publish</label>
|
||||||
|
<div className='value'>
|
||||||
|
{this.renderPublish()}
|
||||||
|
<small>Published homebrews will be publicly viewable and searchable (eventually...)</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.renderShareToReddit()}
|
||||||
|
|
||||||
|
{this.renderDelete()}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = MetadataEditor;
|
||||||
79
client/homebrew/editor/metadataEditor/metadataEditor.less
Normal file
79
client/homebrew/editor/metadataEditor/metadataEditor.less
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
|
||||||
|
.metadataEditor{
|
||||||
|
position : absolute;
|
||||||
|
z-index : 10000;
|
||||||
|
box-sizing : border-box;
|
||||||
|
width : 100%;
|
||||||
|
padding : 25px;
|
||||||
|
background-color : #999;
|
||||||
|
.field{
|
||||||
|
display : flex;
|
||||||
|
width : 100%;
|
||||||
|
margin-bottom : 10px;
|
||||||
|
&>label{
|
||||||
|
display : inline-block;
|
||||||
|
vertical-align : top;
|
||||||
|
width : 80px;
|
||||||
|
font-size : 0.7em;
|
||||||
|
font-weight : 800;
|
||||||
|
line-height : 1.8em;
|
||||||
|
text-transform : uppercase;
|
||||||
|
flex-grow : 0;
|
||||||
|
}
|
||||||
|
&>.value{
|
||||||
|
flex-grow : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.description.field textarea.value{
|
||||||
|
resize : none;
|
||||||
|
height : 5em;
|
||||||
|
font-family : 'Open Sans', sans-serif;
|
||||||
|
font-size : 0.8em;
|
||||||
|
}
|
||||||
|
.systems.field .value{
|
||||||
|
label{
|
||||||
|
vertical-align : middle;
|
||||||
|
margin-right : 15px;
|
||||||
|
cursor : pointer;
|
||||||
|
font-size : 0.7em;
|
||||||
|
font-weight : 800;
|
||||||
|
user-select : none;
|
||||||
|
}
|
||||||
|
input{
|
||||||
|
vertical-align : middle;
|
||||||
|
cursor : pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.publish.field .value{
|
||||||
|
position : relative;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
button.publish{
|
||||||
|
.button(@blueLight);
|
||||||
|
}
|
||||||
|
button.unpublish{
|
||||||
|
.button(@silver);
|
||||||
|
}
|
||||||
|
small{
|
||||||
|
position : absolute;
|
||||||
|
bottom : -15px;
|
||||||
|
left : 0px;
|
||||||
|
font-size : 0.6em;
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete.field .value{
|
||||||
|
button{
|
||||||
|
.button(@red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.reddit.field .value{
|
||||||
|
button{
|
||||||
|
.button(@purple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.authors.field .value{
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height : 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
94
client/homebrew/editor/snippetbar/snippetbar.jsx
Normal file
94
client/homebrew/editor/snippetbar/snippetbar.jsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
|
||||||
|
const Snippets = require('./snippets/snippets.js');
|
||||||
|
|
||||||
|
const execute = function(val, brew){
|
||||||
|
if(_.isFunction(val)) return val(brew);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const Snippetbar = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : '',
|
||||||
|
onInject : ()=>{},
|
||||||
|
onToggle : ()=>{},
|
||||||
|
showmeta : false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSnippetClick : function(injectedText){
|
||||||
|
this.props.onInject(injectedText)
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSnippetGroups : function(){
|
||||||
|
return _.map(Snippets, (snippetGroup)=>{
|
||||||
|
return <SnippetGroup
|
||||||
|
brew={this.props.brew}
|
||||||
|
groupName={snippetGroup.groupName}
|
||||||
|
icon={snippetGroup.icon}
|
||||||
|
snippets={snippetGroup.snippets}
|
||||||
|
key={snippetGroup.groupName}
|
||||||
|
onSnippetClick={this.handleSnippetClick}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='snippetBar'>
|
||||||
|
{this.renderSnippetGroups()}
|
||||||
|
<div className={cx('toggleMeta', {selected: this.props.showmeta})}
|
||||||
|
onClick={this.props.onToggle}>
|
||||||
|
<i className='fa fa-bars' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Snippetbar;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const SnippetGroup = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : '',
|
||||||
|
groupName : '',
|
||||||
|
icon : 'fa-rocket',
|
||||||
|
snippets : [],
|
||||||
|
onSnippetClick : function(){},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleSnippetClick : function(snippet){
|
||||||
|
this.props.onSnippetClick(execute(snippet.gen, this.props.brew));
|
||||||
|
},
|
||||||
|
renderSnippets : function(){
|
||||||
|
return _.map(this.props.snippets, (snippet)=>{
|
||||||
|
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
|
||||||
|
<i className={'fa fa-fw ' + snippet.icon} />
|
||||||
|
{snippet.name}
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='snippetGroup'>
|
||||||
|
<div className='text'>
|
||||||
|
<i className={'fa fa-fw ' + this.props.icon} />
|
||||||
|
<span className='groupName'>{this.props.groupName}</span>
|
||||||
|
</div>
|
||||||
|
<div className='dropdown'>
|
||||||
|
{this.renderSnippets()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
73
client/homebrew/editor/snippetbar/snippetbar.less
Normal file
73
client/homebrew/editor/snippetbar/snippetbar.less
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
.snippetBar{
|
||||||
|
@height : 25px;
|
||||||
|
position : relative;
|
||||||
|
height : @height;
|
||||||
|
background-color : #ddd;
|
||||||
|
.toggleMeta{
|
||||||
|
position : absolute;
|
||||||
|
top : 0px;
|
||||||
|
right : 0px;
|
||||||
|
height : @height;
|
||||||
|
width : @height;
|
||||||
|
cursor : pointer;
|
||||||
|
line-height : @height;
|
||||||
|
text-align : center;
|
||||||
|
.tooltipLeft("Edit Brew Metadata");
|
||||||
|
&:hover, &.selected{
|
||||||
|
background-color : #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.snippetGroup{
|
||||||
|
display : inline-block;
|
||||||
|
height : @height;
|
||||||
|
padding : 0px 5px;
|
||||||
|
cursor : pointer;
|
||||||
|
font-size : 0.6em;
|
||||||
|
font-weight : 800;
|
||||||
|
line-height : @height;
|
||||||
|
text-transform : uppercase;
|
||||||
|
border-right : 1px solid black;
|
||||||
|
i{
|
||||||
|
vertical-align : middle;
|
||||||
|
margin-right : 3px;
|
||||||
|
font-size : 1.2em;
|
||||||
|
}
|
||||||
|
&:hover, &.selected{
|
||||||
|
background-color : #999;
|
||||||
|
}
|
||||||
|
.text{
|
||||||
|
line-height : @height;
|
||||||
|
.groupName{
|
||||||
|
font-size : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
.dropdown{
|
||||||
|
visibility : visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dropdown{
|
||||||
|
position : absolute;
|
||||||
|
top : 100%;
|
||||||
|
visibility : hidden;
|
||||||
|
z-index : 1000;
|
||||||
|
margin-left : -5px;
|
||||||
|
padding : 0px;
|
||||||
|
background-color : #ddd;
|
||||||
|
.snippet{
|
||||||
|
.animate(background-color);
|
||||||
|
padding : 5px;
|
||||||
|
cursor : pointer;
|
||||||
|
font-size : 10px;
|
||||||
|
i{
|
||||||
|
margin-right : 8px;
|
||||||
|
font-size : 13px;
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
background-color : #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ var _ = require('lodash');
|
|||||||
|
|
||||||
module.exports = function(classname){
|
module.exports = function(classname){
|
||||||
|
|
||||||
classname = classname || _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
classname = _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
||||||
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge'])
|
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge'])
|
||||||
|
|
||||||
classname = classname.toLowerCase();
|
classname = classname.toLowerCase();
|
||||||
@@ -19,8 +19,8 @@ module.exports = function(classname){
|
|||||||
"#### Hit Points",
|
"#### Hit Points",
|
||||||
"___",
|
"___",
|
||||||
"- **Hit Dice:** 1d" + hitDie + " per " + classname + " level",
|
"- **Hit Dice:** 1d" + hitDie + " per " + classname + " level",
|
||||||
"- **Hit Points at 1st Level:** " + hitDie + " + your Constituion modifier",
|
"- **Hit Points at 1st Level:** " + hitDie + " + your Constitution modifier",
|
||||||
"- **Hit Points at Higher Levels:** 1d" + hitDie + " (or " + (hitDie/2 + 1) + ") + your Constituion modifier per " + classname + " level after 1st",
|
"- **Hit Points at Higher Levels:** 1d" + hitDie + " (or " + (hitDie/2 + 1) + ") + your Constitution modifier per " + classname + " level after 1st",
|
||||||
"",
|
"",
|
||||||
"#### Proficiencies",
|
"#### Proficiencies",
|
||||||
"___",
|
"___",
|
||||||
@@ -38,10 +38,21 @@ var classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
|||||||
|
|
||||||
var levels = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"]
|
var levels = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"]
|
||||||
|
|
||||||
|
var profBonus = [2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6];
|
||||||
|
|
||||||
|
var getFeature = (level)=>{
|
||||||
|
var res = []
|
||||||
|
if(_.includes([4,6,8,12,14,16,19], level+1)){
|
||||||
|
res = ["Ability Score Improvement"]
|
||||||
|
}
|
||||||
|
res = _.union(res, _.sampleSize(features, _.sample([0,1,1,1,1,1])));
|
||||||
|
if(!res.length) return "─";
|
||||||
|
return res.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
full : function(classname){
|
full : function(){
|
||||||
classname = classname || _.sample(classnames)
|
var classname = _.sample(classnames)
|
||||||
|
|
||||||
var maxes = [4,3,3,3,3,2,2,1,1]
|
var maxes = [4,3,3,3,3,2,2,1,1]
|
||||||
var drawSlots = function(Slots){
|
var drawSlots = function(Slots){
|
||||||
@@ -59,15 +70,14 @@ module.exports = {
|
|||||||
var cantrips = 3;
|
var cantrips = 3;
|
||||||
var spells = 1;
|
var spells = 1;
|
||||||
var slots = 2;
|
var slots = 2;
|
||||||
return "##### The " + classname + "\n" +
|
return "<div class='classTable wide'>\n##### The " + classname + "\n" +
|
||||||
"___\n" +
|
|
||||||
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
|
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
|
||||||
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
|
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
|
||||||
_.map(levels, function(levelName, level){
|
_.map(levels, function(levelName, level){
|
||||||
var res = [
|
var res = [
|
||||||
levelName,
|
levelName,
|
||||||
"+" + Math.ceil(level/5 + 1),
|
"+" + profBonus[level],
|
||||||
_.sampleSize(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
|
getFeature(level),
|
||||||
cantrips,
|
cantrips,
|
||||||
spells,
|
spells,
|
||||||
drawSlots(slots)
|
drawSlots(slots)
|
||||||
@@ -78,28 +88,27 @@ module.exports = {
|
|||||||
slots += _.random(0,2);
|
slots += _.random(0,2);
|
||||||
|
|
||||||
return "| " + res + " |";
|
return "| " + res + " |";
|
||||||
}).join('\n') +'\n\n';
|
}).join('\n') +'\n</div>\n\n';
|
||||||
},
|
},
|
||||||
|
|
||||||
half : function(classname){
|
half : function(){
|
||||||
classname = classname || _.sample(classnames)
|
var classname = _.sample(classnames)
|
||||||
|
|
||||||
var featureScore = 1
|
var featureScore = 1
|
||||||
return "##### The " + classname + "\n" +
|
return "<div class='classTable'>\n##### The " + classname + "\n" +
|
||||||
"___\n" + "___\n" +
|
|
||||||
"| Level | Proficiency Bonus | Features | " + _.sample(features) + "|\n" +
|
"| Level | Proficiency Bonus | Features | " + _.sample(features) + "|\n" +
|
||||||
"|:---:|:---:|:---|:---:|\n" +
|
"|:---:|:---:|:---|:---:|\n" +
|
||||||
_.map(levels, function(levelName, level){
|
_.map(levels, function(levelName, level){
|
||||||
var res = [
|
var res = [
|
||||||
levelName,
|
levelName,
|
||||||
"+" + Math.ceil(level/5 + 1),
|
"+" + profBonus[level],
|
||||||
_.sampleSize(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
|
getFeature(level),
|
||||||
"+" + featureScore
|
"+" + featureScore
|
||||||
].join(' | ');
|
].join(' | ');
|
||||||
|
|
||||||
featureScore += _.random(0,1);
|
featureScore += _.random(0,1);
|
||||||
|
|
||||||
return "| " + res + " |";
|
return "| " + res + " |";
|
||||||
}).join('\n') +'\n\n';
|
}).join('\n') +'\n</div>\n\n';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
117
client/homebrew/editor/snippetbar/snippets/coverpage.gen.js
Normal file
117
client/homebrew/editor/snippetbar/snippets/coverpage.gen.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
var titles = [
|
||||||
|
"The Burning Gallows",
|
||||||
|
"The Ring of Nenlast",
|
||||||
|
"Below the Blind Tavern",
|
||||||
|
"Below the Hungering River",
|
||||||
|
"Before Bahamut's Land",
|
||||||
|
"The Cruel Grave from Within",
|
||||||
|
"The Strength of Trade Road",
|
||||||
|
"Through The Raven Queen's Worlds",
|
||||||
|
"Within the Settlement",
|
||||||
|
"The Crown from Within",
|
||||||
|
"The Merchant Within the Battlefield",
|
||||||
|
"Ioun's Fading Traveler",
|
||||||
|
"The Legion Ingredient",
|
||||||
|
"The Explorer Lure",
|
||||||
|
"Before the Charming Badlands",
|
||||||
|
"The Living Dead Above the Fearful Cage",
|
||||||
|
"Vecna's Hidden Sage",
|
||||||
|
"Bahamut's Demonspawn",
|
||||||
|
"Across Gruumsh's Elemental Chaos",
|
||||||
|
"The Blade of Orcus",
|
||||||
|
"Beyond Revenge",
|
||||||
|
"Brain of Insanity",
|
||||||
|
"Breed Battle!, A New Beginning",
|
||||||
|
"Evil Lake, A New Beginning",
|
||||||
|
"Invasion of the Gigantic Cat, Part II",
|
||||||
|
"Kraken War 2020",
|
||||||
|
"The Body Whisperers",
|
||||||
|
"The Diabolical Tales of the Ape-Women",
|
||||||
|
"The Doctor Immortal",
|
||||||
|
"The Doctor from Heaven",
|
||||||
|
"The Graveyard",
|
||||||
|
"Azure Core",
|
||||||
|
"Core Battle",
|
||||||
|
"Core of Heaven: The Guardian of Amazement",
|
||||||
|
"Deadly Amazement III",
|
||||||
|
"Dry Chaos IX",
|
||||||
|
"Gate Thunder",
|
||||||
|
"Guardian: Skies of the Dark Wizard",
|
||||||
|
"Lute of Eternity",
|
||||||
|
"Mercury's Planet: Brave Evolution",
|
||||||
|
"Ruby of Atlantis: The Quake of Peace",
|
||||||
|
"Sky of Zelda: The Thunder of Force",
|
||||||
|
"Vyse's Skies",
|
||||||
|
"White Greatness III",
|
||||||
|
"Yellow Divinity",
|
||||||
|
"Zidane's Ghost"
|
||||||
|
];
|
||||||
|
|
||||||
|
var subtitles = [
|
||||||
|
"In an ominous universe, a botanist opposes terrorism.",
|
||||||
|
"In a demon-haunted city, in an age of lies and hate, a physicist tries to find an ancient treasure and battles a mob of aliens.",
|
||||||
|
"In a land of corruption, two cyberneticists and a dungeon delver search for freedom.",
|
||||||
|
"In an evil empire of horror, two rangers battle the forces of hell.",
|
||||||
|
"In a lost city, in an age of sorcery, a librarian quests for revenge.",
|
||||||
|
"In a universe of illusions and danger, three time travellers and an adventurer search for justice.",
|
||||||
|
"In a forgotten universe of barbarism, in an era of terror and mysticism, a virtual reality programmer and a spy try to find vengance and battle crime.",
|
||||||
|
"In a universe of demons, in an era of insanity and ghosts, three bodyguards and a bodyguard try to find vengance.",
|
||||||
|
"In a kingdom of corruption and battle, seven artificial intelligences try to save the last living fertile woman.",
|
||||||
|
"In a universe of virutal reality and agony, in an age of ghosts and ghosts, a fortune-teller and a wanderer try to avert the apocalypse.",
|
||||||
|
"In a crime-infested kingdom, three martial artists quest for the truth and oppose evil.",
|
||||||
|
"In a terrifying universe of lost souls, in an era of lost souls, eight dancers fight evil.",
|
||||||
|
"In a galaxy of confusion and insanity, three martial artists and a duke battle a mob of psychics.",
|
||||||
|
"In an amazing kingdom, a wizard and a secretary hope to prevent the destruction of mankind.",
|
||||||
|
"In a kingdom of deception, a reporter searches for fame.",
|
||||||
|
"In a hellish empire, a swordswoman and a duke try to find the ultimate weapon and battle a conspiracy.",
|
||||||
|
"In an evil galaxy of illusion, in a time of technology and misery, seven psychiatrists battle crime.",
|
||||||
|
"In a dark city of confusion, three swordswomen and a singer battle lawlessness.",
|
||||||
|
"In an ominous empire, in an age of hate, two philosophers and a student try to find justice and battle a mob of mages intent on stealing the souls of the innocent.",
|
||||||
|
"In a kingdom of panic, six adventurers oppose lawlessness.",
|
||||||
|
"In a land of dreams and hopelessness, three hackers and a cyborg search for justice.",
|
||||||
|
"On a planet of mysticism, three travelers and a fire fighter quest for the ultimate weapon and oppose evil.",
|
||||||
|
"In a wicked universe, five seers fight lawlessness.",
|
||||||
|
"In a kingdom of death, in an era of illusion and blood, four colonists search for fame.",
|
||||||
|
"In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.",
|
||||||
|
"In a cursed empire, five inventors oppose terrorism.",
|
||||||
|
"On a crime-ridden planet of conspiracy, a watchman and an artificial intelligence try to find love and oppose lawlessness.",
|
||||||
|
"In a forgotten land, a reporter and a spy try to stop the apocalypse.",
|
||||||
|
"In a forbidden land of prophecy, a scientist and an archivist oppose a cabal of barbarians intent on stealing the souls of the innocent.",
|
||||||
|
"On an infernal world of illusion, a grave robber and a watchman try to find revenge and combat a syndicate of mages intent on stealing the source of all magic.",
|
||||||
|
"In a galaxy of dark magic, four fighters seek freedom.",
|
||||||
|
"In an empire of deception, six tomb-robbers quest for the ultimate weapon and combat an army of raiders.",
|
||||||
|
"In a kingdom of corruption and lost souls, in an age of panic, eight planetologists oppose evil.",
|
||||||
|
"In a galaxy of misery and hopelessness, in a time of agony and pain, five planetologists search for vengance.",
|
||||||
|
"In a universe of technology and insanity, in a time of sorcery, a computer techician quests for hope.",
|
||||||
|
"On a planet of dark magic and barbarism, in an age of horror and blasphemy, seven librarians search for fame.",
|
||||||
|
"In an empire of dark magic, in a time of blood and illusions, four monks try to find the ultimate weapon and combat terrorism.",
|
||||||
|
"In a forgotten empire of dark magic, six kings try to prevent the destruction of mankind.",
|
||||||
|
"In a galaxy of dark magic and horror, in an age of hopelessness, four marines and an outlaw combat evil.",
|
||||||
|
"In a mysterious city of illusion, in an age of computerization, a witch-hunter tries to find the ultimate weapon and opposes an evil corporation.",
|
||||||
|
"In a damned kingdom of technology, a virtual reality programmer and a fighter seek fame.",
|
||||||
|
"In a hellish kingdom, in an age of blasphemy and blasphemy, an astrologer searches for fame.",
|
||||||
|
"In a damned world of devils, an alien and a ranger quest for love and oppose a syndicate of demons.",
|
||||||
|
"In a cursed galaxy, in a time of pain, seven librarians hope to avert the apocalypse.",
|
||||||
|
"In a crime-infested galaxy, in an era of hopelessness and panic, three champions and a grave robber try to solve the ultimate crime."
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
return `<style>
|
||||||
|
.phb#p1{ text-align:center; }
|
||||||
|
.phb#p1:after{ display:none; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div style='margin-top:450px;'></div>
|
||||||
|
|
||||||
|
# ${_.sample(titles)}
|
||||||
|
|
||||||
|
<div style='margin-top:25px'></div>
|
||||||
|
<div class='wide'>
|
||||||
|
##### ${_.sample(subtitles)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
\\page`
|
||||||
|
}
|
||||||
91
client/homebrew/editor/snippetbar/snippets/magic.gen.js
Normal file
91
client/homebrew/editor/snippetbar/snippets/magic.gen.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
var spellNames = [
|
||||||
|
"Astral Rite of Acne",
|
||||||
|
"Create Acne",
|
||||||
|
"Cursed Ramen Erruption",
|
||||||
|
"Dark Chant of the Dentists",
|
||||||
|
"Erruption of Immaturity",
|
||||||
|
"Flaming Disc of Inconvenience",
|
||||||
|
"Heal Bad Hygene",
|
||||||
|
"Heavenly Transfiguration of the Cream Devil",
|
||||||
|
"Hellish Cage of Mucus",
|
||||||
|
"Irritate Peanut Butter Fairy",
|
||||||
|
"Luminous Erruption of Tea",
|
||||||
|
"Mystic Spell of the Poser",
|
||||||
|
"Sorcerous Enchantment of the Chimneysweep",
|
||||||
|
"Steak Sauce Ray",
|
||||||
|
"Talk to Groupie",
|
||||||
|
"Astonishing Chant of Chocolate",
|
||||||
|
"Astounding Pasta Puddle",
|
||||||
|
"Ball of Annoyance",
|
||||||
|
"Cage of Yarn",
|
||||||
|
"Control Noodles Elemental",
|
||||||
|
"Create Nervousness",
|
||||||
|
"Cure Baldness",
|
||||||
|
"Cursed Ritual of Bad Hair",
|
||||||
|
"Dispell Piles in Dentist",
|
||||||
|
"Eliminate Florists",
|
||||||
|
"Illusionary Transfiguration of the Babysitter",
|
||||||
|
"Necromantic Armor of Salad Dressing",
|
||||||
|
"Occult Transfiguration of Foot Fetish",
|
||||||
|
"Protection from Mucus Giant",
|
||||||
|
"Tinsel Blast",
|
||||||
|
"Alchemical Evocation of the Goths",
|
||||||
|
"Call Fangirl",
|
||||||
|
"Divine Spell of Crossdressing",
|
||||||
|
"Dominate Ramen Giant",
|
||||||
|
"Eliminate Vindictiveness in Gym Teacher",
|
||||||
|
"Extra-Planar Spell of Irritation",
|
||||||
|
"Induce Whining in Babysitter",
|
||||||
|
"Invoke Complaining",
|
||||||
|
"Magical Enchantment of Arrogance",
|
||||||
|
"Occult Globe of Salad Dressing",
|
||||||
|
"Overwhelming Enchantment of the Chocolate Fairy",
|
||||||
|
"Sorcerous Dandruff Globe",
|
||||||
|
"Spiritual Invocation of the Costumers",
|
||||||
|
"Ultimate Rite of the Confetti Angel",
|
||||||
|
"Ultimate Ritual of Mouthwash",
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
spellList : function(){
|
||||||
|
var levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level'];
|
||||||
|
|
||||||
|
var content = _.map(levels, (level)=>{
|
||||||
|
var spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{
|
||||||
|
return `- ${spell}`;
|
||||||
|
}).join('\n');
|
||||||
|
return `##### ${level} \n${spells} \n`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return `<div class='spellList'>\n${content}\n</div>`;
|
||||||
|
},
|
||||||
|
|
||||||
|
spell : function(){
|
||||||
|
var level = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th"];
|
||||||
|
var spellSchools = ["abjuration", "conjuration", "divination", "enchantment", "evocation", "illusion", "necromancy", "transmutation"];
|
||||||
|
|
||||||
|
|
||||||
|
var components = _.sampleSize(["V", "S", "M"], _.random(1,3)).join(', ');
|
||||||
|
if(components.indexOf("M") !== -1){
|
||||||
|
components += " (" + _.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1,3)).join(', ') + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"#### " + _.sample(spellNames),
|
||||||
|
"*" + _.sample(level) + "-level " + _.sample(spellSchools) + "*",
|
||||||
|
"___",
|
||||||
|
"- **Casting Time:** 1 action",
|
||||||
|
"- **Range:** " + _.sample(["Self", "Touch", "30 feet", "60 feet"]),
|
||||||
|
"- **Components:** " + components,
|
||||||
|
"- **Duration:** " + _.sample(["Until dispelled", "1 round", "Instantaneous", "Concentration, up to 10 minutes", "1 hour"]),
|
||||||
|
"",
|
||||||
|
"A flame, equivalent in brightness to a torch, springs from from an object that you touch. ",
|
||||||
|
"The effect look like a regular flame, but it creates no heat and doesn't use oxygen. ",
|
||||||
|
"A *continual flame* can be covered or hidden but not smothered or quenched.",
|
||||||
|
"\n\n\n"
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
var SpellGen = require('./spell.gen.js');
|
var MagicGen = require('./magic.gen.js');
|
||||||
var ClassTableGen = require('./classtable.gen.js');
|
var ClassTableGen = require('./classtable.gen.js');
|
||||||
var MonsterBlockGen = require('./monsterblock.gen.js');
|
var MonsterBlockGen = require('./monsterblock.gen.js');
|
||||||
var ClassFeatureGen = require('./classfeature.gen.js');
|
var ClassFeatureGen = require('./classfeature.gen.js');
|
||||||
var FullClassGen = require('./fullclass.gen.js');
|
var FullClassGen = require('./fullclass.gen.js');
|
||||||
|
var CoverPageGen = require('./coverpage.gen.js');
|
||||||
|
var TableOfContentsGen = require('./tableOfContents.gen.js');
|
||||||
|
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
@@ -27,6 +28,11 @@ module.exports = [
|
|||||||
icon : 'fa-arrows-v',
|
icon : 'fa-arrows-v',
|
||||||
gen : "<div style='margin-top:140px'></div>\n\n"
|
gen : "<div style='margin-top:140px'></div>\n\n"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name : "Wide Block",
|
||||||
|
icon : 'fa-arrows-h',
|
||||||
|
gen : "<div class='wide'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name : "Image",
|
name : "Image",
|
||||||
icon : 'fa-image',
|
icon : 'fa-image',
|
||||||
@@ -53,6 +59,24 @@ module.exports = [
|
|||||||
gen : "<div class='pageNumber'>1</div>\n<div class='footnote'>PART 1 | FANCINESS</div>\n\n"
|
gen : "<div class='pageNumber'>1</div>\n<div class='footnote'>PART 1 | FANCINESS</div>\n\n"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name : "Auto-incrementing Page Number",
|
||||||
|
icon : 'fa-sort-numeric-asc',
|
||||||
|
gen : "<div class='pageNumber auto'></div>\n"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name : "Link to page",
|
||||||
|
icon : 'fa-link',
|
||||||
|
gen : "[Click here](#p3) to go to page 3\n"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name : "Table of Contents",
|
||||||
|
icon : 'fa-book',
|
||||||
|
gen : TableOfContentsGen
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -67,7 +91,12 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
name : 'Spell',
|
name : 'Spell',
|
||||||
icon : 'fa-magic',
|
icon : 'fa-magic',
|
||||||
gen : SpellGen,
|
gen : MagicGen.spell,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Spell List',
|
||||||
|
icon : 'fa-list',
|
||||||
|
gen : MagicGen.spellList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Class Feature',
|
name : 'Class Feature',
|
||||||
@@ -86,6 +115,20 @@ module.exports = [
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name : 'Descriptive Text Box',
|
||||||
|
icon : 'fa-sticky-note-o',
|
||||||
|
gen : function(){
|
||||||
|
return [
|
||||||
|
"<div class='descriptive'>",
|
||||||
|
"##### Time to Drop Knowledge",
|
||||||
|
"Use notes to point out some interesting information. ",
|
||||||
|
"",
|
||||||
|
"**Tables and lists** both work within a note.",
|
||||||
|
"</div>"
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name : 'Monster Stat Block',
|
name : 'Monster Stat Block',
|
||||||
icon : 'fa-bug',
|
icon : 'fa-bug',
|
||||||
@@ -95,7 +138,12 @@ module.exports = [
|
|||||||
name : 'Wide Monster Stat Block',
|
name : 'Wide Monster Stat Block',
|
||||||
icon : 'fa-paw',
|
icon : 'fa-paw',
|
||||||
gen : MonsterBlockGen.full,
|
gen : MonsterBlockGen.full,
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name : 'Cover Page',
|
||||||
|
icon : 'fa-file-word-o',
|
||||||
|
gen : CoverPageGen,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -132,6 +180,52 @@ module.exports = [
|
|||||||
"| 17th | 4 or lower |\n\n",
|
"| 17th | 4 or lower |\n\n",
|
||||||
].join('\n');
|
].join('\n');
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Wide Table',
|
||||||
|
icon : 'fa-list',
|
||||||
|
gen : function(){
|
||||||
|
return [
|
||||||
|
"<div class='wide'>",
|
||||||
|
"##### Cookie Tastiness",
|
||||||
|
"| Tastiness | Cookie Type |",
|
||||||
|
"|:----:|:-------------|",
|
||||||
|
"| -5 | Raisin |",
|
||||||
|
"| 8th | Chocolate Chip |",
|
||||||
|
"| 11th | 2 or lower |",
|
||||||
|
"| 14th | 3 or lower |",
|
||||||
|
"| 17th | 4 or lower |",
|
||||||
|
"</div>\n\n"
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Split Table',
|
||||||
|
icon : 'fa-th-large',
|
||||||
|
gen : function(){
|
||||||
|
return [
|
||||||
|
"<div style='column-count:2'>",
|
||||||
|
"| d10 | Damage Type |",
|
||||||
|
"|:---:|:------------|",
|
||||||
|
"| 1 | Acid |",
|
||||||
|
"| 2 | Cold |",
|
||||||
|
"| 3 | Fire |",
|
||||||
|
"| 4 | Force |",
|
||||||
|
"| 5 | Lightning |",
|
||||||
|
"",
|
||||||
|
"```",
|
||||||
|
"```",
|
||||||
|
"",
|
||||||
|
"| d10 | Damage Type |",
|
||||||
|
"|:---:|:------------|",
|
||||||
|
"| 6 | Necrotic |",
|
||||||
|
"| 7 | Poison |",
|
||||||
|
"| 8 | Psychic |",
|
||||||
|
"| 9 | Radiant |",
|
||||||
|
"| 10 | Thunder |",
|
||||||
|
"</div>\n\n",
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -171,5 +265,3 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const getTOC = (pages) => {
|
||||||
|
const add1 = (title, page)=>{
|
||||||
|
res.push({
|
||||||
|
title : title,
|
||||||
|
page : page + 1,
|
||||||
|
children : []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const add2 = (title, page)=>{
|
||||||
|
if(!_.last(res)) add1('', page);
|
||||||
|
_.last(res).children.push({
|
||||||
|
title : title,
|
||||||
|
page : page + 1,
|
||||||
|
children : []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const add3 = (title, page)=>{
|
||||||
|
if(!_.last(res)) add1('', page);
|
||||||
|
if(!_.last(_.last(res).children)) add2('', page);
|
||||||
|
_.last(_.last(res).children).children.push({
|
||||||
|
title : title,
|
||||||
|
page : page + 1,
|
||||||
|
children : []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = [];
|
||||||
|
_.each(pages, (page, pageNum)=>{
|
||||||
|
const lines = page.split('\n');
|
||||||
|
_.each(lines, (line) => {
|
||||||
|
if(_.startsWith(line, '# ')){
|
||||||
|
const title = line.replace('# ', '');
|
||||||
|
add1(title, pageNum)
|
||||||
|
}
|
||||||
|
if(_.startsWith(line, '## ')){
|
||||||
|
const title = line.replace('## ', '');
|
||||||
|
add2(title, pageNum);
|
||||||
|
}
|
||||||
|
if(_.startsWith(line, '### ')){
|
||||||
|
const title = line.replace('### ', '');
|
||||||
|
add3(title, pageNum);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function(brew){
|
||||||
|
const pages = brew.split('\\page');
|
||||||
|
const TOC = getTOC(pages);
|
||||||
|
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
|
||||||
|
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`)
|
||||||
|
if(g1.children.length){
|
||||||
|
_.each(g1.children, (g2, idx2) => {
|
||||||
|
r.push(` - [${idx1 + 1}.${idx2 + 1} ${g2.title}](#p${g2.page})`);
|
||||||
|
if(g2.children.length){
|
||||||
|
_.each(g2.children, (g3, idx3) => {
|
||||||
|
r.push(` - [${idx1 + 1}.${idx2 + 1}.${idx3 + 1} ${g3.title}](#p${g3.page})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}, []).join('\n');
|
||||||
|
|
||||||
|
return `<div class='toc'>
|
||||||
|
##### Table Of Contents
|
||||||
|
${markdown}
|
||||||
|
</div>\n`;
|
||||||
|
}
|
||||||
@@ -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,21 +1,26 @@
|
|||||||
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;
|
||||||
|
|
||||||
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 : "",
|
welcomeText : '',
|
||||||
changelog : "",
|
changelog : '',
|
||||||
|
version : '0.0.0',
|
||||||
|
account : null,
|
||||||
brew : {
|
brew : {
|
||||||
title : '',
|
title : '',
|
||||||
text : '',
|
text : '',
|
||||||
@@ -27,29 +32,57 @@ var Homebrew = React.createClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
global.account = this.props.account;
|
||||||
|
global.version = this.props.version;
|
||||||
|
|
||||||
|
|
||||||
Router = CreateRouter({
|
Router = CreateRouter({
|
||||||
'/homebrew/edit/:id' : (args) => {
|
'/edit/:id' : (args) => {
|
||||||
return <EditPage id={args.id} brew={this.props.brew} />
|
if(!this.props.brew.editId){
|
||||||
|
return <ErrorPage errorId={args.id}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <EditPage
|
||||||
|
id={args.id}
|
||||||
|
brew={this.props.brew} />
|
||||||
},
|
},
|
||||||
|
|
||||||
'/homebrew/share/:id' : (args) => {
|
'/share/:id' : (args) => {
|
||||||
return <SharePage id={args.id} brew={this.props.brew} />
|
if(!this.props.brew.shareId){
|
||||||
|
return <ErrorPage errorId={args.id}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SharePage
|
||||||
|
id={args.id}
|
||||||
|
brew={this.props.brew} />
|
||||||
},
|
},
|
||||||
'/homebrew/changelog' : (args) => {
|
'/user/:username' : (args) => {
|
||||||
return <SharePage brew={{title : 'Changelog', text : this.props.changelog}} />
|
return <UserPage
|
||||||
|
username={args.username}
|
||||||
|
brews={this.props.brews}
|
||||||
|
/>
|
||||||
},
|
},
|
||||||
'/homebrew/new' : (args) => {
|
'/print/:id' : (args, query) => {
|
||||||
|
return <PrintPage brew={this.props.brew} query={query}/>;
|
||||||
|
},
|
||||||
|
'/print' : (args, query) => {
|
||||||
|
return <PrintPage query={query}/>;
|
||||||
|
},
|
||||||
|
'/new' : (args) => {
|
||||||
return <NewPage />
|
return <NewPage />
|
||||||
},
|
},
|
||||||
'/homebrew*' : <HomePage welcomeText={this.props.welcomeText} />,
|
'/changelog' : (args) => {
|
||||||
|
return <SharePage
|
||||||
|
brew={{title : 'Changelog', text : this.props.changelog}} />
|
||||||
|
},
|
||||||
|
'*' : <HomePage
|
||||||
|
welcomeText={this.props.welcomeText} />,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
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,12 +1,11 @@
|
|||||||
|
|
||||||
@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%;
|
||||||
|
background-color : @steel;
|
||||||
flex-direction : column;
|
flex-direction : column;
|
||||||
.content{
|
.content{
|
||||||
position : relative;
|
position : relative;
|
||||||
|
|||||||
17
client/homebrew/navbar/account.navitem.jsx
Normal file
17
client/homebrew/navbar/account.navitem.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
|
module.exports = function(props){
|
||||||
|
if(global.account){
|
||||||
|
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
|
||||||
|
{global.account.username}
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
let url = '';
|
||||||
|
if(typeof window !== 'undefined'){
|
||||||
|
url = window.location.href
|
||||||
|
}
|
||||||
|
return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'>
|
||||||
|
login
|
||||||
|
</Nav.item>
|
||||||
|
};
|
||||||
@@ -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,45 @@
|
|||||||
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');
|
||||||
|
|
||||||
var Navbar = React.createClass({
|
const Navbar = React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
//showNonChromeWarning : false,
|
||||||
|
ver : '0.0.0'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
//const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
|
||||||
|
this.setState({
|
||||||
|
//showNonChromeWarning : !isChrome,
|
||||||
|
ver : window.version
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
renderChromeWarning : function(){
|
||||||
|
if(!this.state.showNonChromeWarning) return;
|
||||||
|
return <Nav.item className='warning' icon='fa-exclamation-triangle'>
|
||||||
|
Optimized for Chrome
|
||||||
|
<div className='dropdown'>
|
||||||
|
If you are experiencing rendering issues, use Chrome instead
|
||||||
|
</div>
|
||||||
|
</Nav.item>
|
||||||
|
},
|
||||||
|
*/
|
||||||
render : function(){
|
render : function(){
|
||||||
return <Nav.base>
|
return <Nav.base>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<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${this.state.ver}`}</Nav.item>
|
||||||
|
|
||||||
|
{/*this.renderChromeWarning()*/}
|
||||||
</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);
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
.editTitle.navItem{
|
.editTitle.navItem{
|
||||||
padding : 2px 12px;
|
padding : 2px 12px;
|
||||||
input{
|
input{
|
||||||
|
width : 250px;
|
||||||
margin : 0;
|
margin : 0;
|
||||||
padding : 2px;
|
padding : 2px;
|
||||||
width : 250px;
|
|
||||||
background-color : #444;
|
background-color : #444;
|
||||||
font-family : 'Open Sans', sans-serif;
|
font-family : 'Open Sans', sans-serif;
|
||||||
font-size : 12px;
|
font-size : 12px;
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
display : inline-block;
|
display : inline-block;
|
||||||
vertical-align : bottom;
|
vertical-align : bottom;
|
||||||
margin-left : 8px;
|
margin-left : 8px;
|
||||||
text-align : right;
|
|
||||||
color : #666;
|
color : #666;
|
||||||
|
text-align : right;
|
||||||
&.max{
|
&.max{
|
||||||
color : @red;
|
color : @red;
|
||||||
}
|
}
|
||||||
@@ -54,5 +54,75 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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,156 +1,208 @@
|
|||||||
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");
|
const 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 ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||||
var ReportIssue = require('../../navbar/issue.navitem.jsx');
|
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||||
var PrintLink = require('../../navbar/print.navitem.jsx');
|
const Account = require('../../navbar/account.navitem.jsx');
|
||||||
|
//const RecentlyEdited = require('../../navbar/recent.navitem.jsx').edited;
|
||||||
|
|
||||||
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
|
||||||
var Editor = require('../../editor/editor.jsx');
|
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
|
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
|
const Editor = require('../../editor/editor.jsx');
|
||||||
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
|
|
||||||
const SAVE_TIMEOUT = 3000;
|
const SAVE_TIMEOUT = 3000;
|
||||||
|
|
||||||
|
|
||||||
|
const EditPage = React.createClass({
|
||||||
var EditPage = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
id : null,
|
|
||||||
brew : {
|
brew : {
|
||||||
title : '',
|
|
||||||
text : '',
|
text : '',
|
||||||
shareId : null,
|
shareId : null,
|
||||||
editId : null,
|
editId : null,
|
||||||
createdAt : null,
|
createdAt : null,
|
||||||
updatedAt : null,
|
updatedAt : null,
|
||||||
|
|
||||||
|
title : '',
|
||||||
|
description : '',
|
||||||
|
tags : '',
|
||||||
|
published : false,
|
||||||
|
authors : [],
|
||||||
|
systems : []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
title : this.props.brew.title,
|
brew : this.props.brew,
|
||||||
text: this.props.brew.text,
|
|
||||||
isSaving : false,
|
isSaving : false,
|
||||||
isPending : false,
|
isPending : false,
|
||||||
errors : null,
|
errors : null,
|
||||||
|
htmlErrors : Markdown.validate(this.props.brew.text),
|
||||||
lastUpdated : this.props.brew.updatedAt
|
lastUpdated : this.props.brew.updatedAt
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
savedBrew : null,
|
savedBrew : null,
|
||||||
|
|
||||||
componentDidMount: function(){
|
componentDidMount: function(){
|
||||||
this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
this.trySave();
|
||||||
window.onbeforeunload = ()=>{
|
window.onbeforeunload = ()=>{
|
||||||
if(this.state.isSaving || this.state.isPending){
|
if(this.state.isSaving || this.state.isPending){
|
||||||
return 'You have unsaved changes!';
|
return 'You have unsaved changes!';
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
htmlErrors : Markdown.validate(this.state.brew.text)
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
window.onbeforeunload = function(){};
|
window.onbeforeunload = function(){};
|
||||||
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
handleControlKeys : function(e){
|
||||||
|
if(!(e.ctrlKey || e.metaKey)) return;
|
||||||
|
const S_KEY = 83;
|
||||||
|
const P_KEY = 80;
|
||||||
|
if(e.keyCode == S_KEY) this.save();
|
||||||
|
if(e.keyCode == P_KEY) window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
||||||
|
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSplitMove : function(){
|
handleSplitMove : function(){
|
||||||
this.refs.editor.update();
|
this.refs.editor.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTitleChange : function(title){
|
handleMetadataChange : function(metadata){
|
||||||
this.setState({
|
this.setState({
|
||||||
title : title,
|
brew : _.merge({}, this.state.brew, metadata),
|
||||||
isPending : true
|
isPending : true,
|
||||||
|
}, ()=>{
|
||||||
|
this.trySave();
|
||||||
});
|
});
|
||||||
|
|
||||||
(this.hasChanges() ? this.debounceSave() : this.debounceSave.cancel());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTextChange : function(text){
|
handleTextChange : function(text){
|
||||||
|
|
||||||
|
//If there are errors, run the validator on everychange to give quick feedback
|
||||||
|
var htmlErrors = this.state.htmlErrors;
|
||||||
|
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
text : text,
|
brew : _.merge({}, this.state.brew, {text : text}),
|
||||||
isPending : true
|
isPending : true,
|
||||||
|
htmlErrors : htmlErrors
|
||||||
});
|
});
|
||||||
|
|
||||||
(this.hasChanges() ? this.debounceSave() : this.debounceSave.cancel());
|
this.trySave();
|
||||||
},
|
|
||||||
|
|
||||||
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(){
|
hasChanges : function(){
|
||||||
if(this.savedBrew){
|
if(this.savedBrew){
|
||||||
if(this.state.text !== this.savedBrew.text) return true;
|
return !_.isEqual(this.state.brew, this.savedBrew)
|
||||||
if(this.state.title !== this.savedBrew.title) return true;
|
|
||||||
}else{
|
}else{
|
||||||
if(this.state.text !== this.props.brew.text) return true;
|
return !_.isEqual(this.state.brew, this.props.brew)
|
||||||
if(this.state.title !== this.props.brew.title) return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
save : function(){
|
trySave : function(){
|
||||||
|
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
||||||
|
if(this.hasChanges()){
|
||||||
|
this.debounceSave();
|
||||||
|
}else{
|
||||||
this.debounceSave.cancel();
|
this.debounceSave.cancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
save : function(){
|
||||||
|
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isSaving : true
|
isSaving : true,
|
||||||
|
errors : null,
|
||||||
|
htmlErrors : Markdown.validate(this.state.brew.text)
|
||||||
});
|
});
|
||||||
|
|
||||||
request
|
request
|
||||||
.put('/homebrew/api/update/' + this.props.brew.editId)
|
.put('/api/update/' + this.props.brew.editId)
|
||||||
.send({
|
.send(this.state.brew)
|
||||||
text : this.state.text,
|
|
||||||
title : this.state.title
|
|
||||||
})
|
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
|
if(err){
|
||||||
|
this.setState({
|
||||||
|
errors : err,
|
||||||
|
})
|
||||||
|
}else{
|
||||||
this.savedBrew = res.body;
|
this.savedBrew = res.body;
|
||||||
this.setState({
|
this.setState({
|
||||||
isPending : false,
|
isPending : false,
|
||||||
isSaving : false,
|
isSaving : false,
|
||||||
lastUpdated : res.body.updatedAt
|
lastUpdated : res.body.updatedAt
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSaveButton : function(){
|
renderSaveButton : function(){
|
||||||
|
if(this.state.errors){
|
||||||
|
var errMsg = '';
|
||||||
|
try{
|
||||||
|
errMsg += this.state.errors.toString() + '\n\n';
|
||||||
|
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
||||||
|
}catch(e){}
|
||||||
|
|
||||||
|
return <Nav.item className='save error' icon="fa-warning">
|
||||||
|
Oops!
|
||||||
|
<div className='errorContainer'>
|
||||||
|
Looks like there was a problem saving. <br />
|
||||||
|
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
||||||
|
here
|
||||||
|
</a>.
|
||||||
|
</div>
|
||||||
|
</Nav.item>
|
||||||
|
}
|
||||||
|
|
||||||
if(this.state.isSaving){
|
if(this.state.isSaving){
|
||||||
return <Nav.item className='save' icon="fa-spinner fa-spin">saving...</Nav.item>
|
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()){
|
if(this.state.isPending && this.hasChanges()){
|
||||||
return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item>
|
return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item>
|
||||||
}
|
}
|
||||||
|
if(!this.state.isPending && !this.state.isSaving){
|
||||||
|
return <Nav.item className='save saved'>saved.</Nav.item>
|
||||||
|
}
|
||||||
},
|
},
|
||||||
renderNavbar : function(){
|
renderNavbar : function(){
|
||||||
return <Navbar>
|
return <Navbar>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<EditTitle title={this.state.title} onChange={this.handleTitleChange} />
|
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
{this.renderSaveButton()}
|
{this.renderSaveButton()}
|
||||||
<Nav.item newTab={true} href={'/homebrew/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'>
|
{/*<RecentlyEdited brew={this.props.brew} />*/}
|
||||||
|
<ReportIssue />
|
||||||
|
<Nav.item newTab={true} href={'/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'>
|
||||||
Share
|
Share
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<PrintLink shareId={this.props.brew.shareId} />
|
<PrintLink shareId={this.props.brew.shareId} />
|
||||||
<Nav.item color='red' icon='fa-trash' onClick={this.handleDelete}>
|
<Account />
|
||||||
Delete
|
|
||||||
</Nav.item>
|
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
},
|
},
|
||||||
@@ -161,8 +213,14 @@ var EditPage = React.createClass({
|
|||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
<Editor
|
||||||
<BrewRenderer text={this.state.text} />
|
ref='editor'
|
||||||
|
value={this.state.brew.text}
|
||||||
|
onChange={this.handleTextChange}
|
||||||
|
metadata={this.state.brew}
|
||||||
|
onMetadataChange={this.handleMetadataChange}
|
||||||
|
/>
|
||||||
|
<BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} />
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
.editPage{
|
|
||||||
|
|
||||||
|
.editPage{
|
||||||
.navItem.save{
|
.navItem.save{
|
||||||
width : 75px;
|
width : 105px;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
&.saved{
|
&.saved{
|
||||||
color : #666;
|
|
||||||
cursor : initial;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
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,22 +1,27 @@
|
|||||||
var React = require('react');
|
const React = require('react');
|
||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
const 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 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 SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
var Editor = require('../../editor/editor.jsx');
|
const Editor = require('../../editor/editor.jsx');
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var HomePage = React.createClass({
|
const HomePage = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
welcomeText : ""
|
welcomeText : '',
|
||||||
|
ver : '0.0.0'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
@@ -24,6 +29,17 @@ var HomePage = React.createClass({
|
|||||||
text: this.props.welcomeText
|
text: this.props.welcomeText
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
handleSave : function(){
|
||||||
|
request.post('/api')
|
||||||
|
.send({
|
||||||
|
text : this.state.text
|
||||||
|
})
|
||||||
|
.end((err, res)=>{
|
||||||
|
if(err) return;
|
||||||
|
var brew = res.body;
|
||||||
|
window.location = '/edit/' + brew.editId;
|
||||||
|
});
|
||||||
|
},
|
||||||
handleSplitMove : function(){
|
handleSplitMove : function(){
|
||||||
this.refs.editor.update();
|
this.refs.editor.update();
|
||||||
},
|
},
|
||||||
@@ -33,18 +49,20 @@ var HomePage = React.createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
renderNavbar : function(){
|
renderNavbar : function(){
|
||||||
return <Navbar>
|
return <Navbar ver={this.props.ver}>
|
||||||
<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>
|
||||||
},
|
},
|
||||||
@@ -60,7 +78,11 @@ var HomePage = React.createClass({
|
|||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href='/homebrew/new' className='floatingNewButton'>
|
<div className={cx('floatingSaveButton', {show : this.props.welcomeText != this.state.text})} onClick={this.handleSave}>
|
||||||
|
Save current <i className='fa fa-save' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href='/new' className='floatingNewButton'>
|
||||||
Create your own <i className='fa fa-magic' />
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,17 +8,6 @@ The Homebrewery makes the creation and sharing of authentic looking Fifth-Editio
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Features
|
|
||||||
* Monster Stat Blocks
|
|
||||||
* Full class tables
|
|
||||||
* Notes and Tables
|
|
||||||
* Images
|
|
||||||
* Page numbering and footers
|
|
||||||
* Vertical spacing, column breaks, and multiple pages
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Editing and Sharing
|
### Editing and Sharing
|
||||||
When you create your own homebrew you will be given a *edit url* and a *share url*. Any changes you make will be automatically saved to the database within a few seconds. Anyone with the edit url will be able to make edits to your homebrew. So be careful about who you share it with.
|
When you create your own homebrew you will be given a *edit url* and a *share url*. Any changes you make will be automatically saved to the database within a few seconds. Anyone with the edit url will be able to make edits to your homebrew. So be careful about who you share it with.
|
||||||
|
|
||||||
@@ -29,25 +18,12 @@ Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/stolk
|
|||||||
|
|
||||||
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
|
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
|
||||||
|
|
||||||
### Bugs, Issues, Suggestions?
|
|
||||||
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://github.com/stolksdorf/NaturalCrit/issues/new) and let me know!.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
## New Things in v2.0.0!
|
|
||||||
What's new in the latest update? Check out the full changelog [here](/homebrew/changelog)
|
|
||||||
|
|
||||||
* **A whole new look** The site has been re-built from the ground up!
|
|
||||||
* **Better editor and Split Pane** Syntax highlighting will make writing your brews even easier, and now you can customize how large your editor is.
|
|
||||||
* **More reliable rendering** Lots of work has been put into making the rendering more reliable, not just for web, but also for PDFs
|
|
||||||
* **PDF Printing on Chrome** You don't need to use Chrome Canary anymore!
|
|
||||||
* ** Performance Improvements** The site should load faster, save faster, and render large brews *much* faster.
|
|
||||||
* **Patreon page** If you like this tool and want to show some thanks you can [head here](https://www.patreon.com/stolksdorf).
|
|
||||||
|
|
||||||
>##### PDF Exporting
|
>##### PDF Exporting
|
||||||
> After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up
|
> PDF Printing works best in Chrome. If you are having quality/consistency issues, try using Chrome to print instead.
|
||||||
|
>
|
||||||
|
> After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up.
|
||||||
> * Set the **Destination** to "Save as PDF"
|
> * Set the **Destination** to "Save as PDF"
|
||||||
> * Set **Paper Size** to "Letter"
|
> * Set **Paper Size** to "Letter"
|
||||||
> * If you are printing on A4 paper, make sure to have the "A4 page size snippet" in your brew
|
> * If you are printing on A4 paper, make sure to have the "A4 page size snippet" in your brew
|
||||||
@@ -57,7 +33,25 @@ What's new in the latest update? Check out the full changelog [here](/homebrew/c
|
|||||||
> If you want to save ink or have a monochrome printer, add the **Ink Friendly** snippet to your brew before you print
|
> If you want to save ink or have a monochrome printer, add the **Ink Friendly** snippet to your brew before you print
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
## Big things coming in v3.0.0
|
||||||
|
With the next major release of Homebrewery, v3.0.0, this tool *will no longer support raw HTML input for brew code*. All brews made previous to the release of v3.0.0 will still render normally.
|
||||||
|
|
||||||
|
## New Things All The Time!
|
||||||
|
What's new in the latest update? Check out the full changelog [here](/changelog)
|
||||||
|
|
||||||
|
### Bugs, Issues, Suggestions?
|
||||||
|
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://github.com/stolksdorf/homebrewery/issues/new) and let me know!.
|
||||||
|
|
||||||
|
### Legal Junk
|
||||||
|
The Homebrewery is licensed using the [MIT License](https://github.com/stolksdorf/homebrewery/blob/master/license). Which means you are free to use The Homebrewery is any way that you want, except for claiming that you made it yourself.
|
||||||
|
|
||||||
|
If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||||
|
|
||||||
|
### More Resources
|
||||||
|
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/comments/3uwxx9/resources_open_to_the_community/).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -84,18 +78,17 @@ ___
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Images
|
### Images
|
||||||
Images can be included 'inline' with the text using Markdown-style images. However for background images more control is needed.
|
Images must be hosted online somewhere, like imgur. You use the address to that image to reference it in your brew. Images can be included 'inline' with the text using Markdown-style images. However for background images more control is needed.
|
||||||
|
|
||||||
Background images should be included as HTML-style img tags. Using inline CSS you can precisely position your image where you'd like it to be. I have added both a inflow image snippet and a background image snippet to give you exmaples of how to do it.
|
Background images should be included as HTML-style img tags. Using inline CSS you can precisely position your image where you'd like it to be. I have added both a inflow image snippet and a background image snippet to give you exmaples of how to do it.
|
||||||
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Legal Junk
|
|
||||||
You are free to use The Homebrewery is any way that you want, except for claiming that you made it yourself. If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
|
||||||
|
|
||||||
### Crediting Me
|
### Crediting Me
|
||||||
If you'd like to credit The Homebrewery in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
|
If you'd like to credit The Homebrewery in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
|
||||||
|
|
||||||
@@ -1,76 +1,92 @@
|
|||||||
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");
|
const request = require("superagent");
|
||||||
|
|
||||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
var Navbar = require('../../navbar/navbar.jsx');
|
|
||||||
var EditTitle = require('../../navbar/editTitle.navitem.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
|
const AccountNavItem = require('../../navbar/account.navitem.jsx');
|
||||||
|
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||||
|
|
||||||
|
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
||||||
|
const Editor = require('../../editor/editor.jsx');
|
||||||
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
|
|
||||||
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
const KEY = 'homebrewery-new';
|
||||||
var Editor = require('../../editor/editor.jsx');
|
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
|
||||||
|
|
||||||
|
const NewPage = React.createClass({
|
||||||
const KEY = 'naturalCrit-homebrew-new';
|
|
||||||
|
|
||||||
var NewPage = React.createClass({
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
title : 'My Awesome Brew v99',
|
metadata : {
|
||||||
text: '',
|
title : '',
|
||||||
isSaving : false
|
description : '',
|
||||||
};
|
tags : '',
|
||||||
|
published : false,
|
||||||
|
authors : [],
|
||||||
|
systems : []
|
||||||
},
|
},
|
||||||
|
|
||||||
|
text: '',
|
||||||
|
isSaving : false,
|
||||||
|
errors : []
|
||||||
|
};
|
||||||
|
},
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
var storage = localStorage.getItem(KEY);
|
const storage = localStorage.getItem(KEY);
|
||||||
if(storage){
|
if(storage){
|
||||||
this.setState({
|
this.setState({
|
||||||
text : storage
|
text : storage
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
window.onbeforeunload = (e)=>{
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
if(this.state.text == '') return;
|
},
|
||||||
return "Your homebrew isn't saved. Are you sure you want to leave?";
|
componentWillUnmount: function() {
|
||||||
};
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleControlKeys : function(e){
|
||||||
componentWillUnmount: function() {
|
if(!(e.ctrlKey || e.metaKey)) return;
|
||||||
window.onbeforeunload = function(){};
|
const S_KEY = 83;
|
||||||
|
const P_KEY = 80;
|
||||||
|
if(e.keyCode == S_KEY) this.save();
|
||||||
|
if(e.keyCode == P_KEY) this.print();
|
||||||
|
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSplitMove : function(){
|
handleSplitMove : function(){
|
||||||
this.refs.editor.update();
|
this.refs.editor.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTitleChange : function(title){
|
handleMetadataChange : function(metadata){
|
||||||
this.setState({
|
this.setState({
|
||||||
title : title
|
metadata : _.merge({}, this.state.metadata, metadata)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTextChange : function(text){
|
handleTextChange : function(text){
|
||||||
this.setState({
|
this.setState({
|
||||||
text : text
|
text : text,
|
||||||
|
errors : Markdown.validate(text)
|
||||||
});
|
});
|
||||||
localStorage.setItem(KEY, text);
|
localStorage.setItem(KEY, text);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSave : function(){
|
save : function(){
|
||||||
this.setState({
|
this.setState({
|
||||||
isSaving : true
|
isSaving : true
|
||||||
});
|
});
|
||||||
request.post('/homebrew/api')
|
|
||||||
.send({
|
|
||||||
title : this.state.title,
|
|
||||||
text : this.state.text
|
|
||||||
})
|
|
||||||
.end((err, res)=>{
|
|
||||||
|
|
||||||
|
request.post('/api')
|
||||||
|
.send(_.merge({}, this.state.metadata, {
|
||||||
|
text : this.state.text
|
||||||
|
}))
|
||||||
|
.end((err, res)=>{
|
||||||
if(err){
|
if(err){
|
||||||
this.setState({
|
this.setState({
|
||||||
isSaving : false
|
isSaving : false
|
||||||
@@ -78,9 +94,9 @@ var NewPage = React.createClass({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.onbeforeunload = function(){};
|
window.onbeforeunload = function(){};
|
||||||
var brew = res.body;
|
const brew = res.body;
|
||||||
localStorage.removeItem(KEY);
|
localStorage.removeItem(KEY);
|
||||||
window.location = '/homebrew/edit/' + brew.editId;
|
window.location = '/edit/' + brew.editId;
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -90,24 +106,35 @@ var NewPage = React.createClass({
|
|||||||
save...
|
save...
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
}else{
|
}else{
|
||||||
return <Nav.item icon='fa-save' className='saveButton' onClick={this.handleSave}>
|
return <Nav.item icon='fa-save' className='saveButton' onClick={this.save}>
|
||||||
save
|
save
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
print : function(){
|
||||||
|
localStorage.setItem('print', this.state.text);
|
||||||
|
window.open('/print?dialog=true&local=print','_blank');
|
||||||
|
},
|
||||||
|
|
||||||
|
renderLocalPrintButton : function(){
|
||||||
|
return <Nav.item color='purple' icon='fa-file-pdf-o' onClick={this.print}>
|
||||||
|
get PDF
|
||||||
|
</Nav.item>
|
||||||
|
},
|
||||||
|
|
||||||
renderNavbar : function(){
|
renderNavbar : function(){
|
||||||
return <Navbar>
|
return <Navbar>
|
||||||
|
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<EditTitle title={this.state.title} onChange={this.handleTitleChange} />
|
<Nav.item className='brewTitle'>{this.state.metadata.title}</Nav.item>
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
|
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
{this.renderSaveButton()}
|
{this.renderSaveButton()}
|
||||||
<Nav.item newTab={true} href='https://github.com/stolksdorf/naturalcrit/issues' color='red' icon='fa-bug'>
|
{this.renderLocalPrintButton()}
|
||||||
report issue
|
<IssueNavItem />
|
||||||
</Nav.item>
|
<AccountNavItem />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
},
|
},
|
||||||
@@ -115,12 +142,16 @@ var NewPage = React.createClass({
|
|||||||
render : function(){
|
render : function(){
|
||||||
return <div className='newPage page'>
|
return <div className='newPage page'>
|
||||||
{this.renderNavbar()}
|
{this.renderNavbar()}
|
||||||
|
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
<Editor
|
||||||
<BrewRenderer text={this.state.text} />
|
ref='editor'
|
||||||
|
value={this.state.text}
|
||||||
|
onChange={this.handleTextChange}
|
||||||
|
metadata={this.state.metadata}
|
||||||
|
onMetadataChange={this.handleMetadataChange}
|
||||||
|
/>
|
||||||
|
<BrewRenderer text={this.state.text} errors={this.state.errors} />
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
47
client/homebrew/pages/printPage/printPage.jsx
Normal file
47
client/homebrew/pages/printPage/printPage.jsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
const Markdown = require('naturalcrit/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,19 @@
|
|||||||
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');
|
|
||||||
|
|
||||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
var SharePage = React.createClass({
|
|
||||||
|
const SharePage = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
brew : {
|
brew : {
|
||||||
@@ -23,6 +27,22 @@ var SharePage = React.createClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
|
},
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
document.removeEventListener('keydown', this.handleControlKeys);
|
||||||
|
},
|
||||||
|
handleControlKeys : function(e){
|
||||||
|
if(!(e.ctrlKey || e.metaKey)) return;
|
||||||
|
const P_KEY = 80;
|
||||||
|
if(e.keyCode == P_KEY){
|
||||||
|
window.open(`/print/${this.props.brew.shareId}?dialog=true`, '_blank').focus();
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='sharePage page'>
|
return <div className='sharePage page'>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
@@ -31,10 +51,13 @@ var SharePage = React.createClass({
|
|||||||
</Nav.section>
|
</Nav.section>
|
||||||
|
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
|
<ReportIssue />
|
||||||
|
{/*<RecentlyViewed brew={this.props.brew} />*/}
|
||||||
<PrintLink shareId={this.props.brew.shareId} />
|
<PrintLink shareId={this.props.brew.shareId} />
|
||||||
<Nav.item href={'/homebrew/source/' + this.props.brew.shareId} color='teal' icon='fa-code'>
|
<Nav.item href={'/source/' + this.props.brew.shareId} color='teal' icon='fa-code'>
|
||||||
source
|
source
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
|
<Account />
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
|
|||||||
75
client/homebrew/pages/userPage/brewItem/brewItem.jsx
Normal file
75
client/homebrew/pages/userPage/brewItem/brewItem.jsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
const moment = require('moment');
|
||||||
|
const request = require("superagent");
|
||||||
|
|
||||||
|
const BrewItem = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
brew : {
|
||||||
|
title : '',
|
||||||
|
description : '',
|
||||||
|
|
||||||
|
authors : []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteBrew : function(){
|
||||||
|
if(!confirm("are you sure you want to delete this brew?")) return;
|
||||||
|
if(!confirm("are you REALLY sure? You will not be able to recover it")) return;
|
||||||
|
|
||||||
|
request.get('/api/remove/' + this.props.brew.editId)
|
||||||
|
.send()
|
||||||
|
.end(function(err, res){
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDeleteBrewLink: function(){
|
||||||
|
if(!this.props.brew.editId) return;
|
||||||
|
|
||||||
|
return <a onClick={this.deleteBrew}>
|
||||||
|
<i className='fa fa-trash' />
|
||||||
|
</a>
|
||||||
|
},
|
||||||
|
renderEditLink: function(){
|
||||||
|
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()}
|
||||||
|
{this.renderDeleteBrewLink()}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
client/homebrew/pages/userPage/userPage.jsx
Normal file
75
client/homebrew/pages/userPage/userPage.jsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
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();
|
||||||
|
|
||||||
|
return <div className='userPage page'>
|
||||||
|
<Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<RecentNavItem.both />
|
||||||
|
<Account />
|
||||||
|
</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;
|
||||||
@@ -59,22 +67,25 @@
|
|||||||
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;
|
||||||
@@ -184,8 +195,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//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-image : none;
|
|
||||||
border-style : none;
|
border-style : none;
|
||||||
|
border-image : 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 : stretch;
|
||||||
|
border-image-slice : 150 200 150 200;
|
||||||
|
border-image-source : @frameBorderImage;
|
||||||
|
border-image-width : 47px;
|
||||||
|
h5{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//************************************
|
||||||
|
// * DESCRIPTIVE TEXT BOX
|
||||||
|
// ************************************/
|
||||||
|
.phb .descriptive{
|
||||||
|
display : block-inline;
|
||||||
|
margin-bottom : 1em;
|
||||||
|
background-color : #faf7ea;
|
||||||
|
font-family : ScalySans;
|
||||||
|
border-style : solid;
|
||||||
|
border-width : 7px;
|
||||||
|
border-image : @descriptiveBoxImage 12 stretch;
|
||||||
|
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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
5
config/default.json
Normal file
5
config/default.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"host" : "homebrewery.local.naturalcrit.com:8000",
|
||||||
|
"naturalcrit_url" : "local.naturalcrit.com:8010",
|
||||||
|
"secret" : "secret"
|
||||||
|
}
|
||||||
51
gulpfile.js
51
gulpfile.js
@@ -1,51 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
var vitreumTasks = require("vitreum/tasks");
|
|
||||||
var gulp = require("gulp");
|
|
||||||
|
|
||||||
|
|
||||||
var gulp = vitreumTasks(gulp, {
|
|
||||||
entryPoints: [
|
|
||||||
'./client/main',
|
|
||||||
'./client/homebrew',
|
|
||||||
'./client/admin'
|
|
||||||
],
|
|
||||||
|
|
||||||
DEV: true,
|
|
||||||
buildPath: "./build/",
|
|
||||||
pageTemplate: "./client/template.dot",
|
|
||||||
projectModules: ["./shared/naturalcrit","./shared/codemirror"],
|
|
||||||
additionalRequirePaths : ['./shared', './node_modules'],
|
|
||||||
assetExts: ["*.svg", "*.png", "*.jpg", "*.pdf", "*.eot", "*.otf", "*.woff", "*.woff2", "*.ico", "*.ttf"],
|
|
||||||
serverWatchPaths: ["server"],
|
|
||||||
serverScript: "server.js",
|
|
||||||
libs: [
|
|
||||||
"react",
|
|
||||||
"react-dom",
|
|
||||||
"lodash",
|
|
||||||
"classnames",
|
|
||||||
|
|
||||||
//From ./shared
|
|
||||||
"codemirror",
|
|
||||||
"codemirror/mode/gfm/gfm.js",
|
|
||||||
'codemirror/mode/javascript/javascript.js',
|
|
||||||
|
|
||||||
"moment",
|
|
||||||
"superagent",
|
|
||||||
"marked",
|
|
||||||
"pico-router",
|
|
||||||
"pico-flux"
|
|
||||||
],
|
|
||||||
clientLibs: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var rename = require('gulp-rename');
|
|
||||||
var less = require('gulp-less');
|
|
||||||
gulp.task('phb', function(){
|
|
||||||
gulp.src('./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.
|
||||||
24
package.json
24
package.json
@@ -1,24 +1,32 @@
|
|||||||
{
|
{
|
||||||
"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": "2.7.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "gulp prod",
|
"dev": "node scripts/dev.js",
|
||||||
|
"quick": "node scripts/quick.js",
|
||||||
|
"build": "node scripts/build.js",
|
||||||
|
"phb": "node scripts/phb.js",
|
||||||
|
"prod": "set NODE_ENV=production&& npm run build",
|
||||||
|
"postinstall": "npm run build",
|
||||||
"start": "node server.js"
|
"start": "node server.js"
|
||||||
},
|
},
|
||||||
"author": "stolksdorf",
|
"author": "stolksdorf",
|
||||||
"license": "BSD-2-Clause",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-module-path": "^1.0.4",
|
"babel-preset-env": "^1.1.8",
|
||||||
"basic-auth": "^1.0.3",
|
"basic-auth": "^1.0.3",
|
||||||
"body-parser": "^1.14.2",
|
"body-parser": "^1.14.2",
|
||||||
"classnames": "^2.2.0",
|
"classnames": "^2.2.0",
|
||||||
|
"codemirror": "^5.22.0",
|
||||||
|
"cookie-parser": "^1.4.3",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"gulp": "^3.9.0",
|
"jwt-simple": "^0.5.1",
|
||||||
"lodash": "^4.11.2",
|
"lodash": "^4.11.2",
|
||||||
"marked": "^0.3.5",
|
"marked": "^0.3.5",
|
||||||
"moment": "^2.11.0",
|
"moment": "^2.11.0",
|
||||||
"mongoose": "^4.3.3",
|
"mongoose": "^4.3.3",
|
||||||
|
"nconf": "^0.8.4",
|
||||||
"pico-flux": "^1.1.0",
|
"pico-flux": "^1.1.0",
|
||||||
"pico-router": "^1.1.0",
|
"pico-router": "^1.1.0",
|
||||||
"react": "^15.0.2",
|
"react": "^15.0.2",
|
||||||
@@ -26,6 +34,6 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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);
|
||||||
23
scripts/dev.js
Normal file
23
scripts/dev.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
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);
|
||||||
187
server.js
187
server.js
@@ -1,67 +1,140 @@
|
|||||||
'use strict';
|
const _ = require('lodash');
|
||||||
var _ = require('lodash');
|
const jwt = require('jwt-simple');
|
||||||
require('app-module-path').addPath('./shared');
|
const express = require("express");
|
||||||
var vitreumRender = require('vitreum/render');
|
const app = express();
|
||||||
var bodyParser = require('body-parser')
|
|
||||||
var express = require("express");
|
|
||||||
var app = express();
|
|
||||||
app.use(express.static(__dirname + '/build'));
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
|
|
||||||
//Mongoose
|
app.use(express.static(__dirname + '/build'));''
|
||||||
var mongoose = require('mongoose');
|
app.use(require('body-parser').json({limit: '25mb'}));
|
||||||
var mongoUri = process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || 'mongodb://localhost/naturalcrit';
|
app.use(require('cookie-parser')());
|
||||||
mongoose.connect(mongoUri);
|
|
||||||
mongoose.connection.on('error', function(){
|
const config = require('nconf')
|
||||||
console.log(">>>ERROR: Run Mongodb.exe ya goof!");
|
.argv()
|
||||||
|
.env({ lowerCase: true })
|
||||||
|
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
|
||||||
|
.file('defaults', { file: 'config/default.json' });
|
||||||
|
|
||||||
|
//DB
|
||||||
|
require('mongoose')
|
||||||
|
.connect(process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/naturalcrit')
|
||||||
|
.connection.on('error', () => {
|
||||||
|
console.log('Error : Could not connect to a Mongo Database.');
|
||||||
|
console.log(' If you are running locally, make sure mongodb.exe is running.');
|
||||||
});
|
});
|
||||||
|
|
||||||
//Admin route
|
|
||||||
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
//Account MIddleware
|
||||||
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password';
|
app.use((req, res, next) => {
|
||||||
process.env.ADMIN_KEY = process.env.ADMIN_KEY || 'admin_key';
|
if(req.cookies && req.cookies.nc_session){
|
||||||
var auth = require('basic-auth');
|
try{
|
||||||
app.get('/admin', function(req, res){
|
req.account = jwt.decode(req.cookies.nc_session, config.get('secret'));
|
||||||
var credentials = auth(req)
|
}catch(e){}
|
||||||
if (!credentials || credentials.name !== process.env.ADMIN_USER || credentials.pass !== process.env.ADMIN_PASS) {
|
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="example"')
|
|
||||||
return res.status(401).send('Access denied')
|
|
||||||
}
|
}
|
||||||
vitreumRender({
|
return next();
|
||||||
page: './build/admin/bundle.dot',
|
});
|
||||||
prerenderWith : './client/admin/admin.jsx',
|
|
||||||
clearRequireCache : !process.env.PRODUCTION,
|
|
||||||
initialProps: {
|
app.use(require('./server/homebrew.api.js'));
|
||||||
|
app.use(require('./server/admin.api.js'));
|
||||||
|
|
||||||
|
|
||||||
|
const HomebrewModel = require('./server/homebrew.model.js').model;
|
||||||
|
const welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
|
||||||
|
const changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
|
||||||
|
|
||||||
|
|
||||||
|
//Source page
|
||||||
|
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)}
|
||||||
|
app.get('/source/:id', (req, res)=>{
|
||||||
|
HomebrewModel.get({shareId : req.params.id})
|
||||||
|
.then((brew)=>{
|
||||||
|
const text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
|
||||||
|
return res.send(`<code><pre>${text}</pre></code>`);
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err);
|
||||||
|
return res.status(404).send('Could not find Homebrew with that id');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/user/:username', (req, res, next) => {
|
||||||
|
const fullAccess = req.account && (req.account.username == req.params.username);
|
||||||
|
HomebrewModel.getByUser(req.params.username, fullAccess)
|
||||||
|
.then((brews) => {
|
||||||
|
req.brews = brews;
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/edit/:id', (req, res, next)=>{
|
||||||
|
HomebrewModel.get({editId : req.params.id})
|
||||||
|
.then((brew)=>{
|
||||||
|
req.brew = brew.sanatize();
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err);
|
||||||
|
return res.status(400).send(`Can't get that`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Share Page
|
||||||
|
app.get('/share/:id', (req, res, next)=>{
|
||||||
|
HomebrewModel.get({shareId : req.params.id})
|
||||||
|
.then((brew)=>{
|
||||||
|
return brew.increaseView();
|
||||||
|
})
|
||||||
|
.then((brew)=>{
|
||||||
|
req.brew = brew.sanatize(true);
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err);
|
||||||
|
return res.status(400).send(`Can't get that`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Print Page
|
||||||
|
app.get('/print/:id', (req, res, next)=>{
|
||||||
|
HomebrewModel.get({shareId : req.params.id})
|
||||||
|
.then((brew)=>{
|
||||||
|
req.brew = brew.sanatize(true);
|
||||||
|
return next();
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err);
|
||||||
|
return res.status(400).send(`Can't get that`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//Render Page
|
||||||
|
const render = require('vitreum/steps/render');
|
||||||
|
const templateFn = require('./client/template.js');
|
||||||
|
app.use((req, res) => {
|
||||||
|
render('homebrew', templateFn, {
|
||||||
|
version : require('./package.json').version,
|
||||||
url: req.originalUrl,
|
url: req.originalUrl,
|
||||||
admin_key : process.env.ADMIN_KEY,
|
welcomeText : welcomeText,
|
||||||
},
|
changelog : changelogText,
|
||||||
}, function (err, page) {
|
brew : req.brew,
|
||||||
|
brews : req.brews,
|
||||||
|
account : req.account
|
||||||
|
})
|
||||||
|
.then((page) => {
|
||||||
return res.send(page)
|
return res.send(page)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return res.sendStatus(500);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
//Populate homebrew routes
|
const PORT = process.env.PORT || 8000;
|
||||||
app = require('./server/homebrew.api.js')(app);
|
app.listen(PORT);
|
||||||
app = require('./server/homebrew.server.js')(app);
|
console.log(`server on port:${PORT}`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
85
server/admin.api.js
Normal file
85
server/admin.api.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const auth = require('basic-auth');
|
||||||
|
const HomebrewModel = require('./homebrew.model.js').model;
|
||||||
|
const router = require('express').Router();
|
||||||
|
|
||||||
|
|
||||||
|
const mw = {
|
||||||
|
adminOnly : (req, res, next)=>{
|
||||||
|
if(req.query && req.query.admin_key == process.env.ADMIN_KEY) return next();
|
||||||
|
return res.status(401).send('Access denied');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
||||||
|
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password';
|
||||||
|
process.env.ADMIN_KEY = process.env.ADMIN_KEY || 'admin_key';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
|
||||||
|
router.get('/api/invalid', mw.adminOnly, (req, res)=>{
|
||||||
|
const invalidBrewQuery = HomebrewModel.find({
|
||||||
|
'$where' : "this.text.length < 140",
|
||||||
|
createdAt: {
|
||||||
|
$lt: Moment().subtract(3, 'days').toDate()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(req.query.do_it){
|
||||||
|
invalidBrewQuery.remove().exec((err, objs)=>{
|
||||||
|
refreshCount();
|
||||||
|
return res.send(200);
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
invalidBrewQuery.exec((err, objs)=>{
|
||||||
|
if(err) console.log(err);
|
||||||
|
return res.json({
|
||||||
|
count : objs.length
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/admin/lookup/:id', mw.adminOnly, (req, res, next) => {
|
||||||
|
//search for mathcing edit id
|
||||||
|
//search for matching share id
|
||||||
|
// search for partial match
|
||||||
|
|
||||||
|
HomebrewModel.findOne({ $or:[
|
||||||
|
{editId : { "$regex": req.params.id, "$options": "i" }},
|
||||||
|
{shareId : { "$regex": req.params.id, "$options": "i" }},
|
||||||
|
]}).exec((err, brew) => {
|
||||||
|
return res.json(brew);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Admin route
|
||||||
|
|
||||||
|
const render = require('vitreum/steps/render');
|
||||||
|
const templateFn = require('../client/template.js');
|
||||||
|
router.get('/admin', function(req, res){
|
||||||
|
const credentials = auth(req)
|
||||||
|
if (!credentials || credentials.name !== process.env.ADMIN_USER || credentials.pass !== process.env.ADMIN_PASS) {
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="example"')
|
||||||
|
return res.status(401).send('Access denied')
|
||||||
|
}
|
||||||
|
render('admin', templateFn, {
|
||||||
|
url: req.originalUrl,
|
||||||
|
admin_key : process.env.ADMIN_KEY,
|
||||||
|
})
|
||||||
|
.then((page) => {
|
||||||
|
return res.send(page)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
return res.sendStatus(500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -1,104 +1,125 @@
|
|||||||
var _ = require('lodash');
|
const _ = require('lodash');
|
||||||
var Moment = require('moment');
|
const Moment = require('moment');
|
||||||
var HomebrewModel = require('./homebrew.model.js').model;
|
const HomebrewModel = require('./homebrew.model.js').model;
|
||||||
|
const router = require('express').Router();
|
||||||
|
|
||||||
var homebrewTotal = 0;
|
|
||||||
var refreshCount = function(){
|
|
||||||
HomebrewModel.count({}, function(err, total){
|
//TODO: Possiblity remove
|
||||||
|
let homebrewTotal = 0;
|
||||||
|
const refreshCount = ()=>{
|
||||||
|
HomebrewModel.count({}, (err, total)=>{
|
||||||
homebrewTotal = total;
|
homebrewTotal = total;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
refreshCount()
|
refreshCount();
|
||||||
|
|
||||||
var mw = {
|
|
||||||
adminOnly : function(req, res, next){
|
|
||||||
if(req.query && req.query.admin_key == process.env.ADMIN_KEY){
|
|
||||||
next();
|
|
||||||
}else{
|
|
||||||
return res.status(401).send('Access denied');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var getTopBrews = function(cb){
|
|
||||||
|
const getTopBrews = (cb)=>{
|
||||||
HomebrewModel.find().sort({views: -1}).limit(5).exec(function(err, brews) {
|
HomebrewModel.find().sort({views: -1}).limit(5).exec(function(err, brews) {
|
||||||
cb(brews);
|
cb(brews);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getGoodBrewTitle = (text) => {
|
||||||
module.exports = function(app){
|
const titlePos = text.indexOf('# ');
|
||||||
|
if(titlePos !== -1){
|
||||||
app.get('/homebrew/top', function(req, res){
|
const ending = text.indexOf('\n', titlePos);
|
||||||
getTopBrews(function(topBrews){
|
return text.substring(titlePos + 2, ending);
|
||||||
return res.json(topBrews);
|
}else{
|
||||||
});
|
return _.find(text.split('\n'), (line)=>{
|
||||||
|
return line;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
app.post('/homebrew/api', function(req, res){
|
|
||||||
var newHomebrew = new HomebrewModel(req.body);
|
|
||||||
newHomebrew.save(function(err, obj){
|
router.post('/api', (req, res)=>{
|
||||||
if(err) return;
|
|
||||||
|
let authors = [];
|
||||||
|
if(req.account) authors = [req.account.username];
|
||||||
|
|
||||||
|
const newHomebrew = new HomebrewModel(_.merge({},
|
||||||
|
req.body,
|
||||||
|
{authors : authors}
|
||||||
|
));
|
||||||
|
if(!newHomebrew.title){
|
||||||
|
newHomebrew.title = getGoodBrewTitle(newHomebrew.text);
|
||||||
|
}
|
||||||
|
newHomebrew.save((err, obj)=>{
|
||||||
|
if(err){
|
||||||
|
console.error(err, err.toString(), err.stack);
|
||||||
|
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
|
||||||
|
}
|
||||||
return res.json(obj);
|
return res.json(obj);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/homebrew/api/update/:id', function(req, res){
|
router.put('/api/update/:id', (req, res)=>{
|
||||||
HomebrewModel.find({editId : req.params.id}, function(err, objs){
|
HomebrewModel.get({editId : req.params.id})
|
||||||
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
|
.then((brew)=>{
|
||||||
var resEntry = objs[0];
|
brew = _.merge(brew, req.body);
|
||||||
resEntry.text = req.body.text;
|
brew.updatedAt = new Date();
|
||||||
resEntry.title = req.body.title;
|
if(req.account) brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
||||||
resEntry.updatedAt = new Date();
|
|
||||||
resEntry.save(function(err, obj){
|
brew.markModified('authors');
|
||||||
if(err) return res.status(500).send("Error while saving");
|
brew.markModified('systems');
|
||||||
|
|
||||||
|
brew.save((err, obj)=>{
|
||||||
|
if(err) throw err;
|
||||||
return res.status(200).send(obj);
|
return res.status(200).send(obj);
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err);
|
||||||
|
return res.status(500).send("Error while saving");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/homebrew/api/remove/:id', function(req, res){
|
router.get('/api/remove/:id', (req, res)=>{
|
||||||
HomebrewModel.find({editId : req.params.id}, function(err, objs){
|
HomebrewModel.find({editId : req.params.id}, (err, objs)=>{
|
||||||
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
|
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
|
||||||
var resEntry = objs[0];
|
var resEntry = objs[0];
|
||||||
resEntry.remove(function(err){
|
resEntry.remove((err)=>{
|
||||||
if(err) return res.status(500).send("Error while removing");
|
if(err) return res.status(500).send("Error while removing");
|
||||||
return res.status(200).send();
|
return res.status(200).send();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
//Removes all empty brews that are older than 3 days and that are shorter than a tweet
|
|
||||||
app.get('/homebrew/api/invalid', mw.adminOnly, function(req, res){
|
|
||||||
var invalidBrewQuery = HomebrewModel.find({
|
|
||||||
'$where' : "this.text.length < 140",
|
|
||||||
createdAt: {
|
|
||||||
$lt: Moment().subtract(3, 'days').toDate()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(req.query.do_it){
|
module.exports = router;
|
||||||
invalidBrewQuery.remove().exec((err, objs)=>{
|
|
||||||
refreshCount();
|
/*
|
||||||
return res.send(200);
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
invalidBrewQuery.exec((err, objs)=>{
|
|
||||||
if(err) console.log(err);
|
|
||||||
return res.json({
|
|
||||||
count : objs.length
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
app.get('/homebrew/api/search', mw.adminOnly, function(req, res){
|
|
||||||
|
module.exports = function(app){
|
||||||
|
|
||||||
|
app;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/api/search', mw.adminOnly, function(req, res){
|
||||||
|
|
||||||
var page = req.query.page || 0;
|
var page = req.query.page || 0;
|
||||||
var count = req.query.count || 20;
|
var count = req.query.count || 20;
|
||||||
|
|
||||||
HomebrewModel.find({}, {
|
var query = {};
|
||||||
|
if(req.query && req.query.id){
|
||||||
|
query = {
|
||||||
|
"$or" : [{
|
||||||
|
editId : req.query.id
|
||||||
|
},{
|
||||||
|
shareId : req.query.id
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
HomebrewModel.find(query, {
|
||||||
text : 0 //omit the text
|
text : 0 //omit the text
|
||||||
}, {
|
}, {
|
||||||
skip: page*count,
|
skip: page*count,
|
||||||
@@ -120,3 +141,4 @@ module.exports = function(app){
|
|||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
@@ -8,11 +8,68 @@ var HomebrewSchema = mongoose.Schema({
|
|||||||
title : {type : String, default : ""},
|
title : {type : String, default : ""},
|
||||||
text : {type : String, default : ""},
|
text : {type : String, default : ""},
|
||||||
|
|
||||||
|
description : {type : String, default : ""},
|
||||||
|
tags : {type : String, default : ""},
|
||||||
|
systems : [String],
|
||||||
|
authors : [String],
|
||||||
|
published : {type : Boolean, default : false},
|
||||||
|
|
||||||
createdAt : { type: Date, default: Date.now },
|
createdAt : { type: Date, default: Date.now },
|
||||||
updatedAt : { type: Date, default: Date.now},
|
updatedAt : { type: Date, default: Date.now},
|
||||||
lastViewed : { type: Date, default: Date.now},
|
lastViewed : { type: Date, default: Date.now},
|
||||||
views : {type:Number, default:0}
|
views : {type:Number, default:0},
|
||||||
|
version : {type: Number, default:1}
|
||||||
|
}, { versionKey: false });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HomebrewSchema.methods.sanatize = function(full=false){
|
||||||
|
const brew = this.toJSON();
|
||||||
|
delete brew._id;
|
||||||
|
delete brew.__v;
|
||||||
|
if(full){
|
||||||
|
delete brew.editId;
|
||||||
|
}
|
||||||
|
return brew;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
HomebrewSchema.methods.increaseView = function(){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.lastViewed = new Date();
|
||||||
|
this.views = this.views + 1;
|
||||||
|
this.save((err) => {
|
||||||
|
if(err) return reject(err);
|
||||||
|
return resolve(this);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HomebrewSchema.statics.get = function(query){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
Homebrew.find(query, (err, brews)=>{
|
||||||
|
if(err || !brews.length) return reject('Can not find brew');
|
||||||
|
return resolve(brews[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let query = {authors : username, published : true};
|
||||||
|
if(allowAccess){
|
||||||
|
delete query.published;
|
||||||
|
}
|
||||||
|
Homebrew.find(query, (err, brews)=>{
|
||||||
|
if(err) return reject('Can not find brew');
|
||||||
|
return resolve(_.map(brews, (brew)=>{
|
||||||
|
return brew.sanatize(!allowAccess);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
var _ = require('lodash');
|
|
||||||
var vitreumRender = require('vitreum/render');
|
|
||||||
var HomebrewModel = require('./homebrew.model.js').model;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(app){
|
|
||||||
|
|
||||||
/*
|
|
||||||
app.get('/homebrew/new', function(req, res){
|
|
||||||
var newHomebrew = new HomebrewModel();
|
|
||||||
newHomebrew.save(function(err, obj){
|
|
||||||
return res.redirect('/homebrew/edit/' + obj.editId);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
//Edit Page
|
|
||||||
app.get('/homebrew/edit/:id', function(req, res){
|
|
||||||
HomebrewModel.find({editId : req.params.id}, function(err, objs){
|
|
||||||
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
|
|
||||||
|
|
||||||
var resObj = null;
|
|
||||||
var errObj = {text: "# oops\nCould not find the homebrew."}
|
|
||||||
if(objs.length){
|
|
||||||
resObj = objs[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
vitreumRender({
|
|
||||||
page: './build/homebrew/bundle.dot',
|
|
||||||
globals:{},
|
|
||||||
prerenderWith : './client/homebrew/homebrew.jsx',
|
|
||||||
initialProps: {
|
|
||||||
url: req.originalUrl,
|
|
||||||
brew : resObj || errObj
|
|
||||||
},
|
|
||||||
clearRequireCache : !process.env.PRODUCTION,
|
|
||||||
}, function (err, page) {
|
|
||||||
return res.send(page)
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//Share Page
|
|
||||||
app.get('/homebrew/share/:id', function(req, res){
|
|
||||||
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
|
|
||||||
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
|
|
||||||
|
|
||||||
var resObj = null;
|
|
||||||
var errObj = {text: "# oops\nCould not find the homebrew."}
|
|
||||||
|
|
||||||
if(objs.length){
|
|
||||||
resObj = objs[0];
|
|
||||||
resObj.lastViewed = new Date();
|
|
||||||
resObj.views = resObj.views + 1;
|
|
||||||
resObj.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
vitreumRender({
|
|
||||||
page: './build/homebrew/bundle.dot',
|
|
||||||
globals:{},
|
|
||||||
prerenderWith : './client/homebrew/homebrew.jsx',
|
|
||||||
initialProps: {
|
|
||||||
url: req.originalUrl,
|
|
||||||
brew : resObj || errObj
|
|
||||||
},
|
|
||||||
clearRequireCache : !process.env.PRODUCTION,
|
|
||||||
}, function (err, page) {
|
|
||||||
return res.send(page)
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
//Print Page
|
|
||||||
var Markdown = require('marked');
|
|
||||||
var PHBStyle = '<style>' + require('fs').readFileSync('./phb.standalone.css', 'utf8') + '</style>'
|
|
||||||
app.get('/homebrew/print/:id', function(req, res){
|
|
||||||
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
|
|
||||||
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
|
|
||||||
|
|
||||||
var brew = null;
|
|
||||||
if(objs.length){
|
|
||||||
brew = objs[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
var content = _.map(brew.text.split('\\page'), function(pageText){
|
|
||||||
return '<div class="phb print">' + Markdown(pageText) + '</div>';
|
|
||||||
}).join('\n');
|
|
||||||
|
|
||||||
var dialog = '';
|
|
||||||
if(req.query && req.query.dialog) dialog = 'onload="window.print()"';
|
|
||||||
|
|
||||||
var title = '<title>' + brew.title + '</title>';
|
|
||||||
var page = `<html><head>${title} ${PHBStyle}</head><body ${dialog}>${content}</body></html>`
|
|
||||||
|
|
||||||
return res.send(page)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//Source page
|
|
||||||
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)}
|
|
||||||
app.get('/homebrew/source/:id', function(req, res){
|
|
||||||
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
|
|
||||||
if(err || !objs.length) return res.status(404).send('Could not find Homebrew with that id');
|
|
||||||
var brew = null;
|
|
||||||
if(objs.length) brew = objs[0];
|
|
||||||
var text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
|
|
||||||
return res.send(`<code><pre>${text}</pre></code>`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//Home and 404, etc.
|
|
||||||
var welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.txt', 'utf8');
|
|
||||||
var changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
|
|
||||||
app.get('/homebrew*', function (req, res) {
|
|
||||||
vitreumRender({
|
|
||||||
page: './build/homebrew/bundle.dot',
|
|
||||||
globals:{},
|
|
||||||
prerenderWith : './client/homebrew/homebrew.jsx',
|
|
||||||
initialProps: {
|
|
||||||
url: req.originalUrl,
|
|
||||||
welcomeText : welcomeText,
|
|
||||||
changelog : changelogText
|
|
||||||
},
|
|
||||||
clearRequireCache : !process.env.PRODUCTION,
|
|
||||||
}, function (err, page) {
|
|
||||||
return res.send(page)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
203
shared/codemirror/addon/comment/comment.js
vendored
203
shared/codemirror/addon/comment/comment.js
vendored
@@ -1,203 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var noOptions = {};
|
|
||||||
var nonWS = /[^\s\u00a0]/;
|
|
||||||
var Pos = CodeMirror.Pos;
|
|
||||||
|
|
||||||
function firstNonWS(str) {
|
|
||||||
var found = str.search(nonWS);
|
|
||||||
return found == -1 ? 0 : found;
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.commands.toggleComment = function(cm) {
|
|
||||||
cm.toggleComment();
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("toggleComment", function(options) {
|
|
||||||
if (!options) options = noOptions;
|
|
||||||
var cm = this;
|
|
||||||
var minLine = Infinity, ranges = this.listSelections(), mode = null;
|
|
||||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
|
||||||
var from = ranges[i].from(), to = ranges[i].to();
|
|
||||||
if (from.line >= minLine) continue;
|
|
||||||
if (to.line >= minLine) to = Pos(minLine, 0);
|
|
||||||
minLine = from.line;
|
|
||||||
if (mode == null) {
|
|
||||||
if (cm.uncomment(from, to, options)) mode = "un";
|
|
||||||
else { cm.lineComment(from, to, options); mode = "line"; }
|
|
||||||
} else if (mode == "un") {
|
|
||||||
cm.uncomment(from, to, options);
|
|
||||||
} else {
|
|
||||||
cm.lineComment(from, to, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rough heuristic to try and detect lines that are part of multi-line string
|
|
||||||
function probablyInsideString(cm, pos, line) {
|
|
||||||
return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"`]/.test(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("lineComment", function(from, to, options) {
|
|
||||||
if (!options) options = noOptions;
|
|
||||||
var self = this, mode = self.getModeAt(from);
|
|
||||||
var firstLine = self.getLine(from.line);
|
|
||||||
if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
|
|
||||||
|
|
||||||
var commentString = options.lineComment || mode.lineComment;
|
|
||||||
if (!commentString) {
|
|
||||||
if (options.blockCommentStart || mode.blockCommentStart) {
|
|
||||||
options.fullLines = true;
|
|
||||||
self.blockComment(from, to, options);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
|
|
||||||
var pad = options.padding == null ? " " : options.padding;
|
|
||||||
var blankLines = options.commentBlankLines || from.line == to.line;
|
|
||||||
|
|
||||||
self.operation(function() {
|
|
||||||
if (options.indent) {
|
|
||||||
var baseString = null;
|
|
||||||
for (var i = from.line; i < end; ++i) {
|
|
||||||
var line = self.getLine(i);
|
|
||||||
var whitespace = line.slice(0, firstNonWS(line));
|
|
||||||
if (baseString == null || baseString.length > whitespace.length) {
|
|
||||||
baseString = whitespace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var i = from.line; i < end; ++i) {
|
|
||||||
var line = self.getLine(i), cut = baseString.length;
|
|
||||||
if (!blankLines && !nonWS.test(line)) continue;
|
|
||||||
if (line.slice(0, cut) != baseString) cut = firstNonWS(line);
|
|
||||||
self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (var i = from.line; i < end; ++i) {
|
|
||||||
if (blankLines || nonWS.test(self.getLine(i)))
|
|
||||||
self.replaceRange(commentString + pad, Pos(i, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("blockComment", function(from, to, options) {
|
|
||||||
if (!options) options = noOptions;
|
|
||||||
var self = this, mode = self.getModeAt(from);
|
|
||||||
var startString = options.blockCommentStart || mode.blockCommentStart;
|
|
||||||
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
|
||||||
if (!startString || !endString) {
|
|
||||||
if ((options.lineComment || mode.lineComment) && options.fullLines != false)
|
|
||||||
self.lineComment(from, to, options);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var end = Math.min(to.line, self.lastLine());
|
|
||||||
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
|
|
||||||
|
|
||||||
var pad = options.padding == null ? " " : options.padding;
|
|
||||||
if (from.line > end) return;
|
|
||||||
|
|
||||||
self.operation(function() {
|
|
||||||
if (options.fullLines != false) {
|
|
||||||
var lastLineHasText = nonWS.test(self.getLine(end));
|
|
||||||
self.replaceRange(pad + endString, Pos(end));
|
|
||||||
self.replaceRange(startString + pad, Pos(from.line, 0));
|
|
||||||
var lead = options.blockCommentLead || mode.blockCommentLead;
|
|
||||||
if (lead != null) for (var i = from.line + 1; i <= end; ++i)
|
|
||||||
if (i != end || lastLineHasText)
|
|
||||||
self.replaceRange(lead + pad, Pos(i, 0));
|
|
||||||
} else {
|
|
||||||
self.replaceRange(endString, to);
|
|
||||||
self.replaceRange(startString, from);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("uncomment", function(from, to, options) {
|
|
||||||
if (!options) options = noOptions;
|
|
||||||
var self = this, mode = self.getModeAt(from);
|
|
||||||
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
|
|
||||||
|
|
||||||
// Try finding line comments
|
|
||||||
var lineString = options.lineComment || mode.lineComment, lines = [];
|
|
||||||
var pad = options.padding == null ? " " : options.padding, didSomething;
|
|
||||||
lineComment: {
|
|
||||||
if (!lineString) break lineComment;
|
|
||||||
for (var i = start; i <= end; ++i) {
|
|
||||||
var line = self.getLine(i);
|
|
||||||
var found = line.indexOf(lineString);
|
|
||||||
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
|
|
||||||
if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment;
|
|
||||||
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
self.operation(function() {
|
|
||||||
for (var i = start; i <= end; ++i) {
|
|
||||||
var line = lines[i - start];
|
|
||||||
var pos = line.indexOf(lineString), endPos = pos + lineString.length;
|
|
||||||
if (pos < 0) continue;
|
|
||||||
if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length;
|
|
||||||
didSomething = true;
|
|
||||||
self.replaceRange("", Pos(i, pos), Pos(i, endPos));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (didSomething) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try block comments
|
|
||||||
var startString = options.blockCommentStart || mode.blockCommentStart;
|
|
||||||
var endString = options.blockCommentEnd || mode.blockCommentEnd;
|
|
||||||
if (!startString || !endString) return false;
|
|
||||||
var lead = options.blockCommentLead || mode.blockCommentLead;
|
|
||||||
var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end);
|
|
||||||
var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString);
|
|
||||||
if (close == -1 && start != end) {
|
|
||||||
endLine = self.getLine(--end);
|
|
||||||
close = endLine.lastIndexOf(endString);
|
|
||||||
}
|
|
||||||
if (open == -1 || close == -1 ||
|
|
||||||
!/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) ||
|
|
||||||
!/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Avoid killing block comments completely outside the selection.
|
|
||||||
// Positions of the last startString before the start of the selection, and the first endString after it.
|
|
||||||
var lastStart = startLine.lastIndexOf(startString, from.ch);
|
|
||||||
var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length);
|
|
||||||
if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false;
|
|
||||||
// Positions of the first endString after the end of the selection, and the last startString before it.
|
|
||||||
firstEnd = endLine.indexOf(endString, to.ch);
|
|
||||||
var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch);
|
|
||||||
lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart;
|
|
||||||
if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false;
|
|
||||||
|
|
||||||
self.operation(function() {
|
|
||||||
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
|
|
||||||
Pos(end, close + endString.length));
|
|
||||||
var openEnd = open + startString.length;
|
|
||||||
if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length;
|
|
||||||
self.replaceRange("", Pos(start, open), Pos(start, openEnd));
|
|
||||||
if (lead) for (var i = start + 1; i <= end; ++i) {
|
|
||||||
var line = self.getLine(i), found = line.indexOf(lead);
|
|
||||||
if (found == -1 || nonWS.test(line.slice(0, found))) continue;
|
|
||||||
var foundEnd = found + lead.length;
|
|
||||||
if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length;
|
|
||||||
self.replaceRange("", Pos(i, found), Pos(i, foundEnd));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
var modes = ["clike", "css", "javascript"];
|
|
||||||
|
|
||||||
for (var i = 0; i < modes.length; ++i)
|
|
||||||
CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "});
|
|
||||||
|
|
||||||
function continueComment(cm) {
|
|
||||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
var ranges = cm.listSelections(), mode, inserts = [];
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
var pos = ranges[i].head, token = cm.getTokenAt(pos);
|
|
||||||
if (token.type != "comment") return CodeMirror.Pass;
|
|
||||||
var modeHere = CodeMirror.innerMode(cm.getMode(), token.state).mode;
|
|
||||||
if (!mode) mode = modeHere;
|
|
||||||
else if (mode != modeHere) return CodeMirror.Pass;
|
|
||||||
|
|
||||||
var insert = null;
|
|
||||||
if (mode.blockCommentStart && mode.blockCommentContinue) {
|
|
||||||
var end = token.string.indexOf(mode.blockCommentEnd);
|
|
||||||
var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found;
|
|
||||||
if (end != -1 && end == token.string.length - mode.blockCommentEnd.length && pos.ch >= end) {
|
|
||||||
// Comment ended, don't continue it
|
|
||||||
} else if (token.string.indexOf(mode.blockCommentStart) == 0) {
|
|
||||||
insert = full.slice(0, token.start);
|
|
||||||
if (!/^\s*$/.test(insert)) {
|
|
||||||
insert = "";
|
|
||||||
for (var j = 0; j < token.start; ++j) insert += " ";
|
|
||||||
}
|
|
||||||
} else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 &&
|
|
||||||
found + mode.blockCommentContinue.length > token.start &&
|
|
||||||
/^\s*$/.test(full.slice(0, found))) {
|
|
||||||
insert = full.slice(0, found);
|
|
||||||
}
|
|
||||||
if (insert != null) insert += mode.blockCommentContinue;
|
|
||||||
}
|
|
||||||
if (insert == null && mode.lineComment && continueLineCommentEnabled(cm)) {
|
|
||||||
var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment);
|
|
||||||
if (found > -1) {
|
|
||||||
insert = line.slice(0, found);
|
|
||||||
if (/\S/.test(insert)) insert = null;
|
|
||||||
else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (insert == null) return CodeMirror.Pass;
|
|
||||||
inserts[i] = "\n" + insert;
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.operation(function() {
|
|
||||||
for (var i = ranges.length - 1; i >= 0; i--)
|
|
||||||
cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function continueLineCommentEnabled(cm) {
|
|
||||||
var opt = cm.getOption("continueComments");
|
|
||||||
if (opt && typeof opt == "object")
|
|
||||||
return opt.continueLineComment !== false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.defineOption("continueComments", null, function(cm, val, prev) {
|
|
||||||
if (prev && prev != CodeMirror.Init)
|
|
||||||
cm.removeKeyMap("continueComment");
|
|
||||||
if (val) {
|
|
||||||
var key = "Enter";
|
|
||||||
if (typeof val == "string")
|
|
||||||
key = val;
|
|
||||||
else if (typeof val == "object" && val.key)
|
|
||||||
key = val.key;
|
|
||||||
var map = {name: "continueComment"};
|
|
||||||
map[key] = continueComment;
|
|
||||||
cm.addKeyMap(map);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
32
shared/codemirror/addon/dialog/dialog.css
vendored
32
shared/codemirror/addon/dialog/dialog.css
vendored
@@ -1,32 +0,0 @@
|
|||||||
.CodeMirror-dialog {
|
|
||||||
position: absolute;
|
|
||||||
left: 0; right: 0;
|
|
||||||
background: inherit;
|
|
||||||
z-index: 15;
|
|
||||||
padding: .1em .8em;
|
|
||||||
overflow: hidden;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-dialog-top {
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-dialog-bottom {
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-dialog input {
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
|
||||||
width: 20em;
|
|
||||||
color: inherit;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror-dialog button {
|
|
||||||
font-size: 70%;
|
|
||||||
}
|
|
||||||
157
shared/codemirror/addon/dialog/dialog.js
vendored
157
shared/codemirror/addon/dialog/dialog.js
vendored
@@ -1,157 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
// Open simple dialogs on top of an editor. Relies on dialog.css.
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
function dialogDiv(cm, template, bottom) {
|
|
||||||
var wrap = cm.getWrapperElement();
|
|
||||||
var dialog;
|
|
||||||
dialog = wrap.appendChild(document.createElement("div"));
|
|
||||||
if (bottom)
|
|
||||||
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
|
|
||||||
else
|
|
||||||
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
|
|
||||||
|
|
||||||
if (typeof template == "string") {
|
|
||||||
dialog.innerHTML = template;
|
|
||||||
} else { // Assuming it's a detached DOM element.
|
|
||||||
dialog.appendChild(template);
|
|
||||||
}
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeNotification(cm, newVal) {
|
|
||||||
if (cm.state.currentNotificationClose)
|
|
||||||
cm.state.currentNotificationClose();
|
|
||||||
cm.state.currentNotificationClose = newVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
|
|
||||||
if (!options) options = {};
|
|
||||||
|
|
||||||
closeNotification(this, null);
|
|
||||||
|
|
||||||
var dialog = dialogDiv(this, template, options.bottom);
|
|
||||||
var closed = false, me = this;
|
|
||||||
function close(newVal) {
|
|
||||||
if (typeof newVal == 'string') {
|
|
||||||
inp.value = newVal;
|
|
||||||
} else {
|
|
||||||
if (closed) return;
|
|
||||||
closed = true;
|
|
||||||
dialog.parentNode.removeChild(dialog);
|
|
||||||
me.focus();
|
|
||||||
|
|
||||||
if (options.onClose) options.onClose(dialog);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var inp = dialog.getElementsByTagName("input")[0], button;
|
|
||||||
if (inp) {
|
|
||||||
inp.focus();
|
|
||||||
|
|
||||||
if (options.value) {
|
|
||||||
inp.value = options.value;
|
|
||||||
if (options.selectValueOnOpen !== false) {
|
|
||||||
inp.select();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.onInput)
|
|
||||||
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
|
|
||||||
if (options.onKeyUp)
|
|
||||||
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
|
|
||||||
|
|
||||||
CodeMirror.on(inp, "keydown", function(e) {
|
|
||||||
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
|
|
||||||
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
|
|
||||||
inp.blur();
|
|
||||||
CodeMirror.e_stop(e);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
if (e.keyCode == 13) callback(inp.value, e);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
|
|
||||||
} else if (button = dialog.getElementsByTagName("button")[0]) {
|
|
||||||
CodeMirror.on(button, "click", function() {
|
|
||||||
close();
|
|
||||||
me.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
|
|
||||||
|
|
||||||
button.focus();
|
|
||||||
}
|
|
||||||
return close;
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
|
|
||||||
closeNotification(this, null);
|
|
||||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
|
||||||
var buttons = dialog.getElementsByTagName("button");
|
|
||||||
var closed = false, me = this, blurring = 1;
|
|
||||||
function close() {
|
|
||||||
if (closed) return;
|
|
||||||
closed = true;
|
|
||||||
dialog.parentNode.removeChild(dialog);
|
|
||||||
me.focus();
|
|
||||||
}
|
|
||||||
buttons[0].focus();
|
|
||||||
for (var i = 0; i < buttons.length; ++i) {
|
|
||||||
var b = buttons[i];
|
|
||||||
(function(callback) {
|
|
||||||
CodeMirror.on(b, "click", function(e) {
|
|
||||||
CodeMirror.e_preventDefault(e);
|
|
||||||
close();
|
|
||||||
if (callback) callback(me);
|
|
||||||
});
|
|
||||||
})(callbacks[i]);
|
|
||||||
CodeMirror.on(b, "blur", function() {
|
|
||||||
--blurring;
|
|
||||||
setTimeout(function() { if (blurring <= 0) close(); }, 200);
|
|
||||||
});
|
|
||||||
CodeMirror.on(b, "focus", function() { ++blurring; });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* openNotification
|
|
||||||
* Opens a notification, that can be closed with an optional timer
|
|
||||||
* (default 5000ms timer) and always closes on click.
|
|
||||||
*
|
|
||||||
* If a notification is opened while another is opened, it will close the
|
|
||||||
* currently opened one and open the new one immediately.
|
|
||||||
*/
|
|
||||||
CodeMirror.defineExtension("openNotification", function(template, options) {
|
|
||||||
closeNotification(this, close);
|
|
||||||
var dialog = dialogDiv(this, template, options && options.bottom);
|
|
||||||
var closed = false, doneTimer;
|
|
||||||
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
if (closed) return;
|
|
||||||
closed = true;
|
|
||||||
clearTimeout(doneTimer);
|
|
||||||
dialog.parentNode.removeChild(dialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.on(dialog, 'click', function(e) {
|
|
||||||
CodeMirror.e_preventDefault(e);
|
|
||||||
close();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (duration)
|
|
||||||
doneTimer = setTimeout(close, duration);
|
|
||||||
|
|
||||||
return close;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
47
shared/codemirror/addon/display/autorefresh.js
vendored
47
shared/codemirror/addon/display/autorefresh.js
vendored
@@ -1,47 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"))
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod)
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror)
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict"
|
|
||||||
|
|
||||||
CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
|
|
||||||
if (cm.state.autoRefresh) {
|
|
||||||
stopListening(cm, cm.state.autoRefresh)
|
|
||||||
cm.state.autoRefresh = null
|
|
||||||
}
|
|
||||||
if (val && cm.display.wrapper.offsetHeight == 0)
|
|
||||||
startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
|
|
||||||
})
|
|
||||||
|
|
||||||
function startListening(cm, state) {
|
|
||||||
function check() {
|
|
||||||
if (cm.display.wrapper.offsetHeight) {
|
|
||||||
stopListening(cm, state)
|
|
||||||
if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
|
|
||||||
cm.refresh()
|
|
||||||
} else {
|
|
||||||
state.timeout = setTimeout(check, state.delay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.timeout = setTimeout(check, state.delay)
|
|
||||||
state.hurry = function() {
|
|
||||||
clearTimeout(state.timeout)
|
|
||||||
state.timeout = setTimeout(check, 50)
|
|
||||||
}
|
|
||||||
CodeMirror.on(window, "mouseup", state.hurry)
|
|
||||||
CodeMirror.on(window, "keyup", state.hurry)
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopListening(_cm, state) {
|
|
||||||
clearTimeout(state.timeout)
|
|
||||||
CodeMirror.off(window, "mouseup", state.hurry)
|
|
||||||
CodeMirror.off(window, "keyup", state.hurry)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
.CodeMirror-fullscreen {
|
|
||||||
position: fixed;
|
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
|
||||||
height: auto;
|
|
||||||
z-index: 9;
|
|
||||||
}
|
|
||||||
41
shared/codemirror/addon/display/fullscreen.js
vendored
41
shared/codemirror/addon/display/fullscreen.js
vendored
@@ -1,41 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineOption("fullScreen", false, function(cm, val, old) {
|
|
||||||
if (old == CodeMirror.Init) old = false;
|
|
||||||
if (!old == !val) return;
|
|
||||||
if (val) setFullscreen(cm);
|
|
||||||
else setNormal(cm);
|
|
||||||
});
|
|
||||||
|
|
||||||
function setFullscreen(cm) {
|
|
||||||
var wrap = cm.getWrapperElement();
|
|
||||||
cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset,
|
|
||||||
width: wrap.style.width, height: wrap.style.height};
|
|
||||||
wrap.style.width = "";
|
|
||||||
wrap.style.height = "auto";
|
|
||||||
wrap.className += " CodeMirror-fullscreen";
|
|
||||||
document.documentElement.style.overflow = "hidden";
|
|
||||||
cm.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNormal(cm) {
|
|
||||||
var wrap = cm.getWrapperElement();
|
|
||||||
wrap.className = wrap.className.replace(/\s*CodeMirror-fullscreen\b/, "");
|
|
||||||
document.documentElement.style.overflow = "";
|
|
||||||
var info = cm.state.fullScreenRestore;
|
|
||||||
wrap.style.width = info.width; wrap.style.height = info.height;
|
|
||||||
window.scrollTo(info.scrollLeft, info.scrollTop);
|
|
||||||
cm.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
112
shared/codemirror/addon/display/panel.js
vendored
112
shared/codemirror/addon/display/panel.js
vendored
@@ -1,112 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
CodeMirror.defineExtension("addPanel", function(node, options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
if (!this.state.panels) initPanels(this);
|
|
||||||
|
|
||||||
var info = this.state.panels;
|
|
||||||
var wrapper = info.wrapper;
|
|
||||||
var cmWrapper = this.getWrapperElement();
|
|
||||||
|
|
||||||
if (options.after instanceof Panel && !options.after.cleared) {
|
|
||||||
wrapper.insertBefore(node, options.before.node.nextSibling);
|
|
||||||
} else if (options.before instanceof Panel && !options.before.cleared) {
|
|
||||||
wrapper.insertBefore(node, options.before.node);
|
|
||||||
} else if (options.replace instanceof Panel && !options.replace.cleared) {
|
|
||||||
wrapper.insertBefore(node, options.replace.node);
|
|
||||||
options.replace.clear();
|
|
||||||
} else if (options.position == "bottom") {
|
|
||||||
wrapper.appendChild(node);
|
|
||||||
} else if (options.position == "before-bottom") {
|
|
||||||
wrapper.insertBefore(node, cmWrapper.nextSibling);
|
|
||||||
} else if (options.position == "after-top") {
|
|
||||||
wrapper.insertBefore(node, cmWrapper);
|
|
||||||
} else {
|
|
||||||
wrapper.insertBefore(node, wrapper.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
var height = (options && options.height) || node.offsetHeight;
|
|
||||||
this._setSize(null, info.heightLeft -= height);
|
|
||||||
info.panels++;
|
|
||||||
return new Panel(this, node, options, height);
|
|
||||||
});
|
|
||||||
|
|
||||||
function Panel(cm, node, options, height) {
|
|
||||||
this.cm = cm;
|
|
||||||
this.node = node;
|
|
||||||
this.options = options;
|
|
||||||
this.height = height;
|
|
||||||
this.cleared = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Panel.prototype.clear = function() {
|
|
||||||
if (this.cleared) return;
|
|
||||||
this.cleared = true;
|
|
||||||
var info = this.cm.state.panels;
|
|
||||||
this.cm._setSize(null, info.heightLeft += this.height);
|
|
||||||
info.wrapper.removeChild(this.node);
|
|
||||||
if (--info.panels == 0) removePanels(this.cm);
|
|
||||||
};
|
|
||||||
|
|
||||||
Panel.prototype.changed = function(height) {
|
|
||||||
var newHeight = height == null ? this.node.offsetHeight : height;
|
|
||||||
var info = this.cm.state.panels;
|
|
||||||
this.cm._setSize(null, info.height += (newHeight - this.height));
|
|
||||||
this.height = newHeight;
|
|
||||||
};
|
|
||||||
|
|
||||||
function initPanels(cm) {
|
|
||||||
var wrap = cm.getWrapperElement();
|
|
||||||
var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
|
|
||||||
var height = parseInt(style.height);
|
|
||||||
var info = cm.state.panels = {
|
|
||||||
setHeight: wrap.style.height,
|
|
||||||
heightLeft: height,
|
|
||||||
panels: 0,
|
|
||||||
wrapper: document.createElement("div")
|
|
||||||
};
|
|
||||||
wrap.parentNode.insertBefore(info.wrapper, wrap);
|
|
||||||
var hasFocus = cm.hasFocus();
|
|
||||||
info.wrapper.appendChild(wrap);
|
|
||||||
if (hasFocus) cm.focus();
|
|
||||||
|
|
||||||
cm._setSize = cm.setSize;
|
|
||||||
if (height != null) cm.setSize = function(width, newHeight) {
|
|
||||||
if (newHeight == null) return this._setSize(width, newHeight);
|
|
||||||
info.setHeight = newHeight;
|
|
||||||
if (typeof newHeight != "number") {
|
|
||||||
var px = /^(\d+\.?\d*)px$/.exec(newHeight);
|
|
||||||
if (px) {
|
|
||||||
newHeight = Number(px[1]);
|
|
||||||
} else {
|
|
||||||
info.wrapper.style.height = newHeight;
|
|
||||||
newHeight = info.wrapper.offsetHeight;
|
|
||||||
info.wrapper.style.height = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cm._setSize(width, info.heightLeft += (newHeight - height));
|
|
||||||
height = newHeight;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePanels(cm) {
|
|
||||||
var info = cm.state.panels;
|
|
||||||
cm.state.panels = null;
|
|
||||||
|
|
||||||
var wrap = cm.getWrapperElement();
|
|
||||||
info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
|
|
||||||
wrap.style.height = info.setHeight;
|
|
||||||
cm.setSize = cm._setSize;
|
|
||||||
cm.setSize();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
62
shared/codemirror/addon/display/placeholder.js
vendored
62
shared/codemirror/addon/display/placeholder.js
vendored
@@ -1,62 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
CodeMirror.defineOption("placeholder", "", function(cm, val, old) {
|
|
||||||
var prev = old && old != CodeMirror.Init;
|
|
||||||
if (val && !prev) {
|
|
||||||
cm.on("blur", onBlur);
|
|
||||||
cm.on("change", onChange);
|
|
||||||
cm.on("swapDoc", onChange);
|
|
||||||
onChange(cm);
|
|
||||||
} else if (!val && prev) {
|
|
||||||
cm.off("blur", onBlur);
|
|
||||||
cm.off("change", onChange);
|
|
||||||
cm.off("swapDoc", onChange);
|
|
||||||
clearPlaceholder(cm);
|
|
||||||
var wrapper = cm.getWrapperElement();
|
|
||||||
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val && !cm.hasFocus()) onBlur(cm);
|
|
||||||
});
|
|
||||||
|
|
||||||
function clearPlaceholder(cm) {
|
|
||||||
if (cm.state.placeholder) {
|
|
||||||
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder);
|
|
||||||
cm.state.placeholder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function setPlaceholder(cm) {
|
|
||||||
clearPlaceholder(cm);
|
|
||||||
var elt = cm.state.placeholder = document.createElement("pre");
|
|
||||||
elt.style.cssText = "height: 0; overflow: visible";
|
|
||||||
elt.className = "CodeMirror-placeholder";
|
|
||||||
var placeHolder = cm.getOption("placeholder")
|
|
||||||
if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
|
|
||||||
elt.appendChild(placeHolder)
|
|
||||||
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBlur(cm) {
|
|
||||||
if (isEmpty(cm)) setPlaceholder(cm);
|
|
||||||
}
|
|
||||||
function onChange(cm) {
|
|
||||||
var wrapper = cm.getWrapperElement(), empty = isEmpty(cm);
|
|
||||||
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : "");
|
|
||||||
|
|
||||||
if (empty) setPlaceholder(cm);
|
|
||||||
else clearPlaceholder(cm);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEmpty(cm) {
|
|
||||||
return (cm.lineCount() === 1) && (cm.getLine(0) === "");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
63
shared/codemirror/addon/display/rulers.js
vendored
63
shared/codemirror/addon/display/rulers.js
vendored
@@ -1,63 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineOption("rulers", false, function(cm, val, old) {
|
|
||||||
if (old && old != CodeMirror.Init) {
|
|
||||||
clearRulers(cm);
|
|
||||||
cm.off("refresh", refreshRulers);
|
|
||||||
}
|
|
||||||
if (val && val.length) {
|
|
||||||
setRulers(cm);
|
|
||||||
cm.on("refresh", refreshRulers);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function clearRulers(cm) {
|
|
||||||
for (var i = cm.display.lineSpace.childNodes.length - 1; i >= 0; i--) {
|
|
||||||
var node = cm.display.lineSpace.childNodes[i];
|
|
||||||
if (/(^|\s)CodeMirror-ruler($|\s)/.test(node.className))
|
|
||||||
node.parentNode.removeChild(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRulers(cm) {
|
|
||||||
var val = cm.getOption("rulers");
|
|
||||||
var cw = cm.defaultCharWidth();
|
|
||||||
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
|
|
||||||
var minH = cm.display.scroller.offsetHeight + 30;
|
|
||||||
for (var i = 0; i < val.length; i++) {
|
|
||||||
var elt = document.createElement("div");
|
|
||||||
elt.className = "CodeMirror-ruler";
|
|
||||||
var col, conf = val[i];
|
|
||||||
if (typeof conf == "number") {
|
|
||||||
col = conf;
|
|
||||||
} else {
|
|
||||||
col = conf.column;
|
|
||||||
if (conf.className) elt.className += " " + conf.className;
|
|
||||||
if (conf.color) elt.style.borderColor = conf.color;
|
|
||||||
if (conf.lineStyle) elt.style.borderLeftStyle = conf.lineStyle;
|
|
||||||
if (conf.width) elt.style.borderLeftWidth = conf.width;
|
|
||||||
}
|
|
||||||
elt.style.left = (left + col * cw) + "px";
|
|
||||||
elt.style.top = "-50px";
|
|
||||||
elt.style.bottom = "-20px";
|
|
||||||
elt.style.minHeight = minH + "px";
|
|
||||||
cm.display.lineSpace.insertBefore(elt, cm.display.cursorDiv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshRulers(cm) {
|
|
||||||
clearRulers(cm);
|
|
||||||
setRulers(cm);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
195
shared/codemirror/addon/edit/closebrackets.js
vendored
195
shared/codemirror/addon/edit/closebrackets.js
vendored
@@ -1,195 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
var defaults = {
|
|
||||||
pairs: "()[]{}''\"\"",
|
|
||||||
triples: "",
|
|
||||||
explode: "[]{}"
|
|
||||||
};
|
|
||||||
|
|
||||||
var Pos = CodeMirror.Pos;
|
|
||||||
|
|
||||||
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
|
|
||||||
if (old && old != CodeMirror.Init) {
|
|
||||||
cm.removeKeyMap(keyMap);
|
|
||||||
cm.state.closeBrackets = null;
|
|
||||||
}
|
|
||||||
if (val) {
|
|
||||||
cm.state.closeBrackets = val;
|
|
||||||
cm.addKeyMap(keyMap);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getOption(conf, name) {
|
|
||||||
if (name == "pairs" && typeof conf == "string") return conf;
|
|
||||||
if (typeof conf == "object" && conf[name] != null) return conf[name];
|
|
||||||
return defaults[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
var bind = defaults.pairs + "`";
|
|
||||||
var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
|
|
||||||
for (var i = 0; i < bind.length; i++)
|
|
||||||
keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i));
|
|
||||||
|
|
||||||
function handler(ch) {
|
|
||||||
return function(cm) { return handleChar(cm, ch); };
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfig(cm) {
|
|
||||||
var deflt = cm.state.closeBrackets;
|
|
||||||
if (!deflt) return null;
|
|
||||||
var mode = cm.getModeAt(cm.getCursor());
|
|
||||||
return mode.closeBrackets || deflt;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBackspace(cm) {
|
|
||||||
var conf = getConfig(cm);
|
|
||||||
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
|
|
||||||
var pairs = getOption(conf, "pairs");
|
|
||||||
var ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
|
||||||
var around = charsAround(cm, ranges[i].head);
|
|
||||||
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
|
||||||
var cur = ranges[i].head;
|
|
||||||
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleEnter(cm) {
|
|
||||||
var conf = getConfig(cm);
|
|
||||||
var explode = conf && getOption(conf, "explode");
|
|
||||||
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
|
|
||||||
var ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
|
||||||
var around = charsAround(cm, ranges[i].head);
|
|
||||||
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
cm.operation(function() {
|
|
||||||
cm.replaceSelection("\n\n", null);
|
|
||||||
cm.execCommand("goCharLeft");
|
|
||||||
ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
var line = ranges[i].head.line;
|
|
||||||
cm.indentLine(line, null, true);
|
|
||||||
cm.indentLine(line + 1, null, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function contractSelection(sel) {
|
|
||||||
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
|
|
||||||
return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
|
|
||||||
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChar(cm, ch) {
|
|
||||||
var conf = getConfig(cm);
|
|
||||||
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
|
|
||||||
var pairs = getOption(conf, "pairs");
|
|
||||||
var pos = pairs.indexOf(ch);
|
|
||||||
if (pos == -1) return CodeMirror.Pass;
|
|
||||||
var triples = getOption(conf, "triples");
|
|
||||||
|
|
||||||
var identical = pairs.charAt(pos + 1) == ch;
|
|
||||||
var ranges = cm.listSelections();
|
|
||||||
var opening = pos % 2 == 0;
|
|
||||||
|
|
||||||
var type, next;
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
var range = ranges[i], cur = range.head, curType;
|
|
||||||
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
|
|
||||||
if (opening && !range.empty()) {
|
|
||||||
curType = "surround";
|
|
||||||
} else if ((identical || !opening) && next == ch) {
|
|
||||||
if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
|
|
||||||
curType = "skipThree";
|
|
||||||
else
|
|
||||||
curType = "skip";
|
|
||||||
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
|
|
||||||
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch &&
|
|
||||||
(cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) {
|
|
||||||
curType = "addFour";
|
|
||||||
} else if (identical) {
|
|
||||||
if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both";
|
|
||||||
else return CodeMirror.Pass;
|
|
||||||
} else if (opening && (cm.getLine(cur.line).length == cur.ch ||
|
|
||||||
isClosingBracket(next, pairs) ||
|
|
||||||
/\s/.test(next))) {
|
|
||||||
curType = "both";
|
|
||||||
} else {
|
|
||||||
return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
if (!type) type = curType;
|
|
||||||
else if (type != curType) return CodeMirror.Pass;
|
|
||||||
}
|
|
||||||
|
|
||||||
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
|
|
||||||
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
|
|
||||||
cm.operation(function() {
|
|
||||||
if (type == "skip") {
|
|
||||||
cm.execCommand("goCharRight");
|
|
||||||
} else if (type == "skipThree") {
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
cm.execCommand("goCharRight");
|
|
||||||
} else if (type == "surround") {
|
|
||||||
var sels = cm.getSelections();
|
|
||||||
for (var i = 0; i < sels.length; i++)
|
|
||||||
sels[i] = left + sels[i] + right;
|
|
||||||
cm.replaceSelections(sels, "around");
|
|
||||||
sels = cm.listSelections().slice();
|
|
||||||
for (var i = 0; i < sels.length; i++)
|
|
||||||
sels[i] = contractSelection(sels[i]);
|
|
||||||
cm.setSelections(sels);
|
|
||||||
} else if (type == "both") {
|
|
||||||
cm.replaceSelection(left + right, null);
|
|
||||||
cm.triggerElectric(left + right);
|
|
||||||
cm.execCommand("goCharLeft");
|
|
||||||
} else if (type == "addFour") {
|
|
||||||
cm.replaceSelection(left + left + left + left, "before");
|
|
||||||
cm.execCommand("goCharRight");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isClosingBracket(ch, pairs) {
|
|
||||||
var pos = pairs.lastIndexOf(ch);
|
|
||||||
return pos > -1 && pos % 2 == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function charsAround(cm, pos) {
|
|
||||||
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
|
|
||||||
Pos(pos.line, pos.ch + 1));
|
|
||||||
return str.length == 2 ? str : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Project the token type that will exists after the given char is
|
|
||||||
// typed, and use it to determine whether it would cause the start
|
|
||||||
// of a string token.
|
|
||||||
function enteringString(cm, pos, ch) {
|
|
||||||
var line = cm.getLine(pos.line);
|
|
||||||
var token = cm.getTokenAt(pos);
|
|
||||||
if (/\bstring2?\b/.test(token.type)) return false;
|
|
||||||
var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
|
|
||||||
stream.pos = stream.start = token.start;
|
|
||||||
for (;;) {
|
|
||||||
var type1 = cm.getMode().token(stream, token.state);
|
|
||||||
if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
|
|
||||||
stream.start = stream.pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
169
shared/codemirror/addon/edit/closetag.js
vendored
169
shared/codemirror/addon/edit/closetag.js
vendored
@@ -1,169 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tag-closer extension for CodeMirror.
|
|
||||||
*
|
|
||||||
* This extension adds an "autoCloseTags" option that can be set to
|
|
||||||
* either true to get the default behavior, or an object to further
|
|
||||||
* configure its behavior.
|
|
||||||
*
|
|
||||||
* These are supported options:
|
|
||||||
*
|
|
||||||
* `whenClosing` (default true)
|
|
||||||
* Whether to autoclose when the '/' of a closing tag is typed.
|
|
||||||
* `whenOpening` (default true)
|
|
||||||
* Whether to autoclose the tag when the final '>' of an opening
|
|
||||||
* tag is typed.
|
|
||||||
* `dontCloseTags` (default is empty tags for HTML, none for XML)
|
|
||||||
* An array of tag names that should not be autoclosed.
|
|
||||||
* `indentTags` (default is block tags for HTML, none for XML)
|
|
||||||
* An array of tag names that should, when opened, cause a
|
|
||||||
* blank line to be added inside the tag, and the blank line and
|
|
||||||
* closing line to be indented.
|
|
||||||
*
|
|
||||||
* See demos/closetag.html for a usage example.
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
|
|
||||||
if (old != CodeMirror.Init && old)
|
|
||||||
cm.removeKeyMap("autoCloseTags");
|
|
||||||
if (!val) return;
|
|
||||||
var map = {name: "autoCloseTags"};
|
|
||||||
if (typeof val != "object" || val.whenClosing)
|
|
||||||
map["'/'"] = function(cm) { return autoCloseSlash(cm); };
|
|
||||||
if (typeof val != "object" || val.whenOpening)
|
|
||||||
map["'>'"] = function(cm) { return autoCloseGT(cm); };
|
|
||||||
cm.addKeyMap(map);
|
|
||||||
});
|
|
||||||
|
|
||||||
var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
|
|
||||||
"source", "track", "wbr"];
|
|
||||||
var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
|
|
||||||
"h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
|
|
||||||
|
|
||||||
function autoCloseGT(cm) {
|
|
||||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
var ranges = cm.listSelections(), replacements = [];
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
|
||||||
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
|
|
||||||
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
|
|
||||||
if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
|
|
||||||
|
|
||||||
var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
|
|
||||||
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
|
|
||||||
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
|
|
||||||
|
|
||||||
var tagName = state.tagName;
|
|
||||||
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
|
|
||||||
var lowerTagName = tagName.toLowerCase();
|
|
||||||
// Don't process the '>' at the end of an end-tag or self-closing tag
|
|
||||||
if (!tagName ||
|
|
||||||
tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
|
|
||||||
tok.type == "tag" && state.type == "closeTag" ||
|
|
||||||
tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
|
|
||||||
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
|
|
||||||
closingTagExists(cm, tagName, pos, state, true))
|
|
||||||
return CodeMirror.Pass;
|
|
||||||
|
|
||||||
var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
|
|
||||||
replacements[i] = {indent: indent,
|
|
||||||
text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
|
|
||||||
newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
|
||||||
var info = replacements[i];
|
|
||||||
cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
|
|
||||||
var sel = cm.listSelections().slice(0);
|
|
||||||
sel[i] = {head: info.newPos, anchor: info.newPos};
|
|
||||||
cm.setSelections(sel);
|
|
||||||
if (info.indent) {
|
|
||||||
cm.indentLine(info.newPos.line, null, true);
|
|
||||||
cm.indentLine(info.newPos.line + 1, null, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoCloseCurrent(cm, typingSlash) {
|
|
||||||
var ranges = cm.listSelections(), replacements = [];
|
|
||||||
var head = typingSlash ? "/" : "</";
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
if (!ranges[i].empty()) return CodeMirror.Pass;
|
|
||||||
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
|
|
||||||
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
|
|
||||||
if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
|
|
||||||
tok.start != pos.ch - 1))
|
|
||||||
return CodeMirror.Pass;
|
|
||||||
// Kludge to get around the fact that we are not in XML mode
|
|
||||||
// when completing in JS/CSS snippet in htmlmixed mode. Does not
|
|
||||||
// work for other XML embedded languages (there is no general
|
|
||||||
// way to go from a mixed mode to its current XML state).
|
|
||||||
var replacement;
|
|
||||||
if (inner.mode.name != "xml") {
|
|
||||||
if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript")
|
|
||||||
replacement = head + "script";
|
|
||||||
else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
|
|
||||||
replacement = head + "style";
|
|
||||||
else
|
|
||||||
return CodeMirror.Pass;
|
|
||||||
} else {
|
|
||||||
if (!state.context || !state.context.tagName ||
|
|
||||||
closingTagExists(cm, state.context.tagName, pos, state))
|
|
||||||
return CodeMirror.Pass;
|
|
||||||
replacement = head + state.context.tagName;
|
|
||||||
}
|
|
||||||
if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
|
|
||||||
replacements[i] = replacement;
|
|
||||||
}
|
|
||||||
cm.replaceSelections(replacements);
|
|
||||||
ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++)
|
|
||||||
if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
|
|
||||||
cm.indentLine(ranges[i].head.line);
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoCloseSlash(cm) {
|
|
||||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
return autoCloseCurrent(cm, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
|
|
||||||
|
|
||||||
function indexOf(collection, elt) {
|
|
||||||
if (collection.indexOf) return collection.indexOf(elt);
|
|
||||||
for (var i = 0, e = collection.length; i < e; ++i)
|
|
||||||
if (collection[i] == elt) return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If xml-fold is loaded, we use its functionality to try and verify
|
|
||||||
// whether a given tag is actually unclosed.
|
|
||||||
function closingTagExists(cm, tagName, pos, state, newTag) {
|
|
||||||
if (!CodeMirror.scanForClosingTag) return false;
|
|
||||||
var end = Math.min(cm.lastLine() + 1, pos.line + 500);
|
|
||||||
var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
|
|
||||||
if (!nextClose || nextClose.tag != tagName) return false;
|
|
||||||
var cx = state.context;
|
|
||||||
// If the immediate wrapping context contains onCx instances of
|
|
||||||
// the same tag, a closing tag only exists if there are at least
|
|
||||||
// that many closing tags of that type following.
|
|
||||||
for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx;
|
|
||||||
pos = nextClose.to;
|
|
||||||
for (var i = 1; i < onCx; i++) {
|
|
||||||
var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
|
|
||||||
if (!next || next.tag != tagName) return false;
|
|
||||||
pos = next.to;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
51
shared/codemirror/addon/edit/continuelist.js
vendored
51
shared/codemirror/addon/edit/continuelist.js
vendored
@@ -1,51 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))(\s*)/,
|
|
||||||
emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)[.)])(\s*)$/,
|
|
||||||
unorderedListRE = /[*+-]\s/;
|
|
||||||
|
|
||||||
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
|
|
||||||
if (cm.getOption("disableInput")) return CodeMirror.Pass;
|
|
||||||
var ranges = cm.listSelections(), replacements = [];
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
var pos = ranges[i].head;
|
|
||||||
var eolState = cm.getStateAfter(pos.line);
|
|
||||||
var inList = eolState.list !== false;
|
|
||||||
var inQuote = eolState.quote !== 0;
|
|
||||||
|
|
||||||
var line = cm.getLine(pos.line), match = listRE.exec(line);
|
|
||||||
if (!ranges[i].empty() || (!inList && !inQuote) || !match) {
|
|
||||||
cm.execCommand("newlineAndIndent");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (emptyListRE.test(line)) {
|
|
||||||
cm.replaceRange("", {
|
|
||||||
line: pos.line, ch: 0
|
|
||||||
}, {
|
|
||||||
line: pos.line, ch: pos.ch + 1
|
|
||||||
});
|
|
||||||
replacements[i] = "\n";
|
|
||||||
} else {
|
|
||||||
var indent = match[1], after = match[5];
|
|
||||||
var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
|
|
||||||
? match[2]
|
|
||||||
: (parseInt(match[3], 10) + 1) + match[4];
|
|
||||||
|
|
||||||
replacements[i] = "\n" + indent + bullet + after;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.replaceSelections(replacements);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
120
shared/codemirror/addon/edit/matchbrackets.js
vendored
120
shared/codemirror/addon/edit/matchbrackets.js
vendored
@@ -1,120 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
|
|
||||||
(document.documentMode == null || document.documentMode < 8);
|
|
||||||
|
|
||||||
var Pos = CodeMirror.Pos;
|
|
||||||
|
|
||||||
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
|
|
||||||
|
|
||||||
function findMatchingBracket(cm, where, strict, config) {
|
|
||||||
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
|
|
||||||
var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
|
|
||||||
if (!match) return null;
|
|
||||||
var dir = match.charAt(1) == ">" ? 1 : -1;
|
|
||||||
if (strict && (dir > 0) != (pos == where.ch)) return null;
|
|
||||||
var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
|
|
||||||
|
|
||||||
var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
|
|
||||||
if (found == null) return null;
|
|
||||||
return {from: Pos(where.line, pos), to: found && found.pos,
|
|
||||||
match: found && found.ch == match.charAt(0), forward: dir > 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
// bracketRegex is used to specify which type of bracket to scan
|
|
||||||
// should be a regexp, e.g. /[[\]]/
|
|
||||||
//
|
|
||||||
// Note: If "where" is on an open bracket, then this bracket is ignored.
|
|
||||||
//
|
|
||||||
// Returns false when no bracket was found, null when it reached
|
|
||||||
// maxScanLines and gave up
|
|
||||||
function scanForBracket(cm, where, dir, style, config) {
|
|
||||||
var maxScanLen = (config && config.maxScanLineLength) || 10000;
|
|
||||||
var maxScanLines = (config && config.maxScanLines) || 1000;
|
|
||||||
|
|
||||||
var stack = [];
|
|
||||||
var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
|
|
||||||
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
|
|
||||||
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
|
|
||||||
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
|
|
||||||
var line = cm.getLine(lineNo);
|
|
||||||
if (!line) continue;
|
|
||||||
var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
|
|
||||||
if (line.length > maxScanLen) continue;
|
|
||||||
if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
|
|
||||||
for (; pos != end; pos += dir) {
|
|
||||||
var ch = line.charAt(pos);
|
|
||||||
if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
|
|
||||||
var match = matching[ch];
|
|
||||||
if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
|
|
||||||
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
|
|
||||||
else stack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchBrackets(cm, autoclear, config) {
|
|
||||||
// Disable brace matching in long lines, since it'll cause hugely slow updates
|
|
||||||
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
|
|
||||||
var marks = [], ranges = cm.listSelections();
|
|
||||||
for (var i = 0; i < ranges.length; i++) {
|
|
||||||
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
|
|
||||||
if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
|
|
||||||
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
|
|
||||||
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
|
|
||||||
if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
|
|
||||||
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (marks.length) {
|
|
||||||
// Kludge to work around the IE bug from issue #1193, where text
|
|
||||||
// input stops going to the textare whever this fires.
|
|
||||||
if (ie_lt8 && cm.state.focused) cm.focus();
|
|
||||||
|
|
||||||
var clear = function() {
|
|
||||||
cm.operation(function() {
|
|
||||||
for (var i = 0; i < marks.length; i++) marks[i].clear();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (autoclear) setTimeout(clear, 800);
|
|
||||||
else return clear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentlyHighlighted = null;
|
|
||||||
function doMatchBrackets(cm) {
|
|
||||||
cm.operation(function() {
|
|
||||||
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
|
|
||||||
currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
|
|
||||||
if (old && old != CodeMirror.Init)
|
|
||||||
cm.off("cursorActivity", doMatchBrackets);
|
|
||||||
if (val) {
|
|
||||||
cm.state.matchBrackets = typeof val == "object" ? val : {};
|
|
||||||
cm.on("cursorActivity", doMatchBrackets);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
|
|
||||||
CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){
|
|
||||||
return findMatchingBracket(this, pos, strict, config);
|
|
||||||
});
|
|
||||||
CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
|
|
||||||
return scanForBracket(this, pos, dir, style, config);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
66
shared/codemirror/addon/edit/matchtags.js
vendored
66
shared/codemirror/addon/edit/matchtags.js
vendored
@@ -1,66 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineOption("matchTags", false, function(cm, val, old) {
|
|
||||||
if (old && old != CodeMirror.Init) {
|
|
||||||
cm.off("cursorActivity", doMatchTags);
|
|
||||||
cm.off("viewportChange", maybeUpdateMatch);
|
|
||||||
clear(cm);
|
|
||||||
}
|
|
||||||
if (val) {
|
|
||||||
cm.state.matchBothTags = typeof val == "object" && val.bothTags;
|
|
||||||
cm.on("cursorActivity", doMatchTags);
|
|
||||||
cm.on("viewportChange", maybeUpdateMatch);
|
|
||||||
doMatchTags(cm);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function clear(cm) {
|
|
||||||
if (cm.state.tagHit) cm.state.tagHit.clear();
|
|
||||||
if (cm.state.tagOther) cm.state.tagOther.clear();
|
|
||||||
cm.state.tagHit = cm.state.tagOther = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function doMatchTags(cm) {
|
|
||||||
cm.state.failedTagMatch = false;
|
|
||||||
cm.operation(function() {
|
|
||||||
clear(cm);
|
|
||||||
if (cm.somethingSelected()) return;
|
|
||||||
var cur = cm.getCursor(), range = cm.getViewport();
|
|
||||||
range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to);
|
|
||||||
var match = CodeMirror.findMatchingTag(cm, cur, range);
|
|
||||||
if (!match) return;
|
|
||||||
if (cm.state.matchBothTags) {
|
|
||||||
var hit = match.at == "open" ? match.open : match.close;
|
|
||||||
if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: "CodeMirror-matchingtag"});
|
|
||||||
}
|
|
||||||
var other = match.at == "close" ? match.open : match.close;
|
|
||||||
if (other)
|
|
||||||
cm.state.tagOther = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"});
|
|
||||||
else
|
|
||||||
cm.state.failedTagMatch = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeUpdateMatch(cm) {
|
|
||||||
if (cm.state.failedTagMatch) doMatchTags(cm);
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.commands.toMatchingTag = function(cm) {
|
|
||||||
var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
|
|
||||||
if (found) {
|
|
||||||
var other = found.at == "close" ? found.open : found.close;
|
|
||||||
if (other) cm.extendSelection(other.to, other.from);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
27
shared/codemirror/addon/edit/trailingspace.js
vendored
27
shared/codemirror/addon/edit/trailingspace.js
vendored
@@ -1,27 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) {
|
|
||||||
if (prev == CodeMirror.Init) prev = false;
|
|
||||||
if (prev && !val)
|
|
||||||
cm.removeOverlay("trailingspace");
|
|
||||||
else if (!prev && val)
|
|
||||||
cm.addOverlay({
|
|
||||||
token: function(stream) {
|
|
||||||
for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {}
|
|
||||||
if (i > stream.pos) { stream.pos = i; return null; }
|
|
||||||
stream.pos = l;
|
|
||||||
return "trailingspace";
|
|
||||||
},
|
|
||||||
name: "trailingspace"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
105
shared/codemirror/addon/fold/brace-fold.js
vendored
105
shared/codemirror/addon/fold/brace-fold.js
vendored
@@ -1,105 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "brace", function(cm, start) {
|
|
||||||
var line = start.line, lineText = cm.getLine(line);
|
|
||||||
var startCh, tokenType;
|
|
||||||
|
|
||||||
function findOpening(openCh) {
|
|
||||||
for (var at = start.ch, pass = 0;;) {
|
|
||||||
var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);
|
|
||||||
if (found == -1) {
|
|
||||||
if (pass == 1) break;
|
|
||||||
pass = 1;
|
|
||||||
at = lineText.length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (pass == 1 && found < start.ch) break;
|
|
||||||
tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
|
|
||||||
if (!/^(comment|string)/.test(tokenType)) return found + 1;
|
|
||||||
at = found - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var startToken = "{", endToken = "}", startCh = findOpening("{");
|
|
||||||
if (startCh == null) {
|
|
||||||
startToken = "[", endToken = "]";
|
|
||||||
startCh = findOpening("[");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startCh == null) return;
|
|
||||||
var count = 1, lastLine = cm.lastLine(), end, endCh;
|
|
||||||
outer: for (var i = line; i <= lastLine; ++i) {
|
|
||||||
var text = cm.getLine(i), pos = i == line ? startCh : 0;
|
|
||||||
for (;;) {
|
|
||||||
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
|
|
||||||
if (nextOpen < 0) nextOpen = text.length;
|
|
||||||
if (nextClose < 0) nextClose = text.length;
|
|
||||||
pos = Math.min(nextOpen, nextClose);
|
|
||||||
if (pos == text.length) break;
|
|
||||||
if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {
|
|
||||||
if (pos == nextOpen) ++count;
|
|
||||||
else if (!--count) { end = i; endCh = pos; break outer; }
|
|
||||||
}
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (end == null || line == end && endCh == startCh) return;
|
|
||||||
return {from: CodeMirror.Pos(line, startCh),
|
|
||||||
to: CodeMirror.Pos(end, endCh)};
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "import", function(cm, start) {
|
|
||||||
function hasImport(line) {
|
|
||||||
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
|
||||||
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
|
||||||
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
|
||||||
if (start.type != "keyword" || start.string != "import") return null;
|
|
||||||
// Now find closing semicolon, return its position
|
|
||||||
for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
|
|
||||||
var text = cm.getLine(i), semi = text.indexOf(";");
|
|
||||||
if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var start = start.line, has = hasImport(start), prev;
|
|
||||||
if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1))
|
|
||||||
return null;
|
|
||||||
for (var end = has.end;;) {
|
|
||||||
var next = hasImport(end.line + 1);
|
|
||||||
if (next == null) break;
|
|
||||||
end = next.end;
|
|
||||||
}
|
|
||||||
return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end};
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "include", function(cm, start) {
|
|
||||||
function hasInclude(line) {
|
|
||||||
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
|
||||||
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
|
||||||
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
|
||||||
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
var start = start.line, has = hasInclude(start);
|
|
||||||
if (has == null || hasInclude(start - 1) != null) return null;
|
|
||||||
for (var end = start;;) {
|
|
||||||
var next = hasInclude(end + 1);
|
|
||||||
if (next == null) break;
|
|
||||||
++end;
|
|
||||||
}
|
|
||||||
return {from: CodeMirror.Pos(start, has + 1),
|
|
||||||
to: cm.clipPos(CodeMirror.Pos(end))};
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
59
shared/codemirror/addon/fold/comment-fold.js
vendored
59
shared/codemirror/addon/fold/comment-fold.js
vendored
@@ -1,59 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
|
|
||||||
return mode.blockCommentStart && mode.blockCommentEnd;
|
|
||||||
}, function(cm, start) {
|
|
||||||
var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
|
|
||||||
if (!startToken || !endToken) return;
|
|
||||||
var line = start.line, lineText = cm.getLine(line);
|
|
||||||
|
|
||||||
var startCh;
|
|
||||||
for (var at = start.ch, pass = 0;;) {
|
|
||||||
var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);
|
|
||||||
if (found == -1) {
|
|
||||||
if (pass == 1) return;
|
|
||||||
pass = 1;
|
|
||||||
at = lineText.length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (pass == 1 && found < start.ch) return;
|
|
||||||
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
|
|
||||||
(lineText.slice(found - endToken.length, found) == endToken ||
|
|
||||||
!/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
|
|
||||||
startCh = found + startToken.length;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
at = found - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var depth = 1, lastLine = cm.lastLine(), end, endCh;
|
|
||||||
outer: for (var i = line; i <= lastLine; ++i) {
|
|
||||||
var text = cm.getLine(i), pos = i == line ? startCh : 0;
|
|
||||||
for (;;) {
|
|
||||||
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
|
|
||||||
if (nextOpen < 0) nextOpen = text.length;
|
|
||||||
if (nextClose < 0) nextClose = text.length;
|
|
||||||
pos = Math.min(nextOpen, nextClose);
|
|
||||||
if (pos == text.length) break;
|
|
||||||
if (pos == nextOpen) ++depth;
|
|
||||||
else if (!--depth) { end = i; endCh = pos; break outer; }
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (end == null || line == end && endCh == startCh) return;
|
|
||||||
return {from: CodeMirror.Pos(line, startCh),
|
|
||||||
to: CodeMirror.Pos(end, endCh)};
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
149
shared/codemirror/addon/fold/foldcode.js
vendored
149
shared/codemirror/addon/fold/foldcode.js
vendored
@@ -1,149 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
function doFold(cm, pos, options, force) {
|
|
||||||
if (options && options.call) {
|
|
||||||
var finder = options;
|
|
||||||
options = null;
|
|
||||||
} else {
|
|
||||||
var finder = getOption(cm, options, "rangeFinder");
|
|
||||||
}
|
|
||||||
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
|
|
||||||
var minSize = getOption(cm, options, "minFoldSize");
|
|
||||||
|
|
||||||
function getRange(allowFolded) {
|
|
||||||
var range = finder(cm, pos);
|
|
||||||
if (!range || range.to.line - range.from.line < minSize) return null;
|
|
||||||
var marks = cm.findMarksAt(range.from);
|
|
||||||
for (var i = 0; i < marks.length; ++i) {
|
|
||||||
if (marks[i].__isFold && force !== "fold") {
|
|
||||||
if (!allowFolded) return null;
|
|
||||||
range.cleared = true;
|
|
||||||
marks[i].clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = getRange(true);
|
|
||||||
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
|
|
||||||
pos = CodeMirror.Pos(pos.line - 1, 0);
|
|
||||||
range = getRange(false);
|
|
||||||
}
|
|
||||||
if (!range || range.cleared || force === "unfold") return;
|
|
||||||
|
|
||||||
var myWidget = makeWidget(cm, options);
|
|
||||||
CodeMirror.on(myWidget, "mousedown", function(e) {
|
|
||||||
myRange.clear();
|
|
||||||
CodeMirror.e_preventDefault(e);
|
|
||||||
});
|
|
||||||
var myRange = cm.markText(range.from, range.to, {
|
|
||||||
replacedWith: myWidget,
|
|
||||||
clearOnEnter: true,
|
|
||||||
__isFold: true
|
|
||||||
});
|
|
||||||
myRange.on("clear", function(from, to) {
|
|
||||||
CodeMirror.signal(cm, "unfold", cm, from, to);
|
|
||||||
});
|
|
||||||
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeWidget(cm, options) {
|
|
||||||
var widget = getOption(cm, options, "widget");
|
|
||||||
if (typeof widget == "string") {
|
|
||||||
var text = document.createTextNode(widget);
|
|
||||||
widget = document.createElement("span");
|
|
||||||
widget.appendChild(text);
|
|
||||||
widget.className = "CodeMirror-foldmarker";
|
|
||||||
}
|
|
||||||
return widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clumsy backwards-compatible interface
|
|
||||||
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
|
|
||||||
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
|
|
||||||
};
|
|
||||||
|
|
||||||
// New-style interface
|
|
||||||
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
|
|
||||||
doFold(this, pos, options, force);
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("isFolded", function(pos) {
|
|
||||||
var marks = this.findMarksAt(pos);
|
|
||||||
for (var i = 0; i < marks.length; ++i)
|
|
||||||
if (marks[i].__isFold) return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.commands.toggleFold = function(cm) {
|
|
||||||
cm.foldCode(cm.getCursor());
|
|
||||||
};
|
|
||||||
CodeMirror.commands.fold = function(cm) {
|
|
||||||
cm.foldCode(cm.getCursor(), null, "fold");
|
|
||||||
};
|
|
||||||
CodeMirror.commands.unfold = function(cm) {
|
|
||||||
cm.foldCode(cm.getCursor(), null, "unfold");
|
|
||||||
};
|
|
||||||
CodeMirror.commands.foldAll = function(cm) {
|
|
||||||
cm.operation(function() {
|
|
||||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
|
||||||
cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
CodeMirror.commands.unfoldAll = function(cm) {
|
|
||||||
cm.operation(function() {
|
|
||||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
|
||||||
cm.foldCode(CodeMirror.Pos(i, 0), null, "unfold");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "combine", function() {
|
|
||||||
var funcs = Array.prototype.slice.call(arguments, 0);
|
|
||||||
return function(cm, start) {
|
|
||||||
for (var i = 0; i < funcs.length; ++i) {
|
|
||||||
var found = funcs[i](cm, start);
|
|
||||||
if (found) return found;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
|
|
||||||
var helpers = cm.getHelpers(start, "fold");
|
|
||||||
for (var i = 0; i < helpers.length; i++) {
|
|
||||||
var cur = helpers[i](cm, start);
|
|
||||||
if (cur) return cur;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var defaultOptions = {
|
|
||||||
rangeFinder: CodeMirror.fold.auto,
|
|
||||||
widget: "\u2194",
|
|
||||||
minFoldSize: 0,
|
|
||||||
scanUp: false
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.defineOption("foldOptions", null);
|
|
||||||
|
|
||||||
function getOption(cm, options, name) {
|
|
||||||
if (options && options[name] !== undefined)
|
|
||||||
return options[name];
|
|
||||||
var editorOptions = cm.options.foldOptions;
|
|
||||||
if (editorOptions && editorOptions[name] !== undefined)
|
|
||||||
return editorOptions[name];
|
|
||||||
return defaultOptions[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("foldOption", function(options, name) {
|
|
||||||
return getOption(this, options, name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
20
shared/codemirror/addon/fold/foldgutter.css
vendored
20
shared/codemirror/addon/fold/foldgutter.css
vendored
@@ -1,20 +0,0 @@
|
|||||||
.CodeMirror-foldmarker {
|
|
||||||
color: blue;
|
|
||||||
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
|
|
||||||
font-family: arial;
|
|
||||||
line-height: .3;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.CodeMirror-foldgutter {
|
|
||||||
width: .7em;
|
|
||||||
}
|
|
||||||
.CodeMirror-foldgutter-open,
|
|
||||||
.CodeMirror-foldgutter-folded {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.CodeMirror-foldgutter-open:after {
|
|
||||||
content: "\25BE";
|
|
||||||
}
|
|
||||||
.CodeMirror-foldgutter-folded:after {
|
|
||||||
content: "\25B8";
|
|
||||||
}
|
|
||||||
146
shared/codemirror/addon/fold/foldgutter.js
vendored
146
shared/codemirror/addon/fold/foldgutter.js
vendored
@@ -1,146 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"), require("./foldcode"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror", "./foldcode"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
|
|
||||||
if (old && old != CodeMirror.Init) {
|
|
||||||
cm.clearGutter(cm.state.foldGutter.options.gutter);
|
|
||||||
cm.state.foldGutter = null;
|
|
||||||
cm.off("gutterClick", onGutterClick);
|
|
||||||
cm.off("change", onChange);
|
|
||||||
cm.off("viewportChange", onViewportChange);
|
|
||||||
cm.off("fold", onFold);
|
|
||||||
cm.off("unfold", onFold);
|
|
||||||
cm.off("swapDoc", onChange);
|
|
||||||
}
|
|
||||||
if (val) {
|
|
||||||
cm.state.foldGutter = new State(parseOptions(val));
|
|
||||||
updateInViewport(cm);
|
|
||||||
cm.on("gutterClick", onGutterClick);
|
|
||||||
cm.on("change", onChange);
|
|
||||||
cm.on("viewportChange", onViewportChange);
|
|
||||||
cm.on("fold", onFold);
|
|
||||||
cm.on("unfold", onFold);
|
|
||||||
cm.on("swapDoc", onChange);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var Pos = CodeMirror.Pos;
|
|
||||||
|
|
||||||
function State(options) {
|
|
||||||
this.options = options;
|
|
||||||
this.from = this.to = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseOptions(opts) {
|
|
||||||
if (opts === true) opts = {};
|
|
||||||
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
|
|
||||||
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
|
|
||||||
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFolded(cm, line) {
|
|
||||||
var marks = cm.findMarksAt(Pos(line));
|
|
||||||
for (var i = 0; i < marks.length; ++i)
|
|
||||||
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
function marker(spec) {
|
|
||||||
if (typeof spec == "string") {
|
|
||||||
var elt = document.createElement("div");
|
|
||||||
elt.className = spec + " CodeMirror-guttermarker-subtle";
|
|
||||||
return elt;
|
|
||||||
} else {
|
|
||||||
return spec.cloneNode(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFoldInfo(cm, from, to) {
|
|
||||||
var opts = cm.state.foldGutter.options, cur = from;
|
|
||||||
var minSize = cm.foldOption(opts, "minFoldSize");
|
|
||||||
var func = cm.foldOption(opts, "rangeFinder");
|
|
||||||
cm.eachLine(from, to, function(line) {
|
|
||||||
var mark = null;
|
|
||||||
if (isFolded(cm, cur)) {
|
|
||||||
mark = marker(opts.indicatorFolded);
|
|
||||||
} else {
|
|
||||||
var pos = Pos(cur, 0);
|
|
||||||
var range = func && func(cm, pos);
|
|
||||||
if (range && range.to.line - range.from.line >= minSize)
|
|
||||||
mark = marker(opts.indicatorOpen);
|
|
||||||
}
|
|
||||||
cm.setGutterMarker(line, opts.gutter, mark);
|
|
||||||
++cur;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateInViewport(cm) {
|
|
||||||
var vp = cm.getViewport(), state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
cm.operation(function() {
|
|
||||||
updateFoldInfo(cm, vp.from, vp.to);
|
|
||||||
});
|
|
||||||
state.from = vp.from; state.to = vp.to;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onGutterClick(cm, line, gutter) {
|
|
||||||
var state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
var opts = state.options;
|
|
||||||
if (gutter != opts.gutter) return;
|
|
||||||
var folded = isFolded(cm, line);
|
|
||||||
if (folded) folded.clear();
|
|
||||||
else cm.foldCode(Pos(line, 0), opts.rangeFinder);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChange(cm) {
|
|
||||||
var state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
var opts = state.options;
|
|
||||||
state.from = state.to = 0;
|
|
||||||
clearTimeout(state.changeUpdate);
|
|
||||||
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onViewportChange(cm) {
|
|
||||||
var state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
var opts = state.options;
|
|
||||||
clearTimeout(state.changeUpdate);
|
|
||||||
state.changeUpdate = setTimeout(function() {
|
|
||||||
var vp = cm.getViewport();
|
|
||||||
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
|
|
||||||
updateInViewport(cm);
|
|
||||||
} else {
|
|
||||||
cm.operation(function() {
|
|
||||||
if (vp.from < state.from) {
|
|
||||||
updateFoldInfo(cm, vp.from, state.from);
|
|
||||||
state.from = vp.from;
|
|
||||||
}
|
|
||||||
if (vp.to > state.to) {
|
|
||||||
updateFoldInfo(cm, state.to, vp.to);
|
|
||||||
state.to = vp.to;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, opts.updateViewportTimeSpan || 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFold(cm, from) {
|
|
||||||
var state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
var line = from.line;
|
|
||||||
if (line >= state.from && line < state.to)
|
|
||||||
updateFoldInfo(cm, line, line + 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
44
shared/codemirror/addon/fold/indent-fold.js
vendored
44
shared/codemirror/addon/fold/indent-fold.js
vendored
@@ -1,44 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "indent", function(cm, start) {
|
|
||||||
var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line);
|
|
||||||
if (!/\S/.test(firstLine)) return;
|
|
||||||
var getIndent = function(line) {
|
|
||||||
return CodeMirror.countColumn(line, null, tabSize);
|
|
||||||
};
|
|
||||||
var myIndent = getIndent(firstLine);
|
|
||||||
var lastLineInFold = null;
|
|
||||||
// Go through lines until we find a line that definitely doesn't belong in
|
|
||||||
// the block we're folding, or to the end.
|
|
||||||
for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {
|
|
||||||
var curLine = cm.getLine(i);
|
|
||||||
var curIndent = getIndent(curLine);
|
|
||||||
if (curIndent > myIndent) {
|
|
||||||
// Lines with a greater indent are considered part of the block.
|
|
||||||
lastLineInFold = i;
|
|
||||||
} else if (!/\S/.test(curLine)) {
|
|
||||||
// Empty lines might be breaks within the block we're trying to fold.
|
|
||||||
} else {
|
|
||||||
// A non-empty line at an indent equal to or less than ours marks the
|
|
||||||
// start of another block.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lastLineInFold) return {
|
|
||||||
from: CodeMirror.Pos(start.line, firstLine.length),
|
|
||||||
to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
49
shared/codemirror/addon/fold/markdown-fold.js
vendored
49
shared/codemirror/addon/fold/markdown-fold.js
vendored
@@ -1,49 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "markdown", function(cm, start) {
|
|
||||||
var maxDepth = 100;
|
|
||||||
|
|
||||||
function isHeader(lineNo) {
|
|
||||||
var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0));
|
|
||||||
return tokentype && /\bheader\b/.test(tokentype);
|
|
||||||
}
|
|
||||||
|
|
||||||
function headerLevel(lineNo, line, nextLine) {
|
|
||||||
var match = line && line.match(/^#+/);
|
|
||||||
if (match && isHeader(lineNo)) return match[0].length;
|
|
||||||
match = nextLine && nextLine.match(/^[=\-]+\s*$/);
|
|
||||||
if (match && isHeader(lineNo + 1)) return nextLine[0] == "=" ? 1 : 2;
|
|
||||||
return maxDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1);
|
|
||||||
var level = headerLevel(start.line, firstLine, nextLine);
|
|
||||||
if (level === maxDepth) return undefined;
|
|
||||||
|
|
||||||
var lastLineNo = cm.lastLine();
|
|
||||||
var end = start.line, nextNextLine = cm.getLine(end + 2);
|
|
||||||
while (end < lastLineNo) {
|
|
||||||
if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break;
|
|
||||||
++end;
|
|
||||||
nextLine = nextNextLine;
|
|
||||||
nextNextLine = cm.getLine(end + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
from: CodeMirror.Pos(start.line, firstLine.length),
|
|
||||||
to: CodeMirror.Pos(end, cm.getLine(end).length)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user