mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-31 06:32:37 +00:00
Merge branch 'master' into SwappableThemes-ReorganizeFolderStructure
This commit is contained in:
@@ -2,17 +2,23 @@
|
|||||||
#
|
#
|
||||||
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
||||||
#
|
#
|
||||||
version: 2
|
version: 2.1
|
||||||
|
|
||||||
|
orbs:
|
||||||
|
node: circleci/node@3.0.0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:16.10.0
|
- image: cimg/node:16.11.0
|
||||||
- image: circleci/mongo:4.4
|
- image: mongo:4.4
|
||||||
|
|
||||||
working_directory: ~/repo
|
working_directory: ~/homebrewery
|
||||||
|
executor: node/default
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout:
|
||||||
|
path: ~/homebrewery
|
||||||
|
|
||||||
# Download and cache dependencies
|
# Download and cache dependencies
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@@ -21,12 +27,48 @@ jobs:
|
|||||||
# fallback to using the latest cache if no exact match is found
|
# fallback to using the latest cache if no exact match is found
|
||||||
- v1-dependencies-
|
- v1-dependencies-
|
||||||
|
|
||||||
- run: npm install
|
- node/install-npm
|
||||||
|
- node/install-packages:
|
||||||
|
app-dir: ~/homebrewery
|
||||||
|
cache-path: node_modules
|
||||||
|
override-ci-command: npm i
|
||||||
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
paths:
|
paths:
|
||||||
- node_modules
|
- node_modules
|
||||||
key: v1-dependencies-{{ checksum "package.json" }}
|
key: v1-dependencies-{{ checksum "package.json" }}
|
||||||
|
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: .
|
||||||
|
paths:
|
||||||
|
- .
|
||||||
|
|
||||||
|
test:
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:16.11.0
|
||||||
|
|
||||||
|
working_directory: ~/homebrewery
|
||||||
|
parallelism: 4
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- attach_workspace:
|
||||||
|
at: .
|
||||||
|
|
||||||
# run tests!
|
# run tests!
|
||||||
- run: npm run circleci
|
- run:
|
||||||
|
name: Test - Basic
|
||||||
|
command: npm run test:basic
|
||||||
|
- run:
|
||||||
|
name: Test - Mustache Spans
|
||||||
|
command: npm run test:mustache-span
|
||||||
|
- run:
|
||||||
|
name: Test - Routes
|
||||||
|
command: npm run test:route
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
build_and_test:
|
||||||
|
jobs:
|
||||||
|
- build
|
||||||
|
- test:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
57
README.md
57
README.md
@@ -9,37 +9,37 @@ using [Markdown][markdown-url]. It is distributed under the terms of the [MIT Li
|
|||||||
[markdown-url]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
|
[markdown-url]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
The easiest way to get started using the Homebrewery is to use it
|
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
|
[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
|
clone it and 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
|
your own local version for testing by following the installation instructions
|
||||||
below.
|
below.
|
||||||
|
|
||||||
[homebrewery-url]: https://homebrewery.naturalcrit.com
|
[homebrewery-url]: https://homebrewery.naturalcrit.com
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
First, install three programs that the Homebrewery requires to run and retrieve
|
First, install three programs that The Homebrewery requires to run and retrieve
|
||||||
updates:
|
updates:
|
||||||
|
|
||||||
1. install [node](https://nodejs.org/en/)
|
1. install [node](https://nodejs.org/en/)
|
||||||
1. install [mongodb](https://www.mongodb.com/try/download/community) (Community version)
|
1. install [mongodb](https://www.mongodb.com/try/download/community) (Community version)
|
||||||
|
|
||||||
For easiest installation, follow these steps:
|
For the easiest installation, follow these steps:
|
||||||
1. In the installer, uncheck the option to run as a service
|
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. 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. Go to the C:\ drive and create a folder called "data".
|
||||||
1. Inside the "data" folder, create a new folder called "db"
|
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. 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. 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. 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. 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 `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. 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. 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 "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 "New" and paste in the path to the MongoDB "bin" folder.
|
||||||
1. Click "OK", "OK", "OK" to close all the windows
|
1. Click "OK" three times 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)
|
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]):
|
Checkout the repo ([documentation][github-clone-repo-docs-url]):
|
||||||
```
|
```
|
||||||
@@ -54,7 +54,7 @@ the project to run locally.
|
|||||||
You can set this temporarily in your shell of choice:
|
You can set this temporarily in your shell of choice:
|
||||||
* Windows Powershell: `$env:NODE_ENV="local"`
|
* Windows Powershell: `$env:NODE_ENV="local"`
|
||||||
* Windows CMD: `set NODE_ENV=local`
|
* Windows CMD: `set NODE_ENV=local`
|
||||||
* Linux / OSX: `export NODE_ENV=local`
|
* Linux / macOS: `export NODE_ENV=local`
|
||||||
|
|
||||||
Third, you will need to install the Node dependencies, compile the app, and run
|
Third, you will need to install the Node dependencies, compile the app, and run
|
||||||
it using the two commands:
|
it using the two commands:
|
||||||
@@ -63,7 +63,7 @@ it using the two commands:
|
|||||||
1. `npm start`
|
1. `npm start`
|
||||||
|
|
||||||
You should now be able to go to [http://localhost:8000](http://localhost:8000)
|
You should now be able to go to [http://localhost:8000](http://localhost:8000)
|
||||||
in your browser and use the Homebrewery offline.
|
in your browser and use The Homebrewery offline.
|
||||||
|
|
||||||
### Running the application via Docker
|
### Running the application via Docker
|
||||||
|
|
||||||
@@ -95,11 +95,11 @@ You can check out the [changelog](./changelog.md).
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the [MIT license](./license). Which means you
|
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
|
are free to use The Homebrewery in any way that you want, except for claiming
|
||||||
that you made it yourself.
|
that you made it yourself.
|
||||||
|
|
||||||
If you wish to sell or in some way gain profit for what's created on this site,
|
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
|
it's your responsibility to ensure you have the proper licenses/rights for any
|
||||||
images or resources used.
|
images or resources used.
|
||||||
|
|
||||||
@@ -108,13 +108,12 @@ images or resources used.
|
|||||||
You are welcome to contribute to the development and maintenance of the
|
You are welcome to contribute to the development and maintenance of the
|
||||||
project! There are several ways of doing that:
|
project! There are several ways of doing that:
|
||||||
- At the moment, we have a huge backlog of [issues][repo-issues-url] and some
|
- 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
|
of them are outdated, duplicates, or don't contain any useful info. To help, you can [mark duplicates][github-mark-duplicate-url], try to
|
||||||
to help you can [mark duplicates][github-mark-duplicate-url], try to
|
reproduce some complex or weird issues, try finding a workaround for a
|
||||||
reproduce some complex or weird issues, try with finding a workaround for a
|
reported bug, or just mention our issue managers team to let them know about
|
||||||
reported bug or just mention issue managers team to let them know about
|
outdated issues via `@naturalcrit/issue-managers`.
|
||||||
outdated issue via `@naturalcrit/issue-managers`.
|
|
||||||
- Our [subreddit][subreddit-url] is constantly growing and there are number of
|
- Our [subreddit][subreddit-url] is constantly growing and there are number of
|
||||||
bug reports: any help with sorting them out is very welcome.
|
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
|
- And of course you can contribute by fixing a bug or implementing a new
|
||||||
feature by yourself, we are waiting for your
|
feature by yourself, we are waiting for your
|
||||||
[pull requests][github-pr-docs-url]!
|
[pull requests][github-pr-docs-url]!
|
||||||
|
|||||||
36
changelog.md
36
changelog.md
@@ -29,11 +29,43 @@ pre {
|
|||||||
.page p + pre {
|
.page p + pre {
|
||||||
margin-top : 0.1cm;
|
margin-top : 0.1cm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page .openSans {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## changelog
|
## changelog
|
||||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||||
|
|
||||||
|
### Wednesday 27/03/2022 - v3.0.8
|
||||||
|
{{taskList
|
||||||
|
* [x] Style updates to user page.
|
||||||
|
|
||||||
|
* [x] Added a logout button (finally)! You can find it under {{openSans **USERNAME {{fa,fa-user}} → LOGOUT {{fas,fa-power-off}}**}}
|
||||||
|
|
||||||
|
Fixes issues: [#303](https://github.com/naturalcrit/homebrewery/issues/303)
|
||||||
|
|
||||||
|
* [x] Clarified the default text when submitting an issue via Reddit post.
|
||||||
|
|
||||||
|
* [x] Fixed broken Table of Contents links in PDFs. (Thanks lucastucious!)
|
||||||
|
|
||||||
|
Fixes issues: [#1749](https://github.com/naturalcrit/homebrewery/issues/1749)
|
||||||
|
|
||||||
|
* [x] Fixed window resizing causing the edit page divider to get lost off of the edge of the page.
|
||||||
|
|
||||||
|
Fixes issues: [#2053](https://github.com/naturalcrit/homebrewery/issues/2053)
|
||||||
|
|
||||||
|
* [x] Fixed Class Table decorations overlapping main text.
|
||||||
|
|
||||||
|
Fixes issues: [#1985](https://github.com/naturalcrit/homebrewery/issues/1985)
|
||||||
|
|
||||||
|
* [x] Updated {{openSans **STYLE EDITOR {{fa,fa-pencil-alt}} → REMOVE DROP CAP {{fas,fa-remove-format}}**}} snippet to also remove small-caps first line font.
|
||||||
|
|
||||||
|
* [x] Background work in preparation for brew themes.
|
||||||
|
}}
|
||||||
|
|
||||||
### Wednesday 02/02/2022 - v3.0.7
|
### Wednesday 02/02/2022 - v3.0.7
|
||||||
{{taskList
|
{{taskList
|
||||||
* [x] Revert active line highlighting.
|
* [x] Revert active line highlighting.
|
||||||
@@ -50,7 +82,7 @@ For a full record of development, visit our [Github Page](https://github.com/nat
|
|||||||
|
|
||||||
Fixes issues: [#1943](https://github.com/naturalcrit/homebrewery/issues/1943)
|
Fixes issues: [#1943](https://github.com/naturalcrit/homebrewery/issues/1943)
|
||||||
|
|
||||||
* [x] Added a Legacy to V3 migration guide under **NEED HELP? {{fa,fa-question-circle}} → MIGRATE {{fas,fa-file-import}}**
|
* [x] Added a Legacy to V3 migration guide under {{openSans **NEED HELP? {{fa,fa-question-circle}} → MIGRATE {{fas,fa-file-import}}**}}
|
||||||
|
|
||||||
* [x] Background refactoring and unit tests.
|
* [x] Background refactoring and unit tests.
|
||||||
}}
|
}}
|
||||||
@@ -61,7 +93,7 @@ For a full record of development, visit our [Github Page](https://github.com/nat
|
|||||||
|
|
||||||
Fixes issues: [#1736](https://github.com/naturalcrit/homebrewery/issues/1736)
|
Fixes issues: [#1736](https://github.com/naturalcrit/homebrewery/issues/1736)
|
||||||
|
|
||||||
* [x] Code search/replace `CTRL F / CTRL SHIFT F`
|
* [x] Code search/replace PC: `CTRL F / CTRL SHIFT F` / Mac: `CMD F / OPTION CMD F`
|
||||||
|
|
||||||
Fixes issues: [#1201](https://github.com/naturalcrit/homebrewery/issues/1201)
|
Fixes issues: [#1201](https://github.com/naturalcrit/homebrewery/issues/1201)
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const MetadataEditor = createClass({
|
|||||||
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
|
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.delete(`/api/${this.props.metadata.editId}`)
|
request.delete(`/api/${this.props.metadata.googleId}${this.props.metadata.editId}`)
|
||||||
.send()
|
.send()
|
||||||
.end(function(err, res){
|
.end(function(err, res){
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
|
|||||||
@@ -32,11 +32,14 @@ const Homebrew = createClass({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillMount : function() {
|
|
||||||
global.account = this.props.account;
|
getInitialState : function(){
|
||||||
global.version = this.props.version;
|
global.version = this.props.version;
|
||||||
|
global.account = this.props.account;
|
||||||
global.enable_v3 = this.props.enable_v3;
|
global.enable_v3 = this.props.enable_v3;
|
||||||
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function (){
|
render : function (){
|
||||||
return (
|
return (
|
||||||
<Router location={this.props.url}>
|
<Router location={this.props.url}>
|
||||||
@@ -46,7 +49,7 @@ const Homebrew = createClass({
|
|||||||
<Route path='/share/:id' component={(routeProps)=><SharePage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
<Route path='/share/:id' component={(routeProps)=><SharePage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
||||||
<Route path='/new/:id' component={(routeProps)=><NewPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
<Route path='/new/:id' component={(routeProps)=><NewPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
||||||
<Route path='/new' exact component={(routeProps)=><NewPage />}/>
|
<Route path='/new' exact component={(routeProps)=><NewPage />}/>
|
||||||
<Route path='/user/:username' component={(routeProps)=><UserPage username={routeProps.match.params.username} brews={this.props.brews} />}/>
|
<Route path='/user/:username' component={(routeProps)=><UserPage username={routeProps.match.params.username} brews={this.props.brews} query={queryString.parse(routeProps.location.search)}/>}/>
|
||||||
<Route path='/print/:id' component={(routeProps)=><PrintPage brew={this.props.brew} query={queryString.parse(routeProps.location.search)} />}/>
|
<Route path='/print/:id' component={(routeProps)=><PrintPage brew={this.props.brew} query={queryString.parse(routeProps.location.search)} />}/>
|
||||||
<Route path='/print' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} />}/>
|
<Route path='/print' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} />}/>
|
||||||
<Route path='/changelog' exact component={()=><SharePage brew={this.props.brew} />}/>
|
<Route path='/changelog' exact component={()=><SharePage brew={this.props.brew} />}/>
|
||||||
|
|||||||
@@ -18,11 +18,50 @@ const Account = createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleLogout : function(){
|
||||||
|
if(confirm('Are you sure you want to log out?')) {
|
||||||
|
// Reset divider position
|
||||||
|
window.localStorage.removeItem('naturalcrit-pane-split');
|
||||||
|
// Clear login cookie
|
||||||
|
let domain = '';
|
||||||
|
if(window.location?.hostname) {
|
||||||
|
let domainArray = window.location.hostname.split('.');
|
||||||
|
if(domainArray.length > 2){
|
||||||
|
domainArray = [''].concat(domainArray.slice(-2));
|
||||||
|
}
|
||||||
|
domain = domainArray.join('.');
|
||||||
|
}
|
||||||
|
document.cookie = `nc_session=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;samesite=lax;${domain ? `domain=${domain}` : ''}`;
|
||||||
|
window.location = '/';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
if(global.account){
|
if(global.account){
|
||||||
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fas fa-user'>
|
return <Nav.dropdown>
|
||||||
{global.account.username}
|
<Nav.item
|
||||||
</Nav.item>;
|
className='account'
|
||||||
|
color='orange'
|
||||||
|
icon='fas fa-user'
|
||||||
|
>
|
||||||
|
{global.account.username}
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item
|
||||||
|
href={`/user/${global.account.username}`}
|
||||||
|
color='yellow'
|
||||||
|
icon='fas fa-beer'
|
||||||
|
>
|
||||||
|
brews
|
||||||
|
</Nav.item>
|
||||||
|
<Nav.item
|
||||||
|
className='logout'
|
||||||
|
color='red'
|
||||||
|
icon='fas fa-power-off'
|
||||||
|
onClick={this.handleLogout}
|
||||||
|
>
|
||||||
|
logout
|
||||||
|
</Nav.item>
|
||||||
|
</Nav.dropdown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Nav.item href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`} color='teal' icon='fas fa-sign-in-alt'>
|
return <Nav.item href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`} color='teal' icon='fas fa-sign-in-alt'>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const createClass = require('create-react-class');
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const dedent = require('dedent-tabs').default;
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
@@ -10,7 +11,11 @@ module.exports = function(props){
|
|||||||
need help?
|
need help?
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<Nav.item color='red' icon='fas fa-fw fa-bug'
|
<Nav.item color='red' icon='fas fa-fw fa-bug'
|
||||||
href={`https://www.reddit.com/r/homebrewery/submit?selftext=true&title=${encodeURIComponent('[Issue] Describe Your Issue Here')}`}
|
href={`https://www.reddit.com/r/homebrewery/submit?selftext=true&text=${encodeURIComponent(dedent`
|
||||||
|
**Browser(s)** :
|
||||||
|
**Operating System** :
|
||||||
|
**Legacy or v3 Renderer** :
|
||||||
|
**Issue** : `)}`}
|
||||||
newTab={true}
|
newTab={true}
|
||||||
rel='noopener noreferrer'>
|
rel='noopener noreferrer'>
|
||||||
report issue
|
report issue
|
||||||
|
|||||||
@@ -14,12 +14,10 @@ const Navbar = createClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount : function() {
|
getInitialState : function() {
|
||||||
//const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
|
return {
|
||||||
this.setState({
|
ver : global.version
|
||||||
//showNonChromeWarning : !isChrome,
|
};
|
||||||
ver : window.version
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -142,4 +142,7 @@
|
|||||||
text-align : center;
|
text-align : center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.account.navItem{
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,19 +31,11 @@ const BrewItem = createClass({
|
|||||||
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
|
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.props.brew.googleId) {
|
request.delete(`/api/${this.props.brew.googleId}${this.props.brew.editId}`)
|
||||||
request.get(`/api/removeGoogle/${this.props.brew.googleId}${this.props.brew.editId}`)
|
.send()
|
||||||
.send()
|
.end(function(err, res){
|
||||||
.end(function(err, res){
|
location.reload();
|
||||||
location.reload();
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
request.delete(`/api/${this.props.brew.editId}`)
|
|
||||||
.send()
|
|
||||||
.end(function(err, res){
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderDeleteBrewLink : function(){
|
renderDeleteBrewLink : function(){
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ const ListPage = createClass({
|
|||||||
return {
|
return {
|
||||||
sortType : 'alpha',
|
sortType : 'alpha',
|
||||||
sortDir : 'asc',
|
sortDir : 'asc',
|
||||||
filterString : ''
|
filterString : this.props.query?.filter || '',
|
||||||
|
query : this.props.query
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -74,19 +75,35 @@ const ListPage = createClass({
|
|||||||
|
|
||||||
handleFilterTextChange : function(e){
|
handleFilterTextChange : function(e){
|
||||||
this.setState({
|
this.setState({
|
||||||
filterString : e.target.value
|
filterString : e.target.value,
|
||||||
});
|
});
|
||||||
|
this.updateUrl(e.target.value);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateUrl : function(filterTerm){
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const urlParams = new URLSearchParams(url.search);
|
||||||
|
if(urlParams.get('filter') == filterTerm)
|
||||||
|
return;
|
||||||
|
if(!filterTerm)
|
||||||
|
urlParams.delete('filter');
|
||||||
|
else
|
||||||
|
urlParams.set('filter', filterTerm);
|
||||||
|
url.search = urlParams;
|
||||||
|
window.history.replaceState(null, null, url);
|
||||||
|
},
|
||||||
|
|
||||||
renderFilterOption : function(){
|
renderFilterOption : function(){
|
||||||
return <td>
|
return <td>
|
||||||
<label>
|
<label>
|
||||||
<i className='fas fa-search'></i>
|
<i className='fas fa-search'></i>
|
||||||
<input
|
<input
|
||||||
type='search'
|
type='search'
|
||||||
placeholder='search title/description'
|
autoFocus={true}
|
||||||
|
placeholder='filter title/description'
|
||||||
onChange={this.handleFilterTextChange}
|
onChange={this.handleFilterTextChange}
|
||||||
|
value={this.state.filterString}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</td>;
|
</td>;
|
||||||
|
|||||||
@@ -200,73 +200,18 @@ const EditPage = createClass({
|
|||||||
const brew = this.state.brew;
|
const brew = this.state.brew;
|
||||||
brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
|
brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
|
||||||
|
|
||||||
if(this.state.saveGoogle) {
|
const params = `${transfer ? `?transfer${this.state.saveGoogle ? 'To' : 'From'}Google=true` : ''}`;
|
||||||
if(transfer) {
|
const res = await request
|
||||||
const res = await request
|
.put(`/api/update/${brew.editId}${params}`)
|
||||||
.post('/api/newGoogle/')
|
.send(brew)
|
||||||
.send(brew)
|
.catch((err)=>{
|
||||||
.catch((err)=>{
|
console.log('Error Updating Local Brew');
|
||||||
console.log(err.status === 401
|
this.setState({ errors: err });
|
||||||
? 'Not signed in!'
|
});
|
||||||
: 'Error Transferring to Google!');
|
|
||||||
this.setState({ errors: err, saveGoogle: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!res) { return; }
|
this.savedBrew = res.body;
|
||||||
|
if(transfer) {
|
||||||
console.log('Deleting Local Copy');
|
history.replaceState(null, null, `/edit/${this.savedBrew.googleId ?? ''}${this.savedBrew.editId}`);
|
||||||
await request.delete(`/api/${brew.editId}`)
|
|
||||||
.send()
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log('Error deleting Local Copy');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.savedBrew = res.body;
|
|
||||||
history.replaceState(null, null, `/edit/${this.savedBrew.googleId}${this.savedBrew.editId}`); //update URL to match doc ID
|
|
||||||
} else {
|
|
||||||
const res = await request
|
|
||||||
.put(`/api/updateGoogle/${brew.editId}`)
|
|
||||||
.send(brew)
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log(err.status === 401
|
|
||||||
? 'Not signed in!'
|
|
||||||
: 'Error Saving to Google!');
|
|
||||||
this.setState({ errors: err });
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.savedBrew = res.body;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(transfer) {
|
|
||||||
const res = await request.post('/api')
|
|
||||||
.send(brew)
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log('Error creating Local Copy');
|
|
||||||
this.setState({ errors: err });
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
await request.get(`/api/removeGoogle/${brew.googleId}${brew.editId}`)
|
|
||||||
.send()
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log('Error Deleting Google Brew');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.savedBrew = res.body;
|
|
||||||
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); //update URL to match doc ID
|
|
||||||
} else {
|
|
||||||
const res = await request
|
|
||||||
.put(`/api/update/${brew.editId}`)
|
|
||||||
.send(brew)
|
|
||||||
.catch((err)=>{
|
|
||||||
console.log('Error Updating Local Brew');
|
|
||||||
this.setState({ errors: err });
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.savedBrew = res.body;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
@@ -331,26 +276,26 @@ const EditPage = createClass({
|
|||||||
console.log(errMsg);
|
console.log(errMsg);
|
||||||
} catch (e){}
|
} catch (e){}
|
||||||
|
|
||||||
if(this.state.errors.status == '401'){
|
// if(this.state.errors.status == '401'){
|
||||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
// Oops!
|
||||||
<div className='errorContainer' onClick={this.clearErrors}>
|
// <div className='errorContainer' onClick={this.clearErrors}>
|
||||||
You must be signed in to a Google account
|
// You must be signed in to a Google account
|
||||||
to save this to<br />Google Drive!<br />
|
// to save this to<br />Google Drive!<br />
|
||||||
<a target='_blank' rel='noopener noreferrer'
|
// <a target='_blank' rel='noopener noreferrer'
|
||||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||||
<div className='confirm'>
|
// <div className='confirm'>
|
||||||
Sign In
|
// Sign In
|
||||||
</div>
|
// </div>
|
||||||
</a>
|
// </a>
|
||||||
<div className='deny'>
|
// <div className='deny'>
|
||||||
Not Now
|
// Not Now
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</Nav.item>;
|
// </Nav.item>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if(this.state.errors.response.req.url.match(/^\/api\/.*Google.*$/m)){
|
if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){
|
||||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
Oops!
|
||||||
<div className='errorContainer' onClick={this.clearErrors}>
|
<div className='errorContainer' onClick={this.clearErrors}>
|
||||||
|
|||||||
@@ -162,45 +162,24 @@ const NewPage = createClass({
|
|||||||
const index = brew.text.indexOf('```\n\n');
|
const index = brew.text.indexOf('```\n\n');
|
||||||
brew.style = `${brew.style ? `${brew.style}\n` : ''}${brew.text.slice(7, index - 1)}`;
|
brew.style = `${brew.style ? `${brew.style}\n` : ''}${brew.text.slice(7, index - 1)}`;
|
||||||
brew.text = brew.text.slice(index + 5);
|
brew.text = brew.text.slice(index + 5);
|
||||||
};
|
}
|
||||||
|
|
||||||
brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
|
brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
|
||||||
|
|
||||||
if(this.state.saveGoogle) {
|
const res = await request
|
||||||
const res = await request
|
.post(`/api${this.state.saveGoogle ? '?transferToGoogle=true' : ''}`)
|
||||||
.post('/api/newGoogle/')
|
|
||||||
.send(brew)
|
.send(brew)
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log(err.status === 401
|
console.log(err);
|
||||||
? 'Not signed in!'
|
|
||||||
: 'Error Creating New Google Brew!');
|
|
||||||
this.setState({ isSaving: false, errors: err });
|
this.setState({ isSaving: false, errors: err });
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
if(!res) return;
|
||||||
|
|
||||||
brew = res.body;
|
brew = res.body;
|
||||||
localStorage.removeItem(BREWKEY);
|
localStorage.removeItem(BREWKEY);
|
||||||
localStorage.removeItem(STYLEKEY);
|
localStorage.removeItem(STYLEKEY);
|
||||||
localStorage.removeItem(METAKEY);
|
localStorage.removeItem(METAKEY);
|
||||||
window.location = `/edit/${brew.googleId}${brew.editId}`;
|
window.location = `/edit/${brew.googleId ?? ''}${brew.editId}`;
|
||||||
} else {
|
|
||||||
request.post('/api')
|
|
||||||
.send(brew)
|
|
||||||
.end((err, res)=>{
|
|
||||||
if(err){
|
|
||||||
this.setState({
|
|
||||||
isSaving : false
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.onbeforeunload = function(){};
|
|
||||||
brew = res.body;
|
|
||||||
localStorage.removeItem(BREWKEY);
|
|
||||||
localStorage.removeItem(STYLEKEY);
|
|
||||||
localStorage.removeItem(METAKEY);
|
|
||||||
window.location = `/edit/${brew.editId}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderSaveButton : function(){
|
renderSaveButton : function(){
|
||||||
@@ -213,26 +192,26 @@ const NewPage = createClass({
|
|||||||
console.log(errMsg);
|
console.log(errMsg);
|
||||||
} catch (e){}
|
} catch (e){}
|
||||||
|
|
||||||
if(this.state.errors.status == '401'){
|
// if(this.state.errors.status == '401'){
|
||||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
// Oops!
|
||||||
<div className='errorContainer' onClick={this.clearErrors}>
|
// <div className='errorContainer' onClick={this.clearErrors}>
|
||||||
You must be signed in to a Google account
|
// You must be signed in to a Google account
|
||||||
to save this to<br />Google Drive!<br />
|
// to save this to<br />Google Drive!<br />
|
||||||
<a target='_blank' rel='noopener noreferrer'
|
// <a target='_blank' rel='noopener noreferrer'
|
||||||
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||||
<div className='confirm'>
|
// <div className='confirm'>
|
||||||
Sign In
|
// Sign In
|
||||||
</div>
|
// </div>
|
||||||
</a>
|
// </a>
|
||||||
<div className='deny'>
|
// <div className='deny'>
|
||||||
Not Now
|
// Not Now
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</Nav.item>;
|
// </Nav.item>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if(this.state.errors.response.req.url.match(/^\/api\/.*Google.*$/m)){
|
if(this.state.errors.response.req.url.match(/^\/api.*Google.*$/m)){
|
||||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
Oops!
|
||||||
<div className='errorContainer' onClick={this.clearErrors}>
|
<div className='errorContainer' onClick={this.clearErrors}>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const UserPage = createClass({
|
|||||||
return {
|
return {
|
||||||
username : '',
|
username : '',
|
||||||
brews : [],
|
brews : [],
|
||||||
|
query : ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
@@ -62,7 +63,7 @@ const UserPage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()}></ListPage>;
|
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query}></ListPage>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
744
package-lock.json
generated
744
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "3.0.7",
|
"version": "3.0.8",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.11.x"
|
"node": "16.11.x"
|
||||||
},
|
},
|
||||||
@@ -20,6 +20,9 @@
|
|||||||
"verify": "npm run lint && npm test",
|
"verify": "npm run lint && npm test",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:dev": "jest --verbose --watch",
|
"test:dev": "jest --verbose --watch",
|
||||||
|
"test:basic": "jest tests/markdown/basic.test.js --verbose",
|
||||||
|
"test:mustache-span": "jest tests/markdown/mustache-span.test.js --verbose",
|
||||||
|
"test:route": "jest tests/routes/static-pages.test.js --verbose",
|
||||||
"phb": "node scripts/phb.js",
|
"phb": "node scripts/phb.js",
|
||||||
"prod": "set NODE_ENV=production && npm run build",
|
"prod": "set NODE_ENV=production && npm run build",
|
||||||
"postinstall": "npm run buildall",
|
"postinstall": "npm run buildall",
|
||||||
@@ -31,6 +34,7 @@
|
|||||||
"build/*"
|
"build/*"
|
||||||
],
|
],
|
||||||
"jest": {
|
"jest": {
|
||||||
|
"testTimeout" : 15000,
|
||||||
"modulePaths": [
|
"modulePaths": [
|
||||||
"mode_modules",
|
"mode_modules",
|
||||||
"shared",
|
"shared",
|
||||||
@@ -47,21 +51,21 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.17.2",
|
"@babel/core": "^7.17.8",
|
||||||
"@babel/plugin-transform-runtime": "^7.17.0",
|
"@babel/plugin-transform-runtime": "^7.17.0",
|
||||||
"@babel/preset-env": "^7.16.11",
|
"@babel/preset-env": "^7.16.11",
|
||||||
"@babel/preset-react": "^7.16.7",
|
"@babel/preset-react": "^7.16.7",
|
||||||
"body-parser": "^1.19.1",
|
"body-parser": "^1.19.2",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"codemirror": "^5.65.1",
|
"codemirror": "^5.65.2",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"create-react-class": "^15.7.0",
|
"create-react-class": "^15.7.0",
|
||||||
"dedent-tabs": "^0.10.1",
|
"dedent-tabs": "^0.10.1",
|
||||||
"express": "^4.17.2",
|
"express": "^4.17.3",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "2.1.3",
|
"express-static-gzip": "2.1.5",
|
||||||
"fs-extra": "10.0.0",
|
"fs-extra": "10.0.1",
|
||||||
"googleapis": "95.0.0",
|
"googleapis": "100.0.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
"less": "^3.13.1",
|
"less": "^3.13.1",
|
||||||
@@ -70,8 +74,8 @@
|
|||||||
"marked-extended-tables": "^1.0.3",
|
"marked-extended-tables": "^1.0.3",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"mongoose": "^6.2.1",
|
"mongoose": "^6.2.9",
|
||||||
"nanoid": "3.2.0",
|
"nanoid": "3.3.2",
|
||||||
"nconf": "^0.11.3",
|
"nconf": "^0.11.3",
|
||||||
"query-string": "7.1.1",
|
"query-string": "7.1.1",
|
||||||
"react": "^16.14.0",
|
"react": "^16.14.0",
|
||||||
@@ -83,8 +87,8 @@
|
|||||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.8.0",
|
"eslint": "^8.12.0",
|
||||||
"eslint-plugin-react": "^7.28.0",
|
"eslint-plugin-react": "^7.29.4",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"supertest": "^6.2.2"
|
"supertest": "^6.2.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const getBrewFromId = asyncHandler(async (id, accessType)=>{
|
|||||||
if(id.length > 12) {
|
if(id.length > 12) {
|
||||||
const googleId = id.slice(0, -12);
|
const googleId = id.slice(0, -12);
|
||||||
id = id.slice(-12);
|
id = id.slice(-12);
|
||||||
brew = await GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, id, accessType);
|
brew = await GoogleActions.getGoogleBrew(googleId, id, accessType);
|
||||||
} else {
|
} else {
|
||||||
brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id });
|
brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id });
|
||||||
brew = brew.toObject(); // Convert MongoDB object to standard Javascript Object
|
brew = brew.toObject(); // Convert MongoDB object to standard Javascript Object
|
||||||
@@ -201,13 +201,16 @@ app.get('/user/:username', async (req, res, next)=>{
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(ownAccount && req?.account?.googleId){
|
if(ownAccount && req?.account?.googleId){
|
||||||
const googleBrews = await GoogleActions.listGoogleBrews(req, res)
|
const auth = await GoogleActions.authCheck(req.account, res);
|
||||||
.catch((err)=>{
|
let googleBrews = await GoogleActions.listGoogleBrews(auth)
|
||||||
console.error(err);
|
.catch((err)=>{
|
||||||
});
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
if(googleBrews)
|
if(googleBrews) {
|
||||||
|
googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] }));
|
||||||
brews = _.concat(brews, googleBrews);
|
brews = _.concat(brews, googleBrews);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req.brews = _.map(brews, (brew)=>{
|
req.brews = _.map(brews, (brew)=>{
|
||||||
|
|||||||
@@ -5,7 +5,20 @@ const { nanoid } = require('nanoid');
|
|||||||
const token = require('./token.js');
|
const token = require('./token.js');
|
||||||
const config = require('./config.js');
|
const config = require('./config.js');
|
||||||
|
|
||||||
//let oAuth2Client;
|
const keys = typeof(config.get('service_account')) == 'string' ?
|
||||||
|
JSON.parse(config.get('service_account')) :
|
||||||
|
config.get('service_account');
|
||||||
|
let serviceAuth;
|
||||||
|
try {
|
||||||
|
serviceAuth = google.auth.fromJSON(keys);
|
||||||
|
serviceAuth.scopes = [
|
||||||
|
'https://www.googleapis.com/auth/drive'
|
||||||
|
];
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
console.log('Please make sure that a Google Service Account is set up properly in your config files.');
|
||||||
|
}
|
||||||
|
google.options({ auth: serviceAuth || config.get('google_api_key') });
|
||||||
|
|
||||||
const GoogleActions = {
|
const GoogleActions = {
|
||||||
|
|
||||||
@@ -43,7 +56,7 @@ const GoogleActions = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getGoogleFolder : async (auth)=>{
|
getGoogleFolder : async (auth)=>{
|
||||||
const drive = google.drive({ version: 'v3', auth: auth });
|
const drive = google.drive({ version: 'v3', auth });
|
||||||
|
|
||||||
fileMetadata = {
|
fileMetadata = {
|
||||||
'name' : 'Homebrewery',
|
'name' : 'Homebrewery',
|
||||||
@@ -79,17 +92,8 @@ const GoogleActions = {
|
|||||||
return folderId;
|
return folderId;
|
||||||
},
|
},
|
||||||
|
|
||||||
listGoogleBrews : async (req, res)=>{
|
listGoogleBrews : async (auth)=>{
|
||||||
|
const drive = google.drive({ version: 'v3', auth });
|
||||||
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({
|
const obj = await drive.files.list({
|
||||||
pageSize : 1000,
|
pageSize : 1000,
|
||||||
@@ -97,18 +101,18 @@ const GoogleActions = {
|
|||||||
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
|
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
|
||||||
})
|
})
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log(`Error Listing Google Brews`);
|
console.log(`Error Listing Google Brews`);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
throw (err);
|
throw (err);
|
||||||
//TODO: Should break out here, but continues on for some reason.
|
//TODO: Should break out here, but continues on for some reason.
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!obj.data.files.length) {
|
if(!obj.data.files.length) {
|
||||||
console.log('No files found.');
|
console.log('No files found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const brews = obj.data.files.map((file)=>{
|
const brews = obj.data.files.map((file)=>{
|
||||||
return {
|
return {
|
||||||
text : '',
|
text : '',
|
||||||
shareId : file.properties.shareId,
|
shareId : file.properties.shareId,
|
||||||
editId : file.properties.editId,
|
editId : file.properties.editId,
|
||||||
@@ -122,65 +126,47 @@ const GoogleActions = {
|
|||||||
views : parseInt(file.properties.views),
|
views : parseInt(file.properties.views),
|
||||||
tags : '',
|
tags : '',
|
||||||
published : file.properties.published ? file.properties.published == 'true' : false,
|
published : file.properties.published ? file.properties.published == 'true' : false,
|
||||||
authors : [req.account.username], //TODO: properly save and load authors to google drive
|
|
||||||
systems : []
|
systems : []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return brews;
|
return brews;
|
||||||
},
|
},
|
||||||
|
|
||||||
existsGoogleBrew : async (auth, id)=>{
|
updateGoogleBrew : async (brew)=>{
|
||||||
const drive = google.drive({ version: 'v3', auth: auth });
|
const drive = google.drive({ version: 'v3' });
|
||||||
|
|
||||||
const result = await drive.files.get({ fileId: id })
|
await drive.files.update({
|
||||||
.catch((err)=>{
|
fileId : brew.googleId,
|
||||||
console.log('error checking file exists...');
|
resource : {
|
||||||
console.error(err);
|
name : `${brew.title}.txt`,
|
||||||
return false;
|
description : `${brew.description}`,
|
||||||
});
|
properties : {
|
||||||
|
title : brew.title,
|
||||||
if(result){return true;}
|
published : brew.published,
|
||||||
|
version : brew.version,
|
||||||
return false;
|
renderer : brew.renderer,
|
||||||
},
|
tags : brew.tags,
|
||||||
|
pageCount : brew.pageCount,
|
||||||
updateGoogleBrew : async (auth, brew)=>{
|
systems : brew.systems.join()
|
||||||
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`,
|
|
||||||
description : `${brew.description}`,
|
|
||||||
properties : {
|
|
||||||
title : brew.title,
|
|
||||||
published : brew.published,
|
|
||||||
version : brew.version,
|
|
||||||
renderer : brew.renderer,
|
|
||||||
tags : brew.tags,
|
|
||||||
pageCount : brew.pageCount,
|
|
||||||
systems : brew.systems.join()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
media : {
|
|
||||||
mimeType : 'text/plain',
|
|
||||||
body : brew.text
|
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
.catch((err)=>{
|
media : {
|
||||||
console.log('Error saving to google');
|
mimeType : 'text/plain',
|
||||||
console.error(err);
|
body : brew.text
|
||||||
throw (err);
|
}
|
||||||
//return res.status(500).send('Error while saving');
|
})
|
||||||
});
|
.catch((err)=>{
|
||||||
}
|
console.log('Error saving to google');
|
||||||
|
console.error(err);
|
||||||
|
throw (err);
|
||||||
|
//return res.status(500).send('Error while saving');
|
||||||
|
});
|
||||||
|
|
||||||
return (brew);
|
return (brew);
|
||||||
},
|
},
|
||||||
|
|
||||||
newGoogleBrew : async (auth, brew)=>{
|
newGoogleBrew : async (auth, brew)=>{
|
||||||
const drive = google.drive({ version: 'v3', auth: auth });
|
const drive = google.drive({ version: 'v3', auth });
|
||||||
|
|
||||||
const media = {
|
const media = {
|
||||||
mimeType : 'text/plain',
|
mimeType : 'text/plain',
|
||||||
@@ -194,8 +180,8 @@ const GoogleActions = {
|
|||||||
'description' : `${brew.description}`,
|
'description' : `${brew.description}`,
|
||||||
'parents' : [folderId],
|
'parents' : [folderId],
|
||||||
'properties' : { //AppProperties is not accessible
|
'properties' : { //AppProperties is not accessible
|
||||||
'shareId' : nanoid(12),
|
'shareId' : brew.shareId || nanoid(12),
|
||||||
'editId' : nanoid(12),
|
'editId' : brew.editId || nanoid(12),
|
||||||
'title' : brew.title,
|
'title' : brew.title,
|
||||||
'views' : '0',
|
'views' : '0',
|
||||||
'pageCount' : brew.pageCount,
|
'pageCount' : brew.pageCount,
|
||||||
@@ -248,9 +234,8 @@ const GoogleActions = {
|
|||||||
return newHomebrew;
|
return newHomebrew;
|
||||||
},
|
},
|
||||||
|
|
||||||
readFileMetadata : async (auth, id, accessId, accessType)=>{
|
getGoogleBrew : async (id, accessId, accessType)=>{
|
||||||
|
const drive = google.drive({ version: 'v3' });
|
||||||
const drive = google.drive({ version: 'v3', auth: auth });
|
|
||||||
|
|
||||||
const obj = await drive.files.get({
|
const obj = await drive.files.get({
|
||||||
fileId : id,
|
fileId : id,
|
||||||
@@ -269,16 +254,7 @@ const GoogleActions = {
|
|||||||
throw ('Share ID does not match');
|
throw ('Share ID does not match');
|
||||||
}
|
}
|
||||||
|
|
||||||
//Access file using service account. Using API key only causes "automated query" lockouts after a while.
|
const serviceDrive = google.drive({ version: 'v3' });
|
||||||
|
|
||||||
const keys = typeof(config.get('service_account')) == 'string' ?
|
|
||||||
JSON.parse(config.get('service_account')) :
|
|
||||||
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({
|
const file = await serviceDrive.files.get({
|
||||||
fileId : id,
|
fileId : id,
|
||||||
@@ -319,10 +295,8 @@ const GoogleActions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteGoogleBrew : async (req, res, id)=>{
|
deleteGoogleBrew : async (auth, id)=>{
|
||||||
|
const drive = google.drive({ version: 'v3', auth });
|
||||||
oAuth2Client = GoogleActions.authCheck(req.account, res);
|
|
||||||
const drive = google.drive({ version: 'v3', auth: oAuth2Client });
|
|
||||||
|
|
||||||
const googleId = id.slice(0, -12);
|
const googleId = id.slice(0, -12);
|
||||||
const accessId = id.slice(-12);
|
const accessId = id.slice(-12);
|
||||||
@@ -334,7 +308,6 @@ const GoogleActions = {
|
|||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log('Error loading from Google');
|
console.log('Error loading from Google');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if(obj && obj.data.properties.editId != accessId) {
|
if(obj && obj.data.properties.editId != accessId) {
|
||||||
@@ -349,21 +322,10 @@ const GoogleActions = {
|
|||||||
console.log('Can\'t delete Google file');
|
console.log('Can\'t delete Google file');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
increaseView : async (id, accessId, accessType, brew)=>{
|
increaseView : async (id, accessId, accessType, brew)=>{
|
||||||
//service account because this is modifying another user's file properties
|
const drive = google.drive({ version: 'v3' });
|
||||||
//so we need extended scope
|
|
||||||
const keys = typeof(config.get('service_account')) == 'string' ?
|
|
||||||
JSON.parse(config.get('service_account')) :
|
|
||||||
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({
|
await drive.files.update({
|
||||||
fileId : brew.googleId,
|
fileId : brew.googleId,
|
||||||
@@ -380,8 +342,6 @@ const GoogleActions = {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
//return res.status(500).send('Error while saving');
|
//return res.status(500).send('Error while saving');
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const zlib = require('zlib');
|
|||||||
const GoogleActions = require('./googleActions.js');
|
const GoogleActions = require('./googleActions.js');
|
||||||
const Markdown = require('../shared/naturalcrit/markdown.js');
|
const Markdown = require('../shared/naturalcrit/markdown.js');
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
|
const asyncHandler = require('express-async-handler');
|
||||||
|
|
||||||
// const getTopBrews = (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) {
|
||||||
@@ -41,154 +42,195 @@ const excludePropsFromUpdate = (brew)=>{
|
|||||||
const propsToExclude = ['views', 'lastViewed'];
|
const propsToExclude = ['views', 'lastViewed'];
|
||||||
for (const prop of propsToExclude) {
|
for (const prop of propsToExclude) {
|
||||||
delete brew[prop];
|
delete brew[prop];
|
||||||
};
|
}
|
||||||
return brew;
|
return brew;
|
||||||
};
|
};
|
||||||
|
|
||||||
const newBrew = (req, res)=>{
|
const beforeNewSave = (account, brew)=>{
|
||||||
const brew = req.body;
|
|
||||||
|
|
||||||
if(!brew.title) {
|
if(!brew.title) {
|
||||||
brew.title = getGoodBrewTitle(brew.text);
|
brew.title = getGoodBrewTitle(brew.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
brew.authors = (req.account) ? [req.account.username] : [];
|
brew.authors = (account) ? [account.username] : [];
|
||||||
brew.text = mergeBrewText(brew);
|
brew.text = mergeBrewText(brew);
|
||||||
|
};
|
||||||
|
|
||||||
delete brew.editId;
|
const newLocalBrew = async (brew)=>{
|
||||||
delete brew.shareId;
|
|
||||||
delete brew.googleId;
|
|
||||||
|
|
||||||
const newHomebrew = new HomebrewModel(brew);
|
const newHomebrew = new HomebrewModel(brew);
|
||||||
// Compress brew text to binary before saving
|
// Compress brew text to binary before saving
|
||||||
newHomebrew.textBin = zlib.deflateRawSync(newHomebrew.text);
|
newHomebrew.textBin = zlib.deflateRawSync(newHomebrew.text);
|
||||||
// Delete the non-binary text field since it's not needed anymore
|
// Delete the non-binary text field since it's not needed anymore
|
||||||
newHomebrew.text = undefined;
|
newHomebrew.text = undefined;
|
||||||
|
|
||||||
newHomebrew.save((err, obj)=>{
|
let saved = await newHomebrew.save()
|
||||||
if(err) {
|
|
||||||
console.error(err, err.toString(), err.stack);
|
|
||||||
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
obj = obj.toObject();
|
|
||||||
obj.gDrive = false;
|
|
||||||
return res.status(200).send(obj);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateBrew = (req, res)=>{
|
|
||||||
HomebrewModel.get({ editId: req.params.id })
|
|
||||||
.then((brew)=>{
|
|
||||||
const updateBrew = excludePropsFromUpdate(req.body);
|
|
||||||
brew = _.merge(brew, updateBrew);
|
|
||||||
brew.text = mergeBrewText(brew);
|
|
||||||
|
|
||||||
// Compress brew text to binary before saving
|
|
||||||
brew.textBin = zlib.deflateRawSync(brew.text);
|
|
||||||
// Delete the non-binary text field since it's not needed anymore
|
|
||||||
brew.text = undefined;
|
|
||||||
brew.updatedAt = new Date();
|
|
||||||
|
|
||||||
if(req.account) {
|
|
||||||
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
|
||||||
}
|
|
||||||
|
|
||||||
brew.markModified('authors');
|
|
||||||
brew.markModified('systems');
|
|
||||||
|
|
||||||
brew.save((err, obj)=>{
|
|
||||||
if(err) throw err;
|
|
||||||
return res.status(200).send(obj);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.error(err);
|
console.error(err, err.toString(), err.stack);
|
||||||
return res.status(500).send('Error while saving');
|
throw `Error while creating new brew, ${err.toString()}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
saved = saved.toObject();
|
||||||
|
saved.gDrive = false;
|
||||||
|
return saved;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteBrew = (req, res)=>{
|
const newGoogleBrew = async (account, brew, res)=>{
|
||||||
HomebrewModel.find({ editId: req.params.id }, (err, objs)=>{
|
const oAuth2Client = GoogleActions.authCheck(account, res);
|
||||||
if(!objs.length || err) {
|
|
||||||
return res.status(404).send('Can not find homebrew with that id');
|
|
||||||
}
|
|
||||||
|
|
||||||
const brew = objs[0];
|
return await GoogleActions.newGoogleBrew(oAuth2Client, brew);
|
||||||
|
|
||||||
if(req.account) {
|
|
||||||
// Remove current user as author
|
|
||||||
brew.authors = _.pull(brew.authors, req.account.username);
|
|
||||||
brew.markModified('authors');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(brew.authors.length === 0) {
|
|
||||||
// Delete brew if there are no authors left
|
|
||||||
brew.remove((err)=>{
|
|
||||||
if(err) return res.status(500).send('Error while removing');
|
|
||||||
return res.status(200).send();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Otherwise, save the brew with updated author list
|
|
||||||
brew.save((err, savedBrew)=>{
|
|
||||||
if(err) throw err;
|
|
||||||
return res.status(200).send(savedBrew);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const newGoogleBrew = async (req, res, next)=>{
|
const newBrew = async (req, res)=>{
|
||||||
let oAuth2Client;
|
|
||||||
|
|
||||||
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
|
|
||||||
|
|
||||||
const brew = req.body;
|
const brew = req.body;
|
||||||
|
const { transferToGoogle } = req.query;
|
||||||
if(!brew.title) {
|
|
||||||
brew.title = getGoodBrewTitle(brew.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
brew.authors = (req.account) ? [req.account.username] : [];
|
|
||||||
brew.text = mergeBrewText(brew);
|
|
||||||
|
|
||||||
delete brew.editId;
|
delete brew.editId;
|
||||||
delete brew.shareId;
|
delete brew.shareId;
|
||||||
delete brew.googleId;
|
delete brew.googleId;
|
||||||
|
|
||||||
req.body = brew;
|
beforeNewSave(req.account, brew);
|
||||||
|
|
||||||
try {
|
let saved;
|
||||||
const newBrew = await GoogleActions.newGoogleBrew(oAuth2Client, brew);
|
if(transferToGoogle) {
|
||||||
return res.status(200).send(newBrew);
|
saved = await newGoogleBrew(req.account, brew, res)
|
||||||
} catch (err) {
|
.catch((err)=>{
|
||||||
return res.status(err.response.status).send(err);
|
res.status(err.status || err.response.status).send(err.message || err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
saved = await newLocalBrew(brew)
|
||||||
|
.catch((err)=>{
|
||||||
|
res.status(500).send(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(!saved) return;
|
||||||
|
return res.status(200).send(saved);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBrew = async (req, res)=>{
|
||||||
|
let brew = excludePropsFromUpdate(req.body);
|
||||||
|
const { transferToGoogle, transferFromGoogle } = req.query;
|
||||||
|
|
||||||
|
let saved;
|
||||||
|
if(brew.googleId && transferFromGoogle) {
|
||||||
|
beforeNewSave(req.account, brew);
|
||||||
|
|
||||||
|
saved = await newLocalBrew(brew)
|
||||||
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).send(err);
|
||||||
|
});
|
||||||
|
if(!saved) return;
|
||||||
|
|
||||||
|
await deleteGoogleBrew(req.account, `${brew.googleId}${brew.editId}`, res)
|
||||||
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
res.status(err.status || err.response.status).send(err.message || err);
|
||||||
|
});
|
||||||
|
} else if(!brew.googleId && transferToGoogle) {
|
||||||
|
saved = await newGoogleBrew(req.account, brew, res)
|
||||||
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
res.status(err.status || err.response.status).send(err.message || err);
|
||||||
|
});
|
||||||
|
if(!saved) return;
|
||||||
|
|
||||||
|
await deleteLocalBrew(req.account, brew.editId)
|
||||||
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
res.status(err.status).send(err.message);
|
||||||
|
});
|
||||||
|
} else if(brew.googleId) {
|
||||||
|
brew.text = mergeBrewText(brew);
|
||||||
|
|
||||||
|
saved = await GoogleActions.updateGoogleBrew(brew)
|
||||||
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
res.status(err.response?.status || 500).send(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const dbBrew = await HomebrewModel.get({ editId: req.params.id })
|
||||||
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
return res.status(500).send('Error while saving');
|
||||||
|
});
|
||||||
|
|
||||||
|
brew = _.merge(dbBrew, brew);
|
||||||
|
brew.text = mergeBrewText(brew);
|
||||||
|
|
||||||
|
// Compress brew text to binary before saving
|
||||||
|
brew.textBin = zlib.deflateRawSync(brew.text);
|
||||||
|
// Delete the non-binary text field since it's not needed anymore
|
||||||
|
brew.text = undefined;
|
||||||
|
brew.updatedAt = new Date();
|
||||||
|
|
||||||
|
if(req.account) {
|
||||||
|
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
||||||
|
}
|
||||||
|
|
||||||
|
brew.markModified('authors');
|
||||||
|
brew.markModified('systems');
|
||||||
|
|
||||||
|
saved = await brew.save();
|
||||||
|
}
|
||||||
|
if(!saved) return;
|
||||||
|
|
||||||
|
if(!res.headersSent) return res.status(200).send(saved);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteBrew = async (req, res)=>{
|
||||||
|
if(req.params.id.length > 12) {
|
||||||
|
const deleted = await deleteGoogleBrew(req.account, req.params.id, res)
|
||||||
|
.catch((err)=>{
|
||||||
|
res.status(500).send(err);
|
||||||
|
});
|
||||||
|
if(deleted) return res.status(200).send();
|
||||||
|
} else {
|
||||||
|
const deleted = await deleteLocalBrew(req.account, req.params.id)
|
||||||
|
.catch((err)=>{
|
||||||
|
res.status(err.status).send(err.message);
|
||||||
|
});
|
||||||
|
if(deleted) return res.status(200).send(deleted);
|
||||||
|
return res.status(200).send();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateGoogleBrew = async (req, res, next)=>{
|
const deleteLocalBrew = async (account, id)=>{
|
||||||
let oAuth2Client;
|
const brew = await HomebrewModel.findOne({ editId: id });
|
||||||
|
if(!brew) {
|
||||||
|
throw { status: 404, message: 'Can not find homebrew with that id' };
|
||||||
|
}
|
||||||
|
|
||||||
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
|
if(account) {
|
||||||
|
// Remove current user as author
|
||||||
|
brew.authors = _.pull(brew.authors, account.username);
|
||||||
|
brew.markModified('authors');
|
||||||
|
}
|
||||||
|
|
||||||
const brew = excludePropsFromUpdate(req.body);
|
if(brew.authors.length === 0) {
|
||||||
brew.text = mergeBrewText(brew);
|
// Delete brew if there are no authors left
|
||||||
|
await brew.remove()
|
||||||
try {
|
.catch((err)=>{
|
||||||
const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, brew);
|
console.error(err);
|
||||||
return res.status(200).send(updatedBrew);
|
throw { status: 500, message: 'Error while removing' };
|
||||||
} catch (err) {
|
});
|
||||||
return res.status(err.response?.status || 500).send(err);
|
} else {
|
||||||
|
// Otherwise, save the brew with updated author list
|
||||||
|
return await brew.save()
|
||||||
|
.catch((err)=>{
|
||||||
|
throw { status: 500, message: err };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
router.post('/api', newBrew);
|
const deleteGoogleBrew = async (account, id, res)=>{
|
||||||
router.post('/api/newGoogle/', newGoogleBrew);
|
const auth = await GoogleActions.authCheck(account, res);
|
||||||
router.put('/api/:id', updateBrew);
|
await GoogleActions.deleteGoogleBrew(auth, id);
|
||||||
router.put('/api/update/:id', updateBrew);
|
return true;
|
||||||
router.put('/api/updateGoogle/:id', updateGoogleBrew);
|
};
|
||||||
router.delete('/api/:id', deleteBrew);
|
|
||||||
router.get('/api/remove/:id', deleteBrew);
|
router.post('/api', asyncHandler(newBrew));
|
||||||
router.get('/api/removeGoogle/:id', (req, res)=>{GoogleActions.deleteGoogleBrew(req, res, req.params.id);});
|
router.put('/api/:id', asyncHandler(updateBrew));
|
||||||
|
router.put('/api/update/:id', asyncHandler(updateBrew));
|
||||||
|
router.delete('/api/:id', asyncHandler(deleteBrew));
|
||||||
|
router.get('/api/remove/:id', asyncHandler(deleteBrew));
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -10,43 +10,69 @@ const SplitPane = createClass({
|
|||||||
return {
|
return {
|
||||||
storageKey : 'naturalcrit-pane-split',
|
storageKey : 'naturalcrit-pane-split',
|
||||||
onDragFinish : function(){} //fires when dragging
|
onDragFinish : function(){} //fires when dragging
|
||||||
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
size : null,
|
currentDividerPos : null,
|
||||||
isDragging : false
|
windowWidth : 0,
|
||||||
|
isDragging : false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount : function() {
|
componentDidMount : function() {
|
||||||
const paneSize = window.localStorage.getItem(this.props.storageKey);
|
const dividerPos = window.localStorage.getItem(this.props.storageKey);
|
||||||
if(paneSize){
|
if(dividerPos){
|
||||||
this.setState({
|
this.setState({
|
||||||
size : paneSize
|
currentDividerPos : this.limitPosition(dividerPos, 0.1*(window.innerWidth-13), 0.9*(window.innerWidth-13)),
|
||||||
|
userSetDividerPos : dividerPos,
|
||||||
|
windowWidth : window.innerWidth
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
window.addEventListener('resize', this.handleWindowResize);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount : function() {
|
||||||
|
window.removeEventListener('resize', this.handleWindowResize);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleWindowResize : function() {
|
||||||
|
// Allow divider to increase in size to last user-set position
|
||||||
|
// Limit current position to between 10% and 90% of visible space
|
||||||
|
const newLoc = this.limitPosition(this.state.userSetDividerPos, 0.1*(window.innerWidth-13), 0.9*(window.innerWidth-13));
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
currentDividerPos : newLoc,
|
||||||
|
windowWidth : window.innerWidth
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
limitPosition : function(x, min = 1, max = window.innerWidth - 13) {
|
||||||
|
const result = Math.round(Math.min(max, Math.max(min, x)));
|
||||||
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleUp : function(){
|
handleUp : function(){
|
||||||
if(this.state.isDragging){
|
if(this.state.isDragging){
|
||||||
this.props.onDragFinish(this.state.size);
|
this.props.onDragFinish(this.state.currentDividerPos);
|
||||||
window.localStorage.setItem(this.props.storageKey, this.state.size);
|
window.localStorage.setItem(this.props.storageKey, this.state.currentDividerPos);
|
||||||
}
|
}
|
||||||
this.setState({ isDragging: false });
|
this.setState({ isDragging: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
handleDown : function(){
|
handleDown : function(){
|
||||||
this.setState({ isDragging: true });
|
this.setState({ isDragging: true });
|
||||||
//this.unFocus()
|
//this.unFocus()
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMove : function(e){
|
handleMove : function(e){
|
||||||
if(!this.state.isDragging) return;
|
if(!this.state.isDragging) return;
|
||||||
|
|
||||||
const minWidth = 1;
|
const newSize = this.limitPosition(e.pageX);
|
||||||
const maxWidth = window.innerWidth - 13;
|
|
||||||
const newSize = Math.min(maxWidth, Math.max(minWidth, e.pageX));
|
|
||||||
this.setState({
|
this.setState({
|
||||||
size : newSize
|
currentDividerPos : newSize,
|
||||||
|
userSetDividerPos : newSize
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
@@ -70,7 +96,7 @@ const SplitPane = createClass({
|
|||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
||||||
<Pane ref='pane1' width={this.state.size}>{this.props.children[0]}</Pane>
|
<Pane ref='pane1' width={this.state.currentDividerPos}>{this.props.children[0]}</Pane>
|
||||||
{this.renderDivider()}
|
{this.renderDivider()}
|
||||||
<Pane ref='pane2' isDragging={this.state.isDragging}>{this.props.children[1]}</Pane>
|
<Pane ref='pane2' isDragging={this.state.isDragging}>{this.props.children[1]}</Pane>
|
||||||
</div>;
|
</div>;
|
||||||
|
|||||||
@@ -98,7 +98,11 @@ module.exports = [
|
|||||||
gen : dedent`/* Removes Drop Caps */
|
gen : dedent`/* Removes Drop Caps */
|
||||||
.page h1+p:first-letter {
|
.page h1+p:first-letter {
|
||||||
all: unset;
|
all: unset;
|
||||||
}\n\n`
|
}\n\n
|
||||||
|
/* Removes Small-Caps in first line */
|
||||||
|
.page h1+p:first-line {
|
||||||
|
all: unset;
|
||||||
|
}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Tweak Drop Cap',
|
name : 'Tweak Drop Cap',
|
||||||
|
|||||||
@@ -621,6 +621,8 @@ body {
|
|||||||
}
|
}
|
||||||
&.decoration {
|
&.decoration {
|
||||||
transform-style : preserve-3d;
|
transform-style : preserve-3d;
|
||||||
|
z-index: -1;
|
||||||
|
position:relative;
|
||||||
}
|
}
|
||||||
&.decoration::before {
|
&.decoration::before {
|
||||||
content :'';
|
content :'';
|
||||||
@@ -656,7 +658,7 @@ body {
|
|||||||
margin-bottom : 0.3cm;
|
margin-bottom : 0.3cm;
|
||||||
}
|
}
|
||||||
a{
|
a{
|
||||||
display : table;
|
display : inline;
|
||||||
color : inherit;
|
color : inherit;
|
||||||
text-decoration : none;
|
text-decoration : none;
|
||||||
&:hover{
|
&:hover{
|
||||||
|
|||||||
Reference in New Issue
Block a user