0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-24 20:42:43 +00:00

Merge branch 'master' into addMetadata-#820

This commit is contained in:
G.Ambatte
2022-04-13 09:13:37 +12:00
committed by GitHub
20 changed files with 921 additions and 613 deletions

View File

@@ -2,17 +2,23 @@
#
# 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:
build:
docker:
- image: circleci/node:16.10.0
- image: circleci/mongo:4.4
- image: cimg/node:16.11.0
- image: mongo:4.4
working_directory: ~/repo
working_directory: ~/homebrewery
executor: node/default
steps:
- checkout
- checkout:
path: ~/homebrewery
# Download and cache dependencies
- restore_cache:
@@ -21,12 +27,48 @@ jobs:
# fallback to using the latest cache if no exact match is found
- 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:
paths:
- node_modules
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: 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

View File

@@ -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
## 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
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
below.
[homebrewery-url]: https://homebrewery.naturalcrit.com
### 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:
1. install [node](https://nodejs.org/en/)
1. install [mongodb](https://www.mongodb.com/try/download/community) (Community version)
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)
For the 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" 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).
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:
* Windows Powershell: `$env: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
it using the two commands:
@@ -63,7 +63,7 @@ it using the two commands:
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.
in your browser and use The Homebrewery offline.
### Running the application via Docker
@@ -95,11 +95,11 @@ You can check out the [changelog](./changelog.md).
## 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
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
images or resources used.
@@ -108,13 +108,12 @@ images or resources used.
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`.
of them are outdated, duplicates, or don't contain any useful info. To help, you can [mark duplicates][github-mark-duplicate-url], try to
reproduce some complex or weird issues, try finding a workaround for a
reported bug, or just mention our issue managers team to let them know about
outdated issues 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.
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]!

View File

@@ -29,11 +29,43 @@ pre {
.page p + pre {
margin-top : 0.1cm;
}
.page .openSans {
font-family: 'Open Sans';
font-size: 0.9em;
}
```
## changelog
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
{{taskList
* [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)
* [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.
}}
@@ -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)
* [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)

View File

@@ -78,7 +78,7 @@ const MetadataEditor = createClass({
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()
.end(function(err, res){
window.location.href = '/';

View File

@@ -97,7 +97,11 @@ module.exports = [
gen : dedent`/* Removes Drop Caps */
.page h1+p:first-letter {
all: unset;
}\n\n`
}\n\n
/* Removes Small-Caps in first line */
.page h1+p:first-line {
all: unset;
}`
},
{
name : 'Tweak Drop Cap',

View File

@@ -32,11 +32,14 @@ const Homebrew = createClass({
}
};
},
componentWillMount : function() {
global.account = this.props.account;
getInitialState : function(){
global.version = this.props.version;
global.account = this.props.account;
global.enable_v3 = this.props.enable_v3;
return {};
},
render : function (){
return (
<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='/new/:id' component={(routeProps)=><NewPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
<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' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} />}/>
<Route path='/changelog' exact component={()=><SharePage brew={this.props.brew} />}/>

View File

@@ -20,9 +20,20 @@ const Account = createClass({
handleLogout : function(){
if(confirm('Are you sure you want to log out?')) {
document.cookie = `nc_session=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;samesite=lax;${window.domain ? `domain=${window.domain}` : ''}`;
// 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(){

View File

@@ -1,6 +1,7 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const dedent = require('dedent-tabs').default;
const Nav = require('naturalcrit/nav/nav.jsx');
@@ -10,7 +11,11 @@ module.exports = function(props){
need help?
</Nav.item>
<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}
rel='noopener noreferrer'>
report issue

View File

@@ -14,12 +14,10 @@ const Navbar = createClass({
};
},
componentDidMount : function() {
//const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
this.setState({
//showNonChromeWarning : !isChrome,
ver : window.version
});
getInitialState : function() {
return {
ver : global.version
};
},
/*

View File

@@ -31,19 +31,11 @@ const BrewItem = createClass({
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
}
if(this.props.brew.googleId) {
request.get(`/api/removeGoogle/${this.props.brew.googleId}${this.props.brew.editId}`)
.send()
.end(function(err, res){
location.reload();
});
} else {
request.delete(`/api/${this.props.brew.editId}`)
.send()
.end(function(err, res){
location.reload();
});
}
request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`)
.send()
.end(function(err, res){
location.reload();
});
},
renderDeleteBrewLink : function(){

View File

@@ -24,7 +24,8 @@ const ListPage = createClass({
return {
sortType : 'alpha',
sortDir : 'asc',
filterString : ''
filterString : this.props.query?.filter || '',
query : this.props.query
};
},
@@ -74,19 +75,35 @@ const ListPage = createClass({
handleFilterTextChange : function(e){
this.setState({
filterString : e.target.value
filterString : e.target.value,
});
this.updateUrl(e.target.value);
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(){
return <td>
<label>
<i className='fas fa-search'></i>
<input
type='search'
placeholder='search title/description'
autoFocus={true}
placeholder='filter title/description'
onChange={this.handleFilterTextChange}
value={this.state.filterString}
/>
</label>
</td>;

View File

@@ -200,73 +200,18 @@ const EditPage = createClass({
const brew = this.state.brew;
brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
if(this.state.saveGoogle) {
if(transfer) {
const res = await request
.post('/api/newGoogle/')
.send(brew)
.catch((err)=>{
console.log(err.status === 401
? 'Not signed in!'
: 'Error Transferring to Google!');
this.setState({ errors: err, saveGoogle: false });
});
const params = `${transfer ? `?transfer${this.state.saveGoogle ? 'To' : 'From'}Google=true` : ''}`;
const res = await request
.put(`/api/update/${brew.editId}${params}`)
.send(brew)
.catch((err)=>{
console.log('Error Updating Local Brew');
this.setState({ errors: err });
});
if(!res) { return; }
console.log('Deleting Local Copy');
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.savedBrew = res.body;
if(transfer) {
history.replaceState(null, null, `/edit/${this.savedBrew.googleId ?? ''}${this.savedBrew.editId}`);
}
this.setState((prevState)=>({
@@ -331,26 +276,26 @@ const EditPage = createClass({
console.log(errMsg);
} catch (e){}
if(this.state.errors.status == '401'){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={this.clearErrors}>
You must be signed in to a Google account
to save this to<br />Google Drive!<br />
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
// if(this.state.errors.status == '401'){
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
// Oops!
// <div className='errorContainer' onClick={this.clearErrors}>
// You must be signed in to a Google account
// to save this to<br />Google Drive!<br />
// <a target='_blank' rel='noopener noreferrer'
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
// <div className='confirm'>
// Sign In
// </div>
// </a>
// <div className='deny'>
// Not Now
// </div>
// </div>
// </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'>
Oops!
<div className='errorContainer' onClick={this.clearErrors}>

View File

@@ -157,45 +157,24 @@ const NewPage = createClass({
const index = brew.text.indexOf('```\n\n');
brew.style = `${brew.style ? `${brew.style}\n` : ''}${brew.text.slice(7, index - 1)}`;
brew.text = brew.text.slice(index + 5);
};
}
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
.post('/api/newGoogle/')
const res = await request
.post(`/api${this.state.saveGoogle ? '?transferToGoogle=true' : ''}`)
.send(brew)
.catch((err)=>{
console.log(err.status === 401
? 'Not signed in!'
: 'Error Creating New Google Brew!');
console.log(err);
this.setState({ isSaving: false, errors: err });
return;
});
if(!res) return;
brew = res.body;
localStorage.removeItem(BREWKEY);
localStorage.removeItem(STYLEKEY);
localStorage.removeItem(METAKEY);
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}`;
});
}
brew = res.body;
localStorage.removeItem(BREWKEY);
localStorage.removeItem(STYLEKEY);
localStorage.removeItem(METAKEY);
window.location = `/edit/${brew.googleId ?? ''}${brew.editId}`;
},
renderSaveButton : function(){
@@ -208,26 +187,26 @@ const NewPage = createClass({
console.log(errMsg);
} catch (e){}
if(this.state.errors.status == '401'){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={this.clearErrors}>
You must be signed in to a Google account
to save this to<br />Google Drive!<br />
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
// if(this.state.errors.status == '401'){
// return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
// Oops!
// <div className='errorContainer' onClick={this.clearErrors}>
// You must be signed in to a Google account
// to save this to<br />Google Drive!<br />
// <a target='_blank' rel='noopener noreferrer'
// href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
// <div className='confirm'>
// Sign In
// </div>
// </a>
// <div className='deny'>
// Not Now
// </div>
// </div>
// </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'>
Oops!
<div className='errorContainer' onClick={this.clearErrors}>

View File

@@ -19,6 +19,7 @@ const UserPage = createClass({
return {
username : '',
brews : [],
query : ''
};
},
getInitialState : function() {
@@ -62,7 +63,7 @@ const UserPage = createClass({
},
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

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.0.7",
"version": "3.0.8",
"engines": {
"node": "16.11.x"
},
@@ -20,6 +20,9 @@
"verify": "npm run lint && npm test",
"test": "jest",
"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",
"prod": "set NODE_ENV=production && npm run build",
"postinstall": "npm run buildall",
@@ -31,6 +34,7 @@
"build/*"
],
"jest": {
"testTimeout" : 15000,
"modulePaths": [
"mode_modules",
"shared",
@@ -47,11 +51,11 @@
]
},
"dependencies": {
"@babel/core": "^7.17.5",
"@babel/core": "^7.17.9",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"body-parser": "^1.19.2",
"body-parser": "^1.20.0",
"classnames": "^2.3.1",
"codemirror": "^5.65.2",
"cookie-parser": "^1.4.6",
@@ -61,18 +65,18 @@
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.5",
"fs-extra": "10.0.1",
"googleapis": "95.0.0",
"googleapis": "100.0.0",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "4.0.12",
"marked": "4.0.14",
"marked-extended-tables": "^1.0.3",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.1",
"mongoose": "^6.2.4",
"nanoid": "3.3.1",
"nconf": "^0.11.3",
"moment": "^2.29.2",
"mongoose": "^6.2.10",
"nanoid": "3.3.2",
"nconf": "^0.11.4",
"query-string": "7.1.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
@@ -83,8 +87,8 @@
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
},
"devDependencies": {
"eslint": "^8.10.0",
"eslint-plugin-react": "^7.29.3",
"eslint": "^8.13.0",
"eslint-plugin-react": "^7.29.4",
"jest": "^27.5.1",
"supertest": "^6.2.2"
}

View File

@@ -182,8 +182,8 @@ const GoogleActions = {
'description' : `${brew.description}`,
'parents' : [folderId],
'properties' : { //AppProperties is not accessible
'shareId' : nanoid(12),
'editId' : nanoid(12),
'shareId' : brew.shareId || nanoid(12),
'editId' : brew.editId || nanoid(12),
'title' : brew.title,
'views' : '0',
'pageCount' : brew.pageCount,

View File

@@ -5,6 +5,7 @@ const zlib = require('zlib');
const GoogleActions = require('./googleActions.js');
const Markdown = require('../shared/naturalcrit/markdown.js');
const yaml = require('js-yaml');
const asyncHandler = require('express-async-handler');
// const getTopBrews = (cb) => {
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
@@ -41,154 +42,195 @@ const excludePropsFromUpdate = (brew)=>{
const propsToExclude = ['views', 'lastViewed'];
for (const prop of propsToExclude) {
delete brew[prop];
};
}
return brew;
};
const newBrew = (req, res)=>{
const brew = req.body;
const beforeNewSave = (account, brew)=>{
if(!brew.title) {
brew.title = getGoodBrewTitle(brew.text);
}
brew.authors = (req.account) ? [req.account.username] : [];
brew.authors = (account) ? [account.username] : [];
brew.text = mergeBrewText(brew);
};
delete brew.editId;
delete brew.shareId;
delete brew.googleId;
const newLocalBrew = async (brew)=>{
const newHomebrew = new HomebrewModel(brew);
// Compress brew text to binary before saving
newHomebrew.textBin = zlib.deflateRawSync(newHomebrew.text);
// Delete the non-binary text field since it's not needed anymore
newHomebrew.text = undefined;
newHomebrew.save((err, obj)=>{
if(err) {
console.error(err, err.toString(), err.stack);
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
}
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);
});
})
let saved = await newHomebrew.save()
.catch((err)=>{
console.error(err);
return res.status(500).send('Error while saving');
console.error(err, err.toString(), err.stack);
throw `Error while creating new brew, ${err.toString()}`;
});
saved = saved.toObject();
saved.gDrive = false;
return saved;
};
const deleteBrew = (req, res)=>{
HomebrewModel.find({ editId: req.params.id }, (err, objs)=>{
if(!objs.length || err) {
return res.status(404).send('Can not find homebrew with that id');
}
const newGoogleBrew = async (account, brew, res)=>{
const oAuth2Client = GoogleActions.authCheck(account, res);
const brew = objs[0];
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);
});
}
});
return await GoogleActions.newGoogleBrew(oAuth2Client, brew);
};
const newGoogleBrew = async (req, res, next)=>{
let oAuth2Client;
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
const newBrew = async (req, res)=>{
const brew = req.body;
if(!brew.title) {
brew.title = getGoodBrewTitle(brew.text);
}
brew.authors = (req.account) ? [req.account.username] : [];
brew.text = mergeBrewText(brew);
const { transferToGoogle } = req.query;
delete brew.editId;
delete brew.shareId;
delete brew.googleId;
req.body = brew;
beforeNewSave(req.account, brew);
try {
const newBrew = await GoogleActions.newGoogleBrew(oAuth2Client, brew);
return res.status(200).send(newBrew);
} catch (err) {
return res.status(err.response.status).send(err);
let saved;
if(transferToGoogle) {
saved = await newGoogleBrew(req.account, brew, res)
.catch((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 brew = excludePropsFromUpdate(req.body);
brew.text = mergeBrewText(brew);
const deleteLocalBrew = async (account, id)=>{
const brew = await HomebrewModel.findOne({ editId: id });
if(!brew) {
throw { status: 404, message: 'Can not find homebrew with that id' };
}
try {
const updatedBrew = await GoogleActions.updateGoogleBrew(brew);
return res.status(200).send(updatedBrew);
} catch (err) {
return res.status(err.response?.status || 500).send(err);
if(account) {
// Remove current user as author
brew.authors = _.pull(brew.authors, account.username);
brew.markModified('authors');
}
if(brew.authors.length === 0) {
// Delete brew if there are no authors left
await brew.remove()
.catch((err)=>{
console.error(err);
throw { status: 500, message: 'Error while removing' };
});
} else {
// Otherwise, save the brew with updated author list
return await brew.save()
.catch((err)=>{
throw { status: 500, message: err };
});
}
};
router.post('/api', newBrew);
router.post('/api/newGoogle/', newGoogleBrew);
router.put('/api/:id', updateBrew);
router.put('/api/update/:id', updateBrew);
router.put('/api/updateGoogle/:id', updateGoogleBrew);
router.delete('/api/:id', deleteBrew);
router.get('/api/remove/:id', deleteBrew);
router.get('/api/removeGoogle/:id', async (req, res)=>{
const auth = await GoogleActions.authCheck(req.account, res);
await GoogleActions.deleteGoogleBrew(auth, req.params.id);
return res.status(200).send();
});
const deleteGoogleBrew = async (account, id, res)=>{
const auth = await GoogleActions.authCheck(account, res);
await GoogleActions.deleteGoogleBrew(auth, id);
return true;
};
router.post('/api', asyncHandler(newBrew));
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;

View File

@@ -10,43 +10,69 @@ const SplitPane = createClass({
return {
storageKey : 'naturalcrit-pane-split',
onDragFinish : function(){} //fires when dragging
};
},
getInitialState : function() {
return {
size : null,
isDragging : false
currentDividerPos : null,
windowWidth : 0,
isDragging : false
};
},
componentDidMount : function() {
const paneSize = window.localStorage.getItem(this.props.storageKey);
if(paneSize){
const dividerPos = window.localStorage.getItem(this.props.storageKey);
if(dividerPos){
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(){
if(this.state.isDragging){
this.props.onDragFinish(this.state.size);
window.localStorage.setItem(this.props.storageKey, this.state.size);
this.props.onDragFinish(this.state.currentDividerPos);
window.localStorage.setItem(this.props.storageKey, this.state.currentDividerPos);
}
this.setState({ isDragging: false });
},
handleDown : function(){
this.setState({ isDragging: true });
//this.unFocus()
},
handleMove : function(e){
if(!this.state.isDragging) return;
const minWidth = 1;
const maxWidth = window.innerWidth - 13;
const newSize = Math.min(maxWidth, Math.max(minWidth, e.pageX));
const newSize = this.limitPosition(e.pageX);
this.setState({
size : newSize
currentDividerPos : newSize,
userSetDividerPos : newSize
});
},
/*
@@ -70,7 +96,7 @@ const SplitPane = createClass({
render : function(){
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()}
<Pane ref='pane2' isDragging={this.state.isDragging}>{this.props.children[1]}</Pane>
</div>;

View File

@@ -621,6 +621,8 @@ body {
}
&.decoration {
transform-style : preserve-3d;
z-index: -1;
position:relative;
}
&.decoration::before {
content :'';
@@ -656,7 +658,7 @@ body {
margin-bottom : 0.3cm;
}
a{
display : table;
display : inline;
color : inherit;
text-decoration : none;
&:hover{