mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-23 23:03:01 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a2e39355c | ||
|
|
ba600f5da6 | ||
|
|
2a340b7a65 | ||
|
|
2f27aeb77f | ||
|
|
1a0f29b6ef | ||
|
|
39b160e202 | ||
|
|
aa065fa4d8 | ||
|
|
19ca1db674 | ||
|
|
a58384d8d1 | ||
|
|
8e1951ba67 | ||
|
|
3dba731dd7 | ||
|
|
f8f19efcaa | ||
|
|
980fdf5ad1 | ||
|
|
e127855d84 | ||
|
|
8ffea70b2f | ||
|
|
3fbddd2e41 | ||
|
|
5a17697e7e | ||
|
|
6f66fdc6d6 | ||
|
|
a29fdb43c9 | ||
|
|
7462e66858 | ||
|
|
d9364cf60a | ||
|
|
b0375bddd1 | ||
|
|
56795afabb | ||
|
|
acf9f464f0 | ||
|
|
74c615f156 | ||
|
|
133af4ea2c | ||
|
|
4182c79354 | ||
|
|
759d986188 | ||
|
|
600ca90fc0 | ||
|
|
3b52888877 | ||
|
|
e23120a4c6 | ||
|
|
38d47f6aa1 | ||
|
|
3a25123d7b | ||
|
|
19c04e125a | ||
|
|
8a13387874 | ||
|
|
6c813ddab1 | ||
|
|
965870f8ed | ||
|
|
8add76fb50 | ||
|
|
af4ec3d096 | ||
|
|
b908cd7cbd | ||
|
|
6309ec0bfa | ||
|
|
45d1bef302 | ||
|
|
7d9e1aad83 | ||
|
|
aa2d1f3bc9 | ||
|
|
f6bd1ef513 | ||
|
|
c75ac3c0f5 | ||
|
|
ac2d6fe9a8 | ||
|
|
40d120d875 | ||
|
|
5e2fdcf1e9 | ||
|
|
57c8c24b20 | ||
|
|
460d3fe111 | ||
|
|
1d50cbf684 | ||
|
|
3a250d3da4 | ||
|
|
d05b819ff2 | ||
|
|
fcb3f9ca26 | ||
|
|
69b42ee6e0 |
35
README.FREEBSD.md
Normal file
35
README.FREEBSD.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# FreeBSD/FreeNAS Installation Instructions
|
||||
|
||||
## Before Installing
|
||||
|
||||
These instructions assume that you are installing to a completely new, fresh FreeBSD/FreeNAS jail. As such, some steps will not be necessary if you are installing to an existing FreeBSD/FreeNAS install.
|
||||
|
||||
## Installation instructions
|
||||
|
||||
1. Create a new jail, with the appropriate network settings to access the internet.
|
||||
|
||||
2. Install wget (`pkg install -y wget`). On a fresh jail, you will be prompted to press 'Y' to set up `pkg`.
|
||||
|
||||
3. Download the installation script (`wget --no-check-certificate https://raw.githubusercontent.com/naturalcrit/homebrewery/master/freebsd/install.sh`). The parameter `--no-check-certificate` is required as we haven't set up any trusted certificates/authorities yet.
|
||||
|
||||
4. Make the downloaded file executable (`chmod +x install.sh`).
|
||||
|
||||
5. Run the script (`./install.sh`). This will automatically download all of the required packages, install both them and HomeBrewery, configure the system and finally start HomeBrewery.
|
||||
|
||||
**NOTE:** At this time, the script **ONLY** installs HomeBrewery. It does **NOT** install the NaturalCrit login system, as that is currently a completely separate project.
|
||||
|
||||
---
|
||||
|
||||
### Testing
|
||||
|
||||
These installation instructions have been tested on the following FreeBSD/FreeNAS platforms:
|
||||
|
||||
* FreeNAS-11.3-U5; Jail 11.4-RELEASE-p2
|
||||
|
||||
## Final Notes
|
||||
|
||||
While this installation process works successfully at the time of writing (December 28, 2020), it relies on all of the Node.JS packages used in the HomeBrewery project retaining their cross-platform capabilities to continue to function under FreeBSD. This is one of the inherent advantages of Node.JS, but it is by no means guaranteed and as such, functionality or even installation under FreeBSD may fail without warning at some point in the future.
|
||||
|
||||
Regards,
|
||||
G
|
||||
December 28, 2020
|
||||
103
README.md
103
README.md
@@ -1,45 +1,93 @@
|
||||
# The Homebrewery
|
||||
The Homebrewery is a tool for making authentic looking [D&D content](https://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook) using [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). It is distributed under the terms of the [MIT License](./license).
|
||||
|
||||
[](https://app.circleci.com/pipelines/github/naturalcrit/homebrewery?branch=master)
|
||||
|
||||
The Homebrewery is a tool for making authentic looking [D&D content][dnd-content-url]
|
||||
using [Markdown][markdown-url]. It is distributed under the terms of the [MIT License](./license).
|
||||
|
||||
[dnd-content-url]: https://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook
|
||||
[markdown-url]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
|
||||
|
||||
## Quick Start
|
||||
The easiest way to get started using the Homebrewery is to use it [on our website](https://homebrewery.naturalcrit.com). The code is open source, so feel free to clone it, tinker with it. If you want to make changes to the code, you can run your own local version for testing by following the installation instructions below.
|
||||
The easiest way to get started using the Homebrewery is to use it
|
||||
[on our website][homebrewery-url]. The code is open source, so feel free to
|
||||
clone it, tinker with it. If you want to make changes to the code, you can run
|
||||
your own local version for testing by following the installation instructions
|
||||
below.
|
||||
|
||||
[homebrewery-url]: https://homebrewery.naturalcrit.com
|
||||
|
||||
### Installation
|
||||
First, install two programs that the Homebrewery requires to run.
|
||||
First, install three programs that the Homebrewery requires to run and retrieve
|
||||
updates:
|
||||
|
||||
1. install [node](https://nodejs.org/en/)
|
||||
1. install [mongodb](https://www.mongodb.com/)
|
||||
1. install [mongodb](https://www.mongodb.com/try/download/community) (Community version)
|
||||
|
||||
Second, download a copy of the repository. If you have git you can do so with
|
||||
For easiest installation, follow these steps:
|
||||
1. In the installer, uncheck the option to run as a service
|
||||
1. You can install MongoDB Compass if you want a GUI to view your database documents
|
||||
1. Go to the C drive and create a folder called "data"
|
||||
1. Inside the "data" folder, create a new folder called "db"
|
||||
1. Open a command prompt or other terminal and navigate to your mongodb install folder (c:program files\mongo\server\4.4\bin)
|
||||
1. In the command prompt, run "mongod", which will start up your local database server
|
||||
1. While MongoD is running, open a second command prompt and navigate to the mongodb install folder
|
||||
1. In the second command prompt, run "mongo", which allows you to edit the database
|
||||
1. Type `use homebrewery` to create the homebrewery database. You should see `switched to db homebrewery`
|
||||
1. Type `db.brews.insert({"title":"test"})` to create a blank document. You should see `WriteResult({ "nInserted" : 1 })`
|
||||
1. Search in Windows for "Advanced system settings" and open it
|
||||
1. Click "Environment variables", find the "path" variable, and double-click to open it
|
||||
1. Click "New" and paste in the path to the mongodb "bin" folder
|
||||
1. Click "OK", "OK", "OK" to close all the windows
|
||||
1. install [git](https://git-scm.com/downloads) (select the option that allows Git to run from the command prompt)
|
||||
|
||||
Checkout the repo ([documentation][github-clone-repo-docs-url]):
|
||||
```
|
||||
git clone https://github.com/naturalcrit/homebrewery.git
|
||||
```
|
||||
|
||||
Third, you will need to add the environment variable `NODE_ENV = local` to allow the project to run locally.
|
||||
[github-clone-repo-docs-url]: https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/cloning-a-repository
|
||||
|
||||
Second, you will need to add the environment variable `NODE_ENV=local` to allow
|
||||
the project to run locally.
|
||||
|
||||
You can set this temporarily in your shell of choice:
|
||||
* Windows Powershell: `$env:NODE_ENV="local"`
|
||||
* Windows CMD: `set NODE_ENV=local`
|
||||
* Linux / OSX: `export NODE_ENV=local`
|
||||
|
||||
Fourth, you will need to install the program and run it using the two commands:
|
||||
Third, you will need to install the Node dependencies, compile the app, and run
|
||||
it using the two commands:
|
||||
|
||||
1. `npm install`
|
||||
1. `npm start`
|
||||
|
||||
You should now be able to go to [http://localhost:8000](http://localhost:8000) in your browser and use the Homebrewery offline.
|
||||
You should now be able to go to [http://localhost:8000](http://localhost:8000)
|
||||
in your browser and use the Homebrewery offline.
|
||||
|
||||
### Running the application via Docker
|
||||
|
||||
Please see the docs here: [README.DOCKER.md](./README.DOCKER.md)
|
||||
|
||||
### Standalone PHB Stylesheet
|
||||
If you just want the stylesheet that is generated to make pages look like they are from the Player's Handbook, you will find it in the [phb.standalone.css](./phb.standalone.css) file.
|
||||
### Running the application on FreeBSD or FreeNAS
|
||||
|
||||
If you are developing locally and would like to generate your own, follow the above steps and then run `npm run phb`.
|
||||
Please see the docs here: [README.FreeBSD.md](./README.FREEBSD.md)
|
||||
|
||||
### Standalone PHB Stylesheet
|
||||
If you just want the stylesheet that is generated to make pages look like they
|
||||
are from the Player's Handbook, you will find it in the
|
||||
[phb.standalone.css](./phb.standalone.css) file.
|
||||
|
||||
If you are developing locally and would like to generate your own, follow the
|
||||
above steps and then run `npm run phb`.
|
||||
|
||||
## Issues, Suggestions, and Bugs
|
||||
If you run into any issues using The Homebrewery or have suggestions for improvement, please submit an issue [on GitHub](/issues). You can also get help for issues on the subreddit [r/homebrewery](https://www.reddit.com/r/homebrewery)
|
||||
If you run into any issues using The Homebrewery or have suggestions for
|
||||
improvement, please submit an issue [on GitHub][repo-issues-url].
|
||||
You can also get help for issues on the subreddit [r/homebrewery][subreddit-url]
|
||||
|
||||
[repo-issues-url]: https://github.com/naturalcrit/homebrewery/issues
|
||||
[subreddit-url]: https://www.reddit.com/r/homebrewery
|
||||
|
||||
## Changelog
|
||||
|
||||
@@ -47,6 +95,33 @@ You can check out the [changelog](./changelog.md).
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license](./license). Which means you are free to use The Homebrewery in any way that you want, except for claiming that you made it yourself.
|
||||
This project is licensed under the [MIT license](./license). Which means you
|
||||
are free to use The Homebrewery in any way that you want, except for claiming
|
||||
that you made it yourself.
|
||||
|
||||
If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||
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.
|
||||
|
||||
## Contributing
|
||||
|
||||
You are welcome to contribute to the development and maintenance of the
|
||||
project! There are several ways of doing that:
|
||||
- At the moment, we have a huge backlog of [issues][repo-issues-url] and some
|
||||
of them are outdated, duplicates or doesn't contain any useful info. In order
|
||||
to help you can [mark duplicates][github-mark-duplicate-url], try to
|
||||
reproduce some complex or weird issues, try with finding a workaround for a
|
||||
reported bug or just mention issue managers team to let them know about
|
||||
outdated issue via `@naturalcrit/issue-managers`.
|
||||
- Our [subreddit][subreddit-url] is constantly growing and there are number of
|
||||
bug reports: any help with sorting them out is very welcome.
|
||||
- And of course you can contribute by fixing a bug or implementing a new
|
||||
feature by yourself, we are waiting for your
|
||||
[pull requests][github-pr-docs-url]!
|
||||
|
||||
Anyway, if you would like to get in touch with the team and discuss/coordinate
|
||||
your contribution to the project, please join our [gitter chat][gitter-url].
|
||||
|
||||
[github-mark-duplicate-url]: https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/about-duplicate-issues-and-pull-requests
|
||||
[github-pr-docs-url]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
|
||||
[gitter-url]: https://gitter.im/naturalcrit/Lobby
|
||||
|
||||
60
changelog.md
60
changelog.md
@@ -1,5 +1,31 @@
|
||||
# changelog
|
||||
|
||||
### Friday, 25/1/2021 - v2.10.7
|
||||
- Cover Page snippet now flips left-right page numbering.
|
||||
- Added instructions for [installing on a FreeBSD Jail](https://github.com/naturalcrit/homebrewery/blob/master/README.FREEBSD.md).
|
||||
- Fix for box-shadows breaking across columns. <br>(Thanks @G-Ambatte for all of these!)
|
||||
- Small user interface tweaks (Thanks @Ericsheid)
|
||||
|
||||
### Friday, 02/1/2021 - v2.10.6
|
||||
- Fixed punctuation for usernames ending with 's' on the user page. (Thanks @AlexeySachkov)
|
||||
- Fixed server crashes due to excessive long lines in brews
|
||||
- Fixed "automated request" lockouts from Google
|
||||
|
||||
### Friday, 18/12/2020 - v2.10.5
|
||||
- Brews now immediately save when transferring between Google Drive and Homebrewery storage.
|
||||
- Added confirmation popup to clarify the transfer process.
|
||||
- Brews transferred or deleted from Google will be found in your Google Drive trash.
|
||||
- Dependency updates.
|
||||
|
||||
### Wednesday, 25/11/2020 - v2.10.4
|
||||
- Fixed Google Drive brews not saving metadata (view count, description, etc.) Note that we are still working on making published Google brews visible to the public when viewing your profile page.
|
||||
|
||||
### Thursday, 22/10/2020 - v2.10.3
|
||||
- Fixed brews with broken code crashing the edit page when loaded (the "blue screen of death" bug).
|
||||
|
||||
### Monday, 19/10/2020 - v2.10.2
|
||||
- Fixed issue with "recent" item links not updating when transferring between Google Drive.
|
||||
|
||||
### Monday, 12/10/2020 - v2.10.1
|
||||
- Fixed issue with users unable to create new brews
|
||||
- Fixing brews being lost when loaded via back button
|
||||
@@ -7,6 +33,9 @@
|
||||
### Wednesday, 07/10/2020 - v2.10.0
|
||||
- Google Drive integration -- Sign in with your Google account to link it with your Homebrewery profile. A new button in the Edit page will let you transfer your file to your personal Google Drive storage, and Google will keep a backup of each version! No more lost work surprises!
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
### Friday, 28/08/2020 - v2.9.2
|
||||
- Many dependency updates
|
||||
- Finally fixed this changelog page to not run off the edge :P
|
||||
@@ -44,20 +73,15 @@
|
||||
### Saturday, 22/04/2017 - v2.7.4
|
||||
- Give ability to hide the render warning notification
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
### Friday, 03/03/2017 - v2.7.3
|
||||
- Increasing the range on the Partial Page Rendering for a quick-fix for it getting out of sync on long brews.
|
||||
|
||||
|
||||
|
||||
### Saturday, 18/02/2017 - v2.7.2
|
||||
- Adding ability to delete a brew from the user page, incase the user creates a brew that makes the edit page unrender-able. (re:309)
|
||||
|
||||
\page
|
||||
|
||||
|
||||
### Thursday, 19/01/2017 - v2.7.0
|
||||
### Thursday, 19/01/2017 - v2.7.1
|
||||
- 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
|
||||
@@ -88,7 +112,6 @@
|
||||
- Added a table of contents snippet (thanks u/tullisar)
|
||||
- Added a multicolumn snippet
|
||||
|
||||
\page
|
||||
### Thursday, 01/12/2016
|
||||
- Added in a snippet for a split table
|
||||
- Added an account nav item to new page
|
||||
@@ -98,6 +121,10 @@
|
||||
- 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
|
||||
@@ -108,6 +135,7 @@
|
||||
- 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
|
||||
@@ -132,9 +160,6 @@
|
||||
- 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!)
|
||||
|
||||
@@ -146,6 +171,9 @@
|
||||
- Allows adding in hyperlinks to specific pages
|
||||
- Even works after you print to pdf!
|
||||
|
||||
|
||||
\page
|
||||
|
||||
### Tuesday, 07/06/2016 - v2.2.2
|
||||
- Fixed bug with new markdown lexer and aprser not working on print page
|
||||
|
||||
@@ -162,9 +190,6 @@
|
||||
- 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.
|
||||
|
||||
\page
|
||||
|
||||
- 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!)
|
||||
|
||||
@@ -172,6 +197,10 @@
|
||||
- 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.
|
||||
|
||||
@@ -185,6 +214,8 @@
|
||||
- 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!)
|
||||
|
||||
I've been working on v2 for a *very* long time. I want to thank you guys for being paitent.
|
||||
@@ -283,7 +314,6 @@ Massive changelog incoming:
|
||||
* Increased padding on table cells
|
||||
* Raw html now shows in view source
|
||||
|
||||
|
||||
## v1.0.0 - Release
|
||||
|
||||
### Wednesday, 3/01/2016
|
||||
|
||||
@@ -10,6 +10,7 @@ const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||
//TODO: move to the brew renderer
|
||||
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
|
||||
const NotificationPopup = require('./notificationPopup/notificationPopup.jsx');
|
||||
const Frame = require('react-frame-component').default;
|
||||
|
||||
const PAGE_HEIGHT = 1056;
|
||||
const PPR_THRESHOLD = 50;
|
||||
@@ -29,17 +30,20 @@ const BrewRenderer = createClass({
|
||||
height : 0,
|
||||
isMounted : false,
|
||||
|
||||
pages : pages,
|
||||
usePPR : pages.length >= PPR_THRESHOLD,
|
||||
pages : pages,
|
||||
usePPR : pages.length >= PPR_THRESHOLD,
|
||||
visibility : 'hidden',
|
||||
initialContent : `<!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 href='/homebrew/bundle.css' rel='stylesheet' />
|
||||
<base target=_blank>
|
||||
</head><body style='overflow: hidden'><div></div></body></html>`
|
||||
};
|
||||
},
|
||||
height : 0,
|
||||
lastRender : <div></div>,
|
||||
|
||||
componentDidMount : function() {
|
||||
this.updateSize();
|
||||
window.addEventListener('resize', this.updateSize);
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
window.removeEventListener('resize', this.updateSize);
|
||||
},
|
||||
@@ -54,8 +58,7 @@ const BrewRenderer = createClass({
|
||||
|
||||
updateSize : function() {
|
||||
this.setState({
|
||||
height : this.refs.main.parentNode.clientHeight,
|
||||
isMounted : true
|
||||
height : this.refs.main.parentNode.clientHeight,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -85,7 +88,7 @@ const BrewRenderer = createClass({
|
||||
},
|
||||
|
||||
renderPageInfo : function(){
|
||||
return <div className='pageInfo'>
|
||||
return <div className='pageInfo' ref='main'>
|
||||
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
|
||||
</div>;
|
||||
},
|
||||
@@ -111,7 +114,7 @@ const BrewRenderer = createClass({
|
||||
renderPages : function(){
|
||||
if(this.state.usePPR){
|
||||
return _.map(this.state.pages, (page, index)=>{
|
||||
if(this.shouldRender(page, index)){
|
||||
if(this.shouldRender(page, index) && typeof window !== 'undefined'){
|
||||
return this.renderPage(page, index);
|
||||
} else {
|
||||
return this.renderDummyPage(index);
|
||||
@@ -120,29 +123,59 @@ const BrewRenderer = createClass({
|
||||
}
|
||||
if(this.props.errors && this.props.errors.length) return this.lastRender;
|
||||
this.lastRender = _.map(this.state.pages, (page, index)=>{
|
||||
return this.renderPage(page, index);
|
||||
if(typeof window !== 'undefined') {
|
||||
return this.renderPage(page, index);
|
||||
} else {
|
||||
return this.renderDummyPage(index);
|
||||
}
|
||||
});
|
||||
return this.lastRender;
|
||||
},
|
||||
|
||||
frameDidMount : function(){ //This triggers when iFrame finishes internal "componentDidMount"
|
||||
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
|
||||
this.updateSize();
|
||||
window.addEventListener('resize', this.updateSize);
|
||||
this.renderPages(); //Make sure page is renderable before showing
|
||||
this.setState({
|
||||
isMounted : true,
|
||||
visibility : 'visible'
|
||||
});
|
||||
}, 100);
|
||||
},
|
||||
|
||||
render : function(){
|
||||
//render in iFrame so broken code doesn't crash the site.
|
||||
//Also render dummy page while iframe is mounting.
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className='brewRenderer'
|
||||
onScroll={this.handleScroll}
|
||||
ref='main'
|
||||
style={{ height: this.state.height }}>
|
||||
|
||||
<ErrorBar errors={this.props.errors} />
|
||||
<div className='popups'>
|
||||
<RenderWarnings />
|
||||
<NotificationPopup />
|
||||
{!this.state.isMounted
|
||||
? <div className='brewRenderer' onScroll={this.handleScroll}>
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderDummyPage(1)}
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
<Frame initialContent={this.state.initialContent} style={{ width: '100%', height: '100%', visibility: this.state.visibility }} contentDidMount={this.frameDidMount}>
|
||||
<div className='brewRenderer'
|
||||
onScroll={this.handleScroll}
|
||||
style={{ height: this.state.height }}>
|
||||
|
||||
<ErrorBar errors={this.props.errors} />
|
||||
<div className='popups'>
|
||||
<RenderWarnings />
|
||||
<NotificationPopup />
|
||||
</div>
|
||||
|
||||
<div className='pages' ref='pages'>
|
||||
{this.state.isMounted
|
||||
? this.renderPages()
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
</Frame>
|
||||
{this.renderPageInfo()}
|
||||
{this.renderPPRmsg()}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -23,6 +23,7 @@ const Editor = createClass({
|
||||
|
||||
metadata : {},
|
||||
onMetadataChange : ()=>{},
|
||||
showMetaButton : true
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
@@ -118,7 +119,8 @@ const Editor = createClass({
|
||||
brew={this.props.value}
|
||||
onInject={this.handleInject}
|
||||
onToggle={this.handgleToggle}
|
||||
showmeta={this.state.showMetadataEditor} />
|
||||
showmeta={this.state.showMetadataEditor}
|
||||
showMetaButton={this.props.showMetaButton} />
|
||||
{this.renderMetadataEditor()}
|
||||
<CodeEditor
|
||||
ref='codeEditor'
|
||||
|
||||
@@ -12,15 +12,14 @@ const execute = function(val, brew){
|
||||
return val;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const Snippetbar = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
brew : '',
|
||||
onInject : ()=>{},
|
||||
onToggle : ()=>{},
|
||||
showmeta : false
|
||||
brew : '',
|
||||
onInject : ()=>{},
|
||||
onToggle : ()=>{},
|
||||
showmeta : false,
|
||||
showMetaButton : true
|
||||
};
|
||||
},
|
||||
|
||||
@@ -41,13 +40,19 @@ const Snippetbar = createClass({
|
||||
});
|
||||
},
|
||||
|
||||
renderMetadataButton : function(){
|
||||
if(!this.props.showMetaButton) return;
|
||||
return <div className={cx('snippetBarButton', 'toggleMeta', { selected: this.props.showmeta })}
|
||||
onClick={this.props.onToggle}>
|
||||
<i className='fa fa-info-circle' />
|
||||
<span className='groupName'>Properties</span>
|
||||
</div>;
|
||||
},
|
||||
|
||||
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>
|
||||
{this.renderMetadataButton()}
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
@@ -82,7 +87,7 @@ const SnippetGroup = createClass({
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='snippetGroup'>
|
||||
return <div className='snippetGroup snippetBarButton'>
|
||||
<div className='text'>
|
||||
<i className={`fa fa-fw ${this.props.icon}`} />
|
||||
<span className='groupName'>{this.props.groupName}</span>
|
||||
|
||||
@@ -4,44 +4,33 @@
|
||||
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");
|
||||
.snippetBarButton{
|
||||
height : @height;
|
||||
line-height : @height;
|
||||
display : inline-block;
|
||||
padding : 0px 5px;
|
||||
font-weight : 800;
|
||||
font-size : 0.625em;
|
||||
text-transform : uppercase;
|
||||
cursor : pointer;
|
||||
&: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;
|
||||
}
|
||||
font-size : 1.4em;
|
||||
}
|
||||
}
|
||||
.toggleMeta{
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
border-left : 1px solid black;
|
||||
.tooltipLeft("Edit Brew Properties");
|
||||
}
|
||||
.snippetGroup{
|
||||
border-right : 1px solid black;
|
||||
&:hover{
|
||||
.dropdown{
|
||||
visibility : visible;
|
||||
@@ -62,7 +51,7 @@
|
||||
font-size : 10px;
|
||||
i{
|
||||
margin-right : 8px;
|
||||
font-size : 13px;
|
||||
font-size : 1.2em;
|
||||
}
|
||||
&:hover{
|
||||
background-color : #999;
|
||||
@@ -70,4 +59,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,13 @@ module.exports = ()=>{
|
||||
return `<style>
|
||||
.phb#p1{ text-align:center; }
|
||||
.phb#p1:after{ display:none; }
|
||||
.phb#p2 { counter-reset:phb-page-numbers; }
|
||||
.phb:nth-child(2n) .pageNumber { left: inherit !important; right: 2px !important; }
|
||||
.phb:nth-child(2n+1) .pageNumber { right: inherit !important; left: 2px !important; }
|
||||
.phb:nth-child(2n)::after { transform: scaleX(1); }
|
||||
.phb:nth-child(2n+1)::after { transform: scaleX(-1); }
|
||||
.phb:nth-child(2n) .footnote { left: inherit; text-align: right; }
|
||||
.phb:nth-child(2n+1) .footnote { left: 80px; text-align: left; }
|
||||
</style>
|
||||
|
||||
<div style='margin-top:450px;'></div>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
require('./homebrew.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const { StaticRouter:Router, Switch, Route } = require('react-router-dom');
|
||||
const queryString = require('query-string');
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
@@ -31,4 +30,4 @@ const EditTitle = createClass({
|
||||
|
||||
});
|
||||
|
||||
module.exports = EditTitle;
|
||||
module.exports = EditTitle;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
require('./navbar.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const PatreonNavItem = require('./patreon.navitem.jsx');
|
||||
|
||||
@@ -35,24 +35,32 @@ const RecentItems = createClass({
|
||||
|
||||
//== Add current brew to appropriate recent items list (depending on storageKey) ==//
|
||||
if(this.props.storageKey == 'edit'){
|
||||
let editId = this.props.brew.editId;
|
||||
if(this.props.brew.googleId){
|
||||
editId = `${this.props.brew.googleId}${this.props.brew.editId}`;
|
||||
}
|
||||
edited = _.filter(edited, (brew)=>{
|
||||
return brew.id !== this.props.brew.editId;
|
||||
return brew.id !== editId;
|
||||
});
|
||||
edited.unshift({
|
||||
id : this.props.brew.editId,
|
||||
id : editId,
|
||||
title : this.props.brew.title,
|
||||
url : `/edit/${this.props.brew.editId}`,
|
||||
url : `/edit/${editId}`,
|
||||
ts : Date.now()
|
||||
});
|
||||
}
|
||||
if(this.props.storageKey == 'view'){
|
||||
let shareId = this.props.brew.shareId;
|
||||
if(this.props.brew.googleId){
|
||||
shareId = `${this.props.brew.googleId}${this.props.brew.shareId}`;
|
||||
}
|
||||
viewed = _.filter(viewed, (brew)=>{
|
||||
return brew.id !== this.props.brew.shareId;
|
||||
return brew.id !== shareId;
|
||||
});
|
||||
viewed.unshift({
|
||||
id : this.props.brew.shareId,
|
||||
id : shareId,
|
||||
title : this.props.brew.title,
|
||||
url : `/share/${this.props.brew.shareId}`,
|
||||
url : `/share/${shareId}`,
|
||||
ts : Date.now()
|
||||
});
|
||||
}
|
||||
@@ -70,6 +78,40 @@ const RecentItems = createClass({
|
||||
});
|
||||
},
|
||||
|
||||
componentDidUpdate : function(prevProps) {
|
||||
if(prevProps.brew && this.props.brew.editId !== prevProps.brew.editId) {
|
||||
if(this.props.storageKey == 'edit') {
|
||||
let prevEditId = prevProps.brew.editId;
|
||||
if(prevProps.brew.googleId){
|
||||
prevEditId = `${prevProps.brew.googleId}${prevProps.brew.editId}`;
|
||||
}
|
||||
|
||||
edited = _.filter(this.state.edit, (brew)=>{
|
||||
return brew.id !== prevEditId;
|
||||
});
|
||||
let editId = this.props.brew.editId;
|
||||
if(this.props.brew.googleId){
|
||||
editId = `${this.props.brew.googleId}${this.props.brew.editId}`;
|
||||
}
|
||||
edited.unshift({
|
||||
id : editId,
|
||||
title : this.props.brew.title,
|
||||
url : `/edit/${editId}`,
|
||||
ts : Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
//== Store the updated lists (up to 8 items each) ==//
|
||||
edited = _.slice(edited, 0, 8);
|
||||
|
||||
localStorage.setItem(EDIT_KEY, JSON.stringify(edited));
|
||||
|
||||
this.setState({
|
||||
edit : edited
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleDropdown : function(show){
|
||||
this.setState({
|
||||
showDropdown : show
|
||||
@@ -140,4 +182,4 @@ module.exports = {
|
||||
showView={true}
|
||||
/>;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
//var striptags = require('striptags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const MAX_URL_SIZE = 2083;
|
||||
@@ -31,10 +27,7 @@ const RedditShare = createClass({
|
||||
const url = [
|
||||
MAIN_URL,
|
||||
`title=${encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!')}`,
|
||||
|
||||
`text=${encodeURIComponent(this.props.brew.text)}`
|
||||
|
||||
|
||||
].join('&');
|
||||
|
||||
window.open(url, '_blank');
|
||||
@@ -49,4 +42,4 @@ const RedditShare = createClass({
|
||||
|
||||
});
|
||||
|
||||
module.exports = RedditShare;
|
||||
module.exports = RedditShare;
|
||||
|
||||
@@ -3,7 +3,6 @@ require('./editPage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
const { Meta } = require('vitreum/headtags');
|
||||
|
||||
@@ -51,12 +50,13 @@ const EditPage = createClass({
|
||||
return {
|
||||
brew : this.props.brew,
|
||||
|
||||
isSaving : false,
|
||||
isPending : false,
|
||||
saveGoogle : this.props.brew.googleId ? true : false,
|
||||
errors : null,
|
||||
htmlErrors : Markdown.validate(this.props.brew.text),
|
||||
url : ''
|
||||
isSaving : false,
|
||||
isPending : false,
|
||||
saveGoogle : this.props.brew.googleId ? true : false,
|
||||
confirmGoogleTransfer : false,
|
||||
errors : null,
|
||||
htmlErrors : Markdown.validate(this.props.brew.text),
|
||||
url : ''
|
||||
};
|
||||
},
|
||||
savedBrew : null,
|
||||
@@ -135,12 +135,27 @@ const EditPage = createClass({
|
||||
}
|
||||
},
|
||||
|
||||
handleGoogleClick : function(){
|
||||
this.setState((prevState)=>({
|
||||
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
|
||||
}));
|
||||
this.clearErrors();
|
||||
},
|
||||
|
||||
toggleGoogleStorage : function(){
|
||||
this.setState((prevState)=>({
|
||||
saveGoogle : !prevState.saveGoogle,
|
||||
isSaving : false,
|
||||
errors : null
|
||||
}), ()=>this.trySave());
|
||||
}), ()=>this.save());
|
||||
},
|
||||
|
||||
clearErrors : function(){
|
||||
this.setState({
|
||||
errors : null,
|
||||
isSaving : false
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
save : async function(){
|
||||
@@ -163,7 +178,7 @@ const EditPage = createClass({
|
||||
console.log(err.status === 401
|
||||
? 'Not signed in!'
|
||||
: 'Error Saving to Google!');
|
||||
this.setState({ errors: err });
|
||||
this.setState({ errors: err, saveGoogle: false });
|
||||
});
|
||||
|
||||
if(!res) { return; }
|
||||
@@ -185,7 +200,7 @@ const EditPage = createClass({
|
||||
console.log(err.status === 401
|
||||
? 'Not signed in!'
|
||||
: 'Error Saving to Google!');
|
||||
this.setState({ errors: err });
|
||||
this.setState({ errors: err, saveGoogle: false });
|
||||
return;
|
||||
});
|
||||
|
||||
@@ -236,15 +251,38 @@ const EditPage = createClass({
|
||||
|
||||
renderGoogleDriveIcon : function(){
|
||||
if(this.state.saveGoogle) {
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.toggleGoogleStorage}>
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
|
||||
<img src={googleDriveActive} alt='googleDriveActive' />
|
||||
|
||||
{this.state.confirmGoogleTransfer &&
|
||||
<div className='errorContainer'>
|
||||
Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?<br />
|
||||
<div className='confirm' onClick={this.toggleGoogleStorage}>
|
||||
Yes
|
||||
</div>
|
||||
<div className='deny'>
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Nav.item>;
|
||||
} else {
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.toggleGoogleStorage}>
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
|
||||
<img src={googleDriveInactive} alt='googleDriveInactive' />
|
||||
|
||||
{this.state.confirmGoogleTransfer &&
|
||||
<div className='errorContainer'>
|
||||
Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?<br />
|
||||
<div className='confirm' onClick={this.toggleGoogleStorage}>
|
||||
Yes
|
||||
</div>
|
||||
<div className='deny'>
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
renderSaveButton : function(){
|
||||
@@ -258,12 +296,18 @@ const EditPage = createClass({
|
||||
if(this.state.errors.status == '401'){
|
||||
return <Nav.item className='save error' icon='fa-warning'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
<div className='errorContainer' onClick={this.clearErrors}>
|
||||
You must be signed in to a Google account
|
||||
to save this to Google Drive!<br />
|
||||
Sign in <a target='_blank' rel='noopener noreferrer'
|
||||
to save this to<br />Google Drive!<br />
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`http://naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
here</a>.
|
||||
<div className='confirm' onClick={this.toggleGoogleStorage}>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
<div className='deny'>
|
||||
Not Now
|
||||
</div>
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
@@ -273,7 +317,7 @@ const EditPage = createClass({
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' rel='noopener noreferrer'
|
||||
href={`https://github.com/stolksdorf/naturalcrit/issues/new?body=${encodeURIComponent(errMsg)}`}>
|
||||
href={`https://github.com/naturalcrt/naturalcrit/issues/new?body=${encodeURIComponent(errMsg)}`}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
@@ -311,7 +355,7 @@ const EditPage = createClass({
|
||||
Share
|
||||
</Nav.item>
|
||||
<PrintLink shareId={this.processShareId()} />
|
||||
<RecentNavItem brew={this.props.brew} storageKey='edit' />
|
||||
<RecentNavItem brew={this.state.brew} storageKey='edit' />
|
||||
<Account />
|
||||
</Nav.section>
|
||||
</Navbar>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
.editPage{
|
||||
.navItem.save{
|
||||
width : 105px;
|
||||
width : 106px;
|
||||
text-align : center;
|
||||
&.saved{
|
||||
cursor : initial;
|
||||
@@ -10,23 +10,78 @@
|
||||
&.error{
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
.errorContainer{
|
||||
position : absolute;
|
||||
top : 29px;
|
||||
left : -20px;
|
||||
z-index : 1000;
|
||||
width : 135px;
|
||||
padding : 6px;
|
||||
background-color : #333;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.googleDriveStorage {
|
||||
position : relative;
|
||||
}
|
||||
.googleDriveStorage img{
|
||||
height : 20px;
|
||||
padding : 0px;
|
||||
margin : -5px;
|
||||
}
|
||||
.errorContainer{
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 1000;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
require('./homePage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
const { Meta } = require('vitreum/headtags');
|
||||
@@ -61,11 +60,6 @@ const HomePage = createClass({
|
||||
</Nav.item>
|
||||
<RecentNavItem />
|
||||
<AccountNavItem />
|
||||
{/*}
|
||||
<Nav.item href='/new' color='green' icon='fa-external-link'>
|
||||
New Brew
|
||||
</Nav.item>
|
||||
*/}
|
||||
</Nav.section>
|
||||
</Navbar>;
|
||||
},
|
||||
@@ -77,7 +71,7 @@ const HomePage = createClass({
|
||||
|
||||
<div className='content'>
|
||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||
<Editor value={this.state.text} onChange={this.handleTextChange} ref='editor'/>
|
||||
<Editor value={this.state.text} onChange={this.handleTextChange} showMetaButton={false} ref='editor'/>
|
||||
<BrewRenderer text={this.state.text} />
|
||||
</SplitPane>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ require('./newPage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
require('./sharePage.less');
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const { Meta } = require('vitreum/headtags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
font-family : ScalySans;
|
||||
font-size : 1.2em;
|
||||
&>span{
|
||||
margin-right : 15px;
|
||||
margin-right : 12px;
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
|
||||
@@ -22,11 +22,15 @@ const BrewItem = require('./brewItem/brewItem.jsx');
|
||||
const UserPage = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
username : '',
|
||||
brews : [],
|
||||
googleBrews : []
|
||||
username : '',
|
||||
brews : []
|
||||
};
|
||||
},
|
||||
getUsernameWithS : function() {
|
||||
if(this.props.username.endsWith('s'))
|
||||
return `${this.props.username}'`;
|
||||
return `${this.props.username}'s`;
|
||||
},
|
||||
|
||||
renderBrews : function(brews){
|
||||
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
|
||||
@@ -58,11 +62,11 @@ const UserPage = createClass({
|
||||
<div className='content'>
|
||||
<div className='phb'>
|
||||
<div>
|
||||
<h1>{this.props.username}'s brews</h1>
|
||||
<h1>{this.getUsernameWithS()} brews</h1>
|
||||
{this.renderBrews(brews.published)}
|
||||
</div>
|
||||
<div>
|
||||
<h1>{this.props.username}'s unpublished brews</h1>
|
||||
<h1>{this.getUsernameWithS()} unpublished brews</h1>
|
||||
{this.renderBrews(brews.private)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -189,6 +189,7 @@ body {
|
||||
border-image : @noteBorderImage 11;
|
||||
border-image-outset : 9px 0px;
|
||||
box-shadow : 1px 4px 14px #888;
|
||||
-webkit-transform : translateZ(0); //Prevents shadows from breaking across columns
|
||||
p, ul{
|
||||
font-size : 0.352cm;
|
||||
line-height : 1.1em;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = async (name, props={})=>{
|
||||
module.exports = async(name, title = '', props = {})=>{
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -7,7 +7,7 @@ module.exports = async (name, props={})=>{
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link href=${`/${name}/bundle.css`} rel='stylesheet'></link>
|
||||
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
||||
<title>The Homebrewery - NaturalCrit</title>
|
||||
<title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title>
|
||||
</head>
|
||||
<body>
|
||||
<main id="reactRoot">${require(`../build/${name}/ssr.js`)(props)}</main>
|
||||
@@ -16,4 +16,4 @@ module.exports = async (name, props={})=>{
|
||||
<script>start_app(${JSON.stringify(props)})</script>
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"host" : "homebrewery.local.naturalcrit.com:8000",
|
||||
"naturalcrit_url" : "local.naturalcrit.com:8010",
|
||||
"secret" : "secret"
|
||||
}
|
||||
"secret" : "secret",
|
||||
"web_port" : 8000
|
||||
}
|
||||
|
||||
20
freebsd/install.sh
Normal file
20
freebsd/install.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
pkg install -y git nano node npm mongodb44
|
||||
|
||||
sysrc mongod_enable=YES
|
||||
service mongod start
|
||||
|
||||
cd /usr/local/
|
||||
git clone https://github.com/naturalcrit/homebrewery.git
|
||||
|
||||
cd homebrewery
|
||||
npm install
|
||||
npm audit fix
|
||||
npm run postinstall
|
||||
|
||||
cp freebsd/rc.d/homebrewery /usr/local/etc/rc.d/
|
||||
chmod +x /usr/local/etc/rc.d/homebrewery
|
||||
|
||||
sysrc homebrewery_enable=YES
|
||||
service homebrewery start
|
||||
65
freebsd/rc.d/homebrewery
Normal file
65
freebsd/rc.d/homebrewery
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: homebrewery
|
||||
# REQUIRE: NETWORKING
|
||||
# KEYWORD: shutdown
|
||||
|
||||
# Author: S Robertson
|
||||
# Version: 1.0.0
|
||||
|
||||
# Description:
|
||||
# This script runs HomeBrewery as a service under the supplied user on boot
|
||||
|
||||
# How to use:
|
||||
# Place this file in /usr/local/etc/rc.d/
|
||||
# Add homebrewery_enable="YES" to /etc/rc.config
|
||||
# (Optional) To run as non-root, add homebrewery_runAs="homebrewery" to /etc/rc.config
|
||||
# (Optional) To pass HomeBrewery args, add homebrewery_args="" to /etc/rc.config
|
||||
|
||||
# Freebsd rc library
|
||||
. /etc/rc.subr
|
||||
|
||||
# General Info
|
||||
name="homebrewery" # Safe name of program
|
||||
location="/usr/local/" # Install location
|
||||
program_name="homebrewery" # Name of exec
|
||||
title="HomeBrewery" # Title to display in top/htop
|
||||
|
||||
# RC.config vars
|
||||
load_rc_config $name # Loading rc config vars
|
||||
: ${homebrewery_enable="NO"} # Default: Do not enable HomeBrewery
|
||||
: ${homebrewery_runAs="root"} # Default: Run HomeBrewery as root
|
||||
: ${homebrewery_port=8000} # Default: Run HomeBrewery on port 8000
|
||||
: ${homebrewery_NODE_ENV="local"} # Default: Run HomeBrewery in local mode
|
||||
|
||||
# Freebsd Setup
|
||||
rcvar=homebrewery_enable # Enables the rc.conf YES/NO flag
|
||||
pidfile="/var/run/${program_name}.pid" # File that allows the system to keep track of HomeBrewery status
|
||||
|
||||
# Env Setup
|
||||
export HOME=$( getent passwd "homebrewery_runAs" | cut -d: -f6 ) # Gets the home directory of the runAs user
|
||||
export NODE_ENV=${homebrewery_NODE_ENV}
|
||||
export PORT=${homebrewery_port}
|
||||
|
||||
# Command Setup
|
||||
exec_cmd="${location}/${program_name}/server.js" # Path to the HomeBrewery server.js, /usr/local/bin/ when installed globally
|
||||
output_file="/var/log/${program_name}.log" # Path to HomeBrewery output file
|
||||
|
||||
# Command
|
||||
command="/usr/sbin/daemon"
|
||||
command_args="-r -t ${title} -u ${homebrewery_runAs} -o ${output_file} -P ${pidfile} /usr/local/bin/node ${exec_cmd} ${homebrewery_args}"
|
||||
|
||||
# Extra Commands
|
||||
extra_commands="dev_mode"
|
||||
|
||||
dev_mode_cmd="homebrewery_dev_mode"
|
||||
|
||||
homebrewery_dev_mode() {
|
||||
echo "Starting HomeBrewery in live rebuild Developer mode..."
|
||||
cd ${location}/${program_name}/
|
||||
/usr/local/bin/node ${location}/${program_name}/scripts/buildHomebrew.js --dev
|
||||
}
|
||||
|
||||
# Loading Config
|
||||
load_rc_config ${name}
|
||||
run_rc_command "$1"
|
||||
3041
package-lock.json
generated
3041
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "2.10.1",
|
||||
"version": "2.10.7",
|
||||
"engines": {
|
||||
"node": "12.16.x"
|
||||
},
|
||||
@@ -40,36 +40,38 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.11.4",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-react": "^7.12.10",
|
||||
"body-parser": "^1.19.0",
|
||||
"classnames": "^2.2.6",
|
||||
"codemirror": "^5.57.0",
|
||||
"codemirror": "^5.59.2",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"create-react-class": "^15.6.3",
|
||||
"create-react-class": "^15.7.0",
|
||||
"express": "^4.17.1",
|
||||
"fs-extra": "9.0.1",
|
||||
"googleapis": "59.0.0",
|
||||
"express-static-gzip": "2.1.1",
|
||||
"fs-extra": "9.1.0",
|
||||
"googleapis": "67.0.0",
|
||||
"jwt-simple": "^0.5.6",
|
||||
"less": "^3.12.2",
|
||||
"less": "^3.13.1",
|
||||
"lodash": "^4.17.20",
|
||||
"marked": "^0.3.19",
|
||||
"moment": "^2.27.0",
|
||||
"mongoose": "^5.10.0",
|
||||
"nanoid": "3.1.12",
|
||||
"nconf": "^0.10.0",
|
||||
"moment": "^2.29.1",
|
||||
"mongoose": "^5.11.13",
|
||||
"nanoid": "3.1.20",
|
||||
"nconf": "^0.11.1",
|
||||
"prop-types": "15.7.2",
|
||||
"query-string": "6.13.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"query-string": "6.13.8",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-frame-component": "4.1.3",
|
||||
"react-router-dom": "5.2.0",
|
||||
"superagent": "^6.0.0",
|
||||
"superagent": "^6.1.0",
|
||||
"vitreum": "github:calculuschild/vitreum#21a8e1c9421f1d3a3b474c12f480feb2fbd28c5b"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.7.0",
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"pico-check": "^1.3.2"
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"pico-check": "^2.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const fs = require('fs-extra');
|
||||
const zlib = require('zlib');
|
||||
const Proj = require('./project.json');
|
||||
|
||||
const { pack, watchFile, livereload } = require('vitreum');
|
||||
@@ -14,10 +15,16 @@ const transforms = {
|
||||
};
|
||||
|
||||
const build = async ({ bundle, render, ssr })=>{
|
||||
await fs.outputFile('./build/homebrew/bundle.css', await lessTransform.generate({ paths: './shared' }));
|
||||
const css = await lessTransform.generate({ paths: './shared' });
|
||||
await fs.outputFile('./build/homebrew/bundle.css', css);
|
||||
await fs.outputFile('./build/homebrew/bundle.js', bundle);
|
||||
await fs.outputFile('./build/homebrew/ssr.js', ssr);
|
||||
await fs.outputFile('./build/homebrew/render.js', render);
|
||||
|
||||
//compress files
|
||||
await fs.outputFile('./build/homebrew/bundle.css.br', zlib.brotliCompressSync(css));
|
||||
await fs.outputFile('./build/homebrew/bundle.js.br', zlib.brotliCompressSync(bundle));
|
||||
await fs.outputFile('./build/homebrew/ssr.js.br', zlib.brotliCompressSync(ssr));
|
||||
};
|
||||
|
||||
fs.emptyDirSync('./build/homebrew');
|
||||
|
||||
40
server.js
40
server.js
@@ -1,12 +1,22 @@
|
||||
const _ = require('lodash');
|
||||
const jwt = require('jwt-simple');
|
||||
const expressStaticGzip = require('express-static-gzip');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
const homebrewApi = require('./server/homebrew.api.js');
|
||||
const GoogleActions = require('./server/googleActions.js');
|
||||
|
||||
app.use(express.static(`${__dirname}/build`));
|
||||
// Serve brotli-compressed static files if available
|
||||
app.use('/', expressStaticGzip(`${__dirname}/build`, {
|
||||
enableBrotli : true,
|
||||
orderPreference : ['br'],
|
||||
index : false
|
||||
}));
|
||||
|
||||
process.chdir(__dirname);
|
||||
|
||||
//app.use(express.static(`${__dirname}/build`));
|
||||
app.use(require('body-parser').json({ limit: '25mb' }));
|
||||
app.use(require('cookie-parser')());
|
||||
app.use(require('./server/forcessl.mw.js'));
|
||||
@@ -44,14 +54,9 @@ app.use((req, res, next)=>{
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
app.use(homebrewApi);
|
||||
|
||||
app.use(require('./server/admin.api.js'));
|
||||
|
||||
//app.use('/user',require('./server/user.routes.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');
|
||||
@@ -149,6 +154,10 @@ app.get('/share/:id', (req, res, next)=>{
|
||||
const googleId = req.params.id.slice(0, -12);
|
||||
const shareId = req.params.id.slice(-12);
|
||||
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
|
||||
.then((brew)=>{
|
||||
GoogleActions.increaseView(googleId, shareId, 'share', brew);
|
||||
return brew;
|
||||
})
|
||||
.then((brew)=>{
|
||||
req.brew = brew; //TODO Need to sanitize later
|
||||
return next();
|
||||
@@ -200,11 +209,6 @@ app.get('/print/:id', (req, res, next)=>{
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/source/:id', (req, res)=>{
|
||||
|
||||
});
|
||||
|
||||
|
||||
//Render the page
|
||||
//const render = require('.build/render');
|
||||
const templateFn = require('./client/template.js');
|
||||
@@ -219,15 +223,15 @@ app.use((req, res)=>{
|
||||
googleBrews : req.googleBrews,
|
||||
account : req.account,
|
||||
};
|
||||
templateFn('homebrew', props)
|
||||
.then((page)=>{res.send(page);})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.sendStatus(500);
|
||||
});
|
||||
templateFn('homebrew', title = req.brew ? req.brew.title : '', props)
|
||||
.then((page)=>{ res.send(page); })
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.sendStatus(500);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
const PORT = process.env.PORT || 8000;
|
||||
const PORT = process.env.PORT || config.get('web_port') || 8000;
|
||||
app.listen(PORT);
|
||||
console.log(`server on port:${PORT}`);
|
||||
|
||||
@@ -37,7 +37,7 @@ const junkBrewQuery = HomebrewModel.find({
|
||||
|
||||
/* Search for brews that aren't compressed (missing the compressed text field) */
|
||||
const uncompressedBrewQuery = HomebrewModel.find({
|
||||
'textBin' : null
|
||||
'text' : { '$exists': true }
|
||||
}).lean().limit(10000).select('_id');
|
||||
|
||||
router.get('/admin/cleanup', mw.adminOnly, (req, res)=>{
|
||||
|
||||
@@ -73,8 +73,6 @@ GoogleActions = {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
console.log('created new drive folder with ID:');
|
||||
console.log(obj.data.id);
|
||||
folderId = obj.data.id;
|
||||
} else {
|
||||
folderId = obj.data.files[0].id;
|
||||
@@ -87,11 +85,17 @@ GoogleActions = {
|
||||
|
||||
oAuth2Client = GoogleActions.authCheck(req.account, res);
|
||||
|
||||
//TODO: Change to service account to allow non-owners to view published files.
|
||||
// Requires a driveId parameter in the drive.files.list command
|
||||
// const keys = JSON.parse(config.get('service_account'));
|
||||
// const auth = google.auth.fromJSON(keys);
|
||||
// auth.scopes = ['https://www.googleapis.com/auth/drive'];
|
||||
|
||||
const drive = google.drive({ version: 'v3', auth: oAuth2Client });
|
||||
|
||||
const obj = await drive.files.list({
|
||||
pageSize : 100,
|
||||
fields : 'nextPageToken, files(id, name, modifiedTime, properties)',
|
||||
fields : 'nextPageToken, files(id, name, description, modifiedTime, properties)',
|
||||
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
|
||||
})
|
||||
.catch((err)=>{
|
||||
@@ -107,15 +111,16 @@ GoogleActions = {
|
||||
text : '',
|
||||
shareId : file.properties.shareId,
|
||||
editId : file.properties.editId,
|
||||
createdAt : null,
|
||||
createdAt : file.createdTime,
|
||||
updatedAt : file.modifiedTime,
|
||||
gDrive : true,
|
||||
googleId : file.id,
|
||||
|
||||
title : file.properties.title,
|
||||
description : '',
|
||||
description : file.description,
|
||||
views : file.properties.views,
|
||||
tags : '',
|
||||
published : false,
|
||||
published : file.properties.published ? file.properties.published == 'true' : false,
|
||||
authors : [req.account.username], //TODO: properly save and load authors to google drive
|
||||
systems : []
|
||||
};
|
||||
@@ -129,6 +134,8 @@ GoogleActions = {
|
||||
|
||||
const result = await drive.files.get({ fileId: id })
|
||||
.catch((err)=>{
|
||||
console.log('error checking file exists...');
|
||||
console.log(err);
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -141,11 +148,17 @@ GoogleActions = {
|
||||
const drive = google.drive({ version: 'v3', auth: auth });
|
||||
|
||||
if(await GoogleActions.existsGoogleBrew(auth, brew.googleId) == true) {
|
||||
|
||||
await drive.files.update({
|
||||
fileId : brew.googleId,
|
||||
resource : { name : `${brew.title}.txt`,
|
||||
properties : { title: brew.title } //AppProperties is not accessible via API key
|
||||
resource : { name : `${brew.title}.txt`,
|
||||
description : `${brew.description}`,
|
||||
properties : { title : brew.title,
|
||||
published : brew.published,
|
||||
lastViewed : brew.lastViewed,
|
||||
views : brew.views,
|
||||
version : brew.version,
|
||||
tags : brew.tags,
|
||||
systems : brew.systems.join() }
|
||||
},
|
||||
media : { mimeType : 'text/plain',
|
||||
body : brew.text }
|
||||
@@ -171,12 +184,14 @@ GoogleActions = {
|
||||
const folderId = await GoogleActions.getGoogleFolder(auth);
|
||||
|
||||
const fileMetadata = {
|
||||
'name' : `${brew.title}.txt`,
|
||||
'parents' : [folderId],
|
||||
'properties' : { //AppProperties is not accessible
|
||||
'name' : `${brew.title}.txt`,
|
||||
'description' : `${brew.description}`,
|
||||
'parents' : [folderId],
|
||||
'properties' : { //AppProperties is not accessible
|
||||
'shareId' : nanoid(12),
|
||||
'editId' : nanoid(12),
|
||||
'title' : brew.title,
|
||||
'views' : '0'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -206,15 +221,15 @@ GoogleActions = {
|
||||
text : brew.text,
|
||||
shareId : fileMetadata.properties.shareId,
|
||||
editId : fileMetadata.properties.editId,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
createdAt : new Date(),
|
||||
updatedAt : new Date(),
|
||||
gDrive : true,
|
||||
googleId : obj.data.id,
|
||||
|
||||
title : brew.title,
|
||||
description : '',
|
||||
description : brew.description,
|
||||
tags : '',
|
||||
published : false,
|
||||
published : brew.published,
|
||||
authors : [],
|
||||
systems : []
|
||||
};
|
||||
@@ -224,11 +239,10 @@ GoogleActions = {
|
||||
|
||||
readFileMetadata : async (auth, id, accessId, accessType)=>{
|
||||
const drive = google.drive({ version: 'v3', auth: auth });
|
||||
console.log(auth);
|
||||
|
||||
const obj = await drive.files.get({
|
||||
fileId : id,
|
||||
fields : 'properties'
|
||||
fields : 'properties, createdTime, modifiedTime, description'
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log('Error loading from Google');
|
||||
@@ -236,8 +250,6 @@ GoogleActions = {
|
||||
return;
|
||||
});
|
||||
|
||||
console.log(`ACCESS TYPE: ${accessType}`);
|
||||
|
||||
if(obj) {
|
||||
if(accessType == 'edit' && obj.data.properties.editId != accessId){
|
||||
throw ('Edit ID does not match');
|
||||
@@ -245,8 +257,16 @@ GoogleActions = {
|
||||
throw ('Share ID does not match');
|
||||
}
|
||||
|
||||
const file = await drive.files.get({
|
||||
//Access actual file with service account. Just api key is causing "automated query" errors.
|
||||
const keys = JSON.parse(config.get('service_account'));
|
||||
const serviceAuth = google.auth.fromJSON(keys);
|
||||
serviceAuth.scopes = ['https://www.googleapis.com/auth/drive'];
|
||||
|
||||
const serviceDrive = google.drive({ version: 'v3', auth: serviceAuth });
|
||||
|
||||
const file = await serviceDrive.files.get({
|
||||
fileId : id,
|
||||
fields : 'description, properties',
|
||||
alt : 'media'
|
||||
})
|
||||
.catch((err)=>{
|
||||
@@ -255,20 +275,25 @@ GoogleActions = {
|
||||
});
|
||||
|
||||
const brew = {
|
||||
text : file.data,
|
||||
shareId : obj.data.properties.shareId,
|
||||
editId : obj.data.properties.editId,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
gDrive : true,
|
||||
googleId : id,
|
||||
shareId : obj.data.properties.shareId,
|
||||
editId : obj.data.properties.editId,
|
||||
title : obj.data.properties.title,
|
||||
text : file.data,
|
||||
|
||||
title : obj.data.properties.title,
|
||||
description : '',
|
||||
tags : '',
|
||||
published : false,
|
||||
description : obj.data.description,
|
||||
tags : obj.data.properties.tags ? obj.data.properties.tags : '',
|
||||
systems : obj.data.properties.systems ? obj.data.properties.systems.split(',') : [],
|
||||
authors : [],
|
||||
systems : []
|
||||
published : obj.data.properties.published ? obj.data.properties.published == 'true' : false,
|
||||
|
||||
createdAt : obj.data.createdTime,
|
||||
updatedAt : obj.data.modifiedTime,
|
||||
lastViewed : obj.data.properties.lastViewed,
|
||||
views : parseInt(obj.data.properties.views) || 0, //brews with no view parameter will return undefined
|
||||
version : parseInt(obj.data.properties.version) || 0,
|
||||
|
||||
gDrive : true,
|
||||
googleId : id
|
||||
};
|
||||
|
||||
return (brew);
|
||||
@@ -297,8 +322,9 @@ GoogleActions = {
|
||||
throw ('Not authorized to delete this Google brew');
|
||||
}
|
||||
|
||||
await drive.files.delete({
|
||||
fileId : googleId
|
||||
await drive.files.update({
|
||||
fileId : googleId,
|
||||
resource : { trashed: true }
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log('Can\'t delete Google file');
|
||||
@@ -306,6 +332,29 @@ GoogleActions = {
|
||||
});
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
|
||||
increaseView : async (id, accessId, accessType, brew)=>{
|
||||
//service account because this is modifying another user's file properties
|
||||
//so we need extended scope
|
||||
const keys = JSON.parse(config.get('service_account'));
|
||||
const auth = google.auth.fromJSON(keys);
|
||||
auth.scopes = ['https://www.googleapis.com/auth/drive'];
|
||||
|
||||
const drive = google.drive({ version: 'v3', auth: auth });
|
||||
|
||||
await drive.files.update({
|
||||
fileId : brew.googleId,
|
||||
resource : { properties : { views : brew.views + 1,
|
||||
lastViewed : new Date() } }
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log('Error updating Google views');
|
||||
console.error(err);
|
||||
//return res.status(500).send('Error while saving');
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ const HomebrewModel = require('./homebrew.model.js').model;
|
||||
const router = require('express').Router();
|
||||
const zlib = require('zlib');
|
||||
const GoogleActions = require('./googleActions.js');
|
||||
const Markdown = require('../shared/naturalcrit/markdown.js');
|
||||
|
||||
// const getTopBrews = (cb) => {
|
||||
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
|
||||
@@ -11,13 +12,8 @@ const GoogleActions = require('./googleActions.js');
|
||||
// };
|
||||
|
||||
const getGoodBrewTitle = (text)=>{
|
||||
const titlePos = text.indexOf('# ');
|
||||
if(titlePos !== -1) {
|
||||
const ending = text.indexOf('\n', titlePos);
|
||||
return text.substring(titlePos + 2, ending);
|
||||
} else {
|
||||
return _.find(text.split('\n'), (line)=>line);
|
||||
}
|
||||
const tokens = Markdown.marked.lexer(text);
|
||||
return title = (tokens.find((token)=>token.type == 'heading' || token.type == 'paragraph') || { text: 'No Title' }).text;
|
||||
};
|
||||
|
||||
const newBrew = (req, res)=>{
|
||||
|
||||
@@ -34,15 +34,17 @@ HomebrewSchema.methods.sanatize = function(full=false){
|
||||
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.methods.increaseView = async function(){
|
||||
this.lastViewed = new Date();
|
||||
this.views = this.views + 1;
|
||||
const text = this.text;
|
||||
this.text = undefined;
|
||||
await this.save()
|
||||
.catch((err)=>{
|
||||
return err;
|
||||
});
|
||||
this.text = text;
|
||||
return this;
|
||||
};
|
||||
|
||||
HomebrewSchema.statics.get = function(query){
|
||||
|
||||
@@ -46,11 +46,19 @@ const CodeEditor = createClass({
|
||||
makeBold : function() {
|
||||
const selection = this.codeMirror.getSelection();
|
||||
this.codeMirror.replaceSelection(`**${selection}**`, 'around');
|
||||
if(selection.length === 0){
|
||||
const cursor = this.codeMirror.getCursor();
|
||||
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 2 });
|
||||
}
|
||||
},
|
||||
|
||||
makeItalic : function() {
|
||||
const selection = this.codeMirror.getSelection();
|
||||
this.codeMirror.replaceSelection(`*${selection}*`, 'around');
|
||||
if(selection.length === 0){
|
||||
const cursor = this.codeMirror.getCursor();
|
||||
this.codeMirror.setCursor({ line: cursor.line, ch: cursor.ch - 1 });
|
||||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps : function(nextProps){
|
||||
|
||||
@@ -13,6 +13,76 @@ renderer.html = function (html) {
|
||||
return html;
|
||||
};
|
||||
|
||||
renderer.link = function (href, title, text) {
|
||||
let self = false;
|
||||
if(href[0] == '#') {
|
||||
self = true;
|
||||
}
|
||||
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
|
||||
|
||||
if(href === null) {
|
||||
return text;
|
||||
}
|
||||
let out = `<a href="${escape(href)}"`;
|
||||
if(title) {
|
||||
out += ` title="${title}"`;
|
||||
}
|
||||
if(self) {
|
||||
out += ' target="_self"';
|
||||
}
|
||||
out += `>${text}</a>`;
|
||||
return out;
|
||||
};
|
||||
|
||||
const nonWordAndColonTest = /[^\w:]/g;
|
||||
const cleanUrl = function (sanitize, base, href) {
|
||||
if(sanitize) {
|
||||
let prot;
|
||||
try {
|
||||
prot = decodeURIComponent(unescape(href))
|
||||
.replace(nonWordAndColonTest, '')
|
||||
.toLowerCase();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
if(prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
href = encodeURI(href).replace(/%25/g, '%');
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
return href;
|
||||
};
|
||||
|
||||
const escapeTest = /[&<>"']/;
|
||||
const escapeReplace = /[&<>"']/g;
|
||||
const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
|
||||
const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
|
||||
const escapeReplacements = {
|
||||
'&' : '&',
|
||||
'<' : '<',
|
||||
'>' : '>',
|
||||
'"' : '"',
|
||||
'\'' : '''
|
||||
};
|
||||
const getEscapeReplacement = (ch)=>escapeReplacements[ch];
|
||||
const escape = function (html, encode) {
|
||||
if(encode) {
|
||||
if(escapeTest.test(html)) {
|
||||
return html.replace(escapeReplace, getEscapeReplacement);
|
||||
}
|
||||
} else {
|
||||
if(escapeTestNoEncode.test(html)) {
|
||||
return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
const sanatizeScriptTags = (content)=>{
|
||||
return content
|
||||
.replace(/<script/ig, '<script')
|
||||
@@ -87,4 +157,3 @@ module.exports = {
|
||||
return errors;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ const SplitPane = createClass({
|
||||
},
|
||||
*/
|
||||
renderDivider : function(){
|
||||
return <div className='divider' onMouseDown={this.handleDown}>
|
||||
return <div className='divider' onMouseDown={this.handleDown} >
|
||||
<div className='dots'>
|
||||
<i className='fa fa-circle' />
|
||||
<i className='fa fa-circle' />
|
||||
@@ -67,16 +67,11 @@ const SplitPane = createClass({
|
||||
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
||||
<Pane ref='pane1' width={this.state.size}>{this.props.children[0]}</Pane>
|
||||
{this.renderDivider()}
|
||||
<Pane ref='pane2'>{this.props.children[1]}</Pane>
|
||||
<Pane ref='pane2' isDragging={this.state.isDragging}>{this.props.children[1]}</Pane>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const Pane = createClass({
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
@@ -90,12 +85,16 @@ const Pane = createClass({
|
||||
flex : 'none',
|
||||
width : `${this.props.width}px`
|
||||
};
|
||||
} else {
|
||||
styles = {
|
||||
pointerEvents : this.props.isDragging ? 'none' : 'auto' //Disable mouse capture in the rightmost pane; dragging into the iframe drops the divider otherwise
|
||||
};
|
||||
}
|
||||
|
||||
return <div className={cx('pane', this.props.className)} style={styles}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = SplitPane;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
height : 100%;
|
||||
width : 12px;
|
||||
cursor : ew-resize;
|
||||
background-color : #ddd;
|
||||
background-color : #bbb;
|
||||
text-align : center;
|
||||
.dots{
|
||||
display : table-cell;
|
||||
|
||||
Reference in New Issue
Block a user