69
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 99
|
||||
ignore:
|
||||
- dependency-name: eslint
|
||||
versions:
|
||||
- 7.19.0
|
||||
- 7.22.0
|
||||
- 7.23.0
|
||||
- 7.24.0
|
||||
- dependency-name: "@babel/core"
|
||||
versions:
|
||||
- 7.12.13
|
||||
- 7.12.16
|
||||
- 7.12.17
|
||||
- 7.13.13
|
||||
- 7.13.14
|
||||
- 7.13.15
|
||||
- dependency-name: googleapis
|
||||
versions:
|
||||
- 68.0.0
|
||||
- 70.0.0
|
||||
- 71.0.0
|
||||
- dependency-name: "@babel/preset-env"
|
||||
versions:
|
||||
- 7.12.13
|
||||
- 7.12.16
|
||||
- 7.12.17
|
||||
- 7.13.0
|
||||
- 7.13.12
|
||||
- 7.13.8
|
||||
- dependency-name: mongoose
|
||||
versions:
|
||||
- 5.11.14
|
||||
- 5.11.15
|
||||
- 5.11.16
|
||||
- 5.11.17
|
||||
- 5.11.18
|
||||
- 5.11.19
|
||||
- 5.12.1
|
||||
- 5.12.2
|
||||
- 5.12.3
|
||||
- dependency-name: eslint-plugin-react
|
||||
versions:
|
||||
- 7.23.0
|
||||
- 7.23.1
|
||||
- dependency-name: query-string
|
||||
versions:
|
||||
- 7.0.0
|
||||
- dependency-name: nanoid
|
||||
versions:
|
||||
- 3.1.22
|
||||
- dependency-name: "@babel/preset-react"
|
||||
versions:
|
||||
- 7.13.13
|
||||
- dependency-name: codemirror
|
||||
versions:
|
||||
- 5.59.3
|
||||
- 5.60.0
|
||||
- dependency-name: classnames
|
||||
versions:
|
||||
- 2.3.0
|
||||
- dependency-name: marked
|
||||
versions:
|
||||
- 1.2.8
|
||||
26
changelog.md
@@ -6,6 +6,11 @@ h5 {
|
||||
|
||||
# changelog
|
||||
|
||||
### Saturday, 02/5/2021 - v2.11.2
|
||||
|
||||
- Fix for edge case where brews could accidentally transfer from Google Drive back to Homebrewery.
|
||||
- Move cursor to end of snippet after insertion
|
||||
|
||||
### Saturday, 20/3/2021 - v2.11.1
|
||||
|
||||
- Warning when opening brew in your Google Drive trash
|
||||
@@ -52,10 +57,6 @@ h5 {
|
||||
### Monday, 19/10/2020 - v2.10.2
|
||||
- Fixed issue with "recent" item links not updating when transferring between Google Drive.
|
||||
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
### Monday, 12/10/2020 - v2.10.1
|
||||
- Fixed issue with users unable to create new brews
|
||||
- Fixing brews being lost when loaded via back button
|
||||
@@ -73,6 +74,13 @@ h5 {
|
||||
### Wednesday, 20/05/2020 - v2.9.0
|
||||
- Major refactoring of site backend to work with updated dependencies for security (should be invisible to users)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
\page
|
||||
|
||||
### Wednesday, 11/03/2020 - v2.8.2
|
||||
- Fixed delete button removing everyone's copy for brews with multiple authors
|
||||
- Compressed homebrew text in database
|
||||
@@ -100,9 +108,6 @@ h5 {
|
||||
### Saturday, 22/04/2017 - v2.7.4
|
||||
- Give ability to hide the render warning notification
|
||||
|
||||
|
||||
\page
|
||||
|
||||
### Friday, 03/03/2017 - v2.7.3
|
||||
- Increasing the range on the Partial Page Rendering for a quick-fix for it getting out of sync on long brews.
|
||||
|
||||
@@ -144,9 +149,6 @@ h5 {
|
||||
- Added in a snippet for a split table
|
||||
- Added an account nav item to new page
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
### Sunday, 27/11/2016 - v2.5.1
|
||||
- Fixed the column rendering on the new user page. Really should have tested that better
|
||||
- Added a hover tooltip to fully read the brew description
|
||||
@@ -167,6 +169,8 @@ h5 {
|
||||
- You can now print from a new page without saving
|
||||
- Added the ability to use ctrl+p and ctrl+s to print and save respectively.
|
||||
|
||||
\page
|
||||
|
||||
### Monday, 07/11/2016
|
||||
- Added final touches to the html validator and updating the rest of the branch
|
||||
- If anyone finds issues with the new HTML validator, please let me know. I hope this will bring a more consistent feel to Homebrewery rendering.
|
||||
@@ -186,8 +190,6 @@ h5 {
|
||||
- Fixed the noteblock overlapping into titles (thanks u/dsompura!)
|
||||
- Fixed a bad search route in the admin panel (thanks u/SnappyTom!)
|
||||
|
||||
\page
|
||||
|
||||
### Friday, 29/07/2016 - v2.2.7
|
||||
- Adding in descriptive note blocks. (Thanks calculuschild!)
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ const Editor = createClass({
|
||||
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
|
||||
|
||||
this.handleTextChange(lines.join('\n'));
|
||||
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
|
||||
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line + injectText.split('\n').length, this.cursorPosition.ch + injectText.length);
|
||||
},
|
||||
handgleToggle : function(){
|
||||
this.setState({
|
||||
|
||||
@@ -88,7 +88,7 @@ module.exports = {
|
||||
`- **Components:** ${components}`,
|
||||
`- **Duration:** ${_.sample(['Until dispelled', '1 round', 'Instantaneous', 'Concentration, up to 10 minutes', '1 hour'])}`,
|
||||
'',
|
||||
'A flame, equivalent in brightness to a torch, springs from from an object that you touch. ',
|
||||
'A flame, equivalent in brightness to a torch, springs from an object that you touch. ',
|
||||
'The effect look like a regular flame, but it creates no heat and doesn\'t use oxygen. ',
|
||||
'A *continual flame* can be covered or hidden but not smothered or quenched.',
|
||||
'\n\n\n'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const _ = require('lodash');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
const genList = function(list, max){
|
||||
return _.sampleSize(list, _.random(0, max)).join(', ') || 'None';
|
||||
@@ -86,7 +87,7 @@ const getAlignment = function(){
|
||||
};
|
||||
|
||||
const getStats = function(){
|
||||
return `>|${_.times(6, function(){
|
||||
return `|${_.times(6, function(){
|
||||
const num = _.random(1, 20);
|
||||
const mod = Math.ceil(num/2 - 5);
|
||||
return `${num} (${mod >= 0 ? `+${mod}` : mod})`;
|
||||
@@ -95,12 +96,12 @@ const getStats = function(){
|
||||
|
||||
const genAbilities = function(){
|
||||
return _.sample([
|
||||
'> ***Pack Tactics.*** These guys work together. Like super well, you don\'t even know.',
|
||||
'> ***Fowl Appearance.*** While the creature remains motionless, it is indistinguishable from a normal chicken.',
|
||||
'> ***Onion Stench.*** Any creatures within 5 feet of this thing develops an irrational craving for onion rings.',
|
||||
'> ***Enormous Nose.*** This creature gains advantage on any check involving putting things in its nose.',
|
||||
'> ***Sassiness.*** When questioned, this creature will talk back instead of answering.',
|
||||
'> ***Big Jerk.*** Thinks he is just *waaaay* better than you.',
|
||||
'***Pack Tactics.*** These guys work together like peanut butter and jelly.',
|
||||
'***Fowl Appearance.*** While the creature remains motionless, it is indistinguishable from a normal chicken.',
|
||||
'***Onion Stench.*** Any creatures within 5 feet of this thing develops an irrational craving for onion rings.',
|
||||
'***Enormous Nose.*** This creature gains advantage on any check involving putting things in its nose.',
|
||||
'***Sassiness.*** When questioned, this creature will talk back instead of answering.',
|
||||
'***Big Jerk.*** Whenever this creature makes an attack, it starts telling you how much cooler it is than you.',
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -133,68 +134,37 @@ const genAction = function(){
|
||||
'Turnbuckle Roll'
|
||||
]);
|
||||
|
||||
return `> ***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `;
|
||||
return `***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `;
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
full : function(){
|
||||
return `${[
|
||||
'___',
|
||||
'___',
|
||||
`> ## ${getMonsterName()}`,
|
||||
`>*${getType()}, ${getAlignment()}*`,
|
||||
'> ___',
|
||||
`> - **Armor Class** ${_.random(10, 20)}`,
|
||||
`> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
|
||||
`> - **Speed** ${_.random(0, 50)}ft.`,
|
||||
'>___',
|
||||
'>|STR|DEX|CON|INT|WIS|CHA|',
|
||||
'>|:---:|:---:|:---:|:---:|:---:|:---:|',
|
||||
getStats(),
|
||||
'>___',
|
||||
`> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
|
||||
`> - **Senses** passive Perception ${_.random(3, 20)}`,
|
||||
`> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
|
||||
`> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
|
||||
'> ___',
|
||||
_.times(_.random(3, 6), function(){
|
||||
return genAbilities();
|
||||
}).join('\n>\n'),
|
||||
'> ### Actions',
|
||||
_.times(_.random(4, 6), function(){
|
||||
return genAction();
|
||||
}).join('\n>\n'),
|
||||
].join('\n')}\n\n\n`;
|
||||
},
|
||||
|
||||
half : function(){
|
||||
return `${[
|
||||
'___',
|
||||
`> ## ${getMonsterName()}`,
|
||||
`>*${getType()}, ${getAlignment()}*`,
|
||||
'> ___',
|
||||
`> - **Armor Class** ${_.random(10, 20)}`,
|
||||
`> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
|
||||
`> - **Speed** ${_.random(0, 50)}ft.`,
|
||||
'>___',
|
||||
'>|STR|DEX|CON|INT|WIS|CHA|',
|
||||
'>|:---:|:---:|:---:|:---:|:---:|:---:|',
|
||||
getStats(),
|
||||
'>___',
|
||||
`> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
|
||||
`> - **Senses** passive Perception ${_.random(3, 20)}`,
|
||||
`> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
|
||||
`> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
|
||||
'> ___',
|
||||
_.times(_.random(2, 3), function(){
|
||||
return genAbilities();
|
||||
}).join('\n>\n'),
|
||||
'> ### Actions',
|
||||
_.times(_.random(1, 2), function(){
|
||||
return genAction();
|
||||
}).join('\n>\n'),
|
||||
].join('\n')}\n\n\n`;
|
||||
monster : function(classes, genLines){
|
||||
return dedent`
|
||||
{{${classes}
|
||||
## ${getMonsterName()}
|
||||
*${getType()}, ${getAlignment()}*
|
||||
___
|
||||
: **Armor Class** : ${_.random(10, 20)} (chain mail, shield)
|
||||
: **Hit Points** : ${_.random(1, 150)}(1d4 + 5)
|
||||
: **Speed** : ${_.random(0, 50)}ft.
|
||||
___
|
||||
| STR | DEX | CON | INT | WIS | CHA |
|
||||
|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|
|
||||
${getStats()}
|
||||
___
|
||||
: **Condition Immunities** : ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}
|
||||
: **Senses** : darkvision 60 ft., passive Perception ${_.random(3, 20)}
|
||||
: **Languages** : ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}
|
||||
: **Challenge** : ${_.random(0, 15)} (${_.random(10, 10000)} XP)
|
||||
___
|
||||
:
|
||||
${_.times(_.random(genLines, genLines + 2), function(){return genAbilities();}).join('\n\t\t\t\n\t\t\t')}
|
||||
:
|
||||
### Actions
|
||||
${_.times(_.random(genLines, genLines + 2), function(){return genAction();}).join('\n\t\t\t\n\t\t\t')}
|
||||
}}
|
||||
\n`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ const MonsterBlockGen = require('./monsterblock.gen.js');
|
||||
const ClassFeatureGen = require('./classfeature.gen.js');
|
||||
const CoverPageGen = require('./coverpage.gen.js');
|
||||
const TableOfContentsGen = require('./tableOfContents.gen.js');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
|
||||
module.exports = [
|
||||
@@ -151,15 +152,20 @@ module.exports = [
|
||||
].join('\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
name : 'Monster Stat Block (unframed)',
|
||||
icon : 'fas fa-paw',
|
||||
gen : MonsterBlockGen.monster('monster', 2),
|
||||
},
|
||||
{
|
||||
name : 'Monster Stat Block',
|
||||
icon : 'fas fa-spider',
|
||||
gen : MonsterBlockGen.half,
|
||||
gen : MonsterBlockGen.monster('monster,frame', 2),
|
||||
},
|
||||
{
|
||||
name : 'Wide Monster Stat Block',
|
||||
icon : 'fas fa-dragon',
|
||||
gen : MonsterBlockGen.full,
|
||||
gen : MonsterBlockGen.monster('monster,frame,wide', 4),
|
||||
},
|
||||
{
|
||||
name : 'Cover Page',
|
||||
@@ -196,63 +202,61 @@ module.exports = [
|
||||
name : 'Table',
|
||||
icon : 'fas fa-th-list',
|
||||
gen : function(){
|
||||
return [
|
||||
'##### Cookie Tastiness',
|
||||
'| Tastiness | Cookie Type |',
|
||||
'|:----:|:-------------|',
|
||||
'| -5 | Raisin |',
|
||||
'| 8th | Chocolate Chip |',
|
||||
'| 11th | 2 or lower |',
|
||||
'| 14th | 3 or lower |',
|
||||
'| 17th | 4 or lower |\n\n',
|
||||
].join('\n');
|
||||
},
|
||||
return dedent`
|
||||
##### Character Advancement
|
||||
| Experience Points | Level | Proficiency Bonus |
|
||||
|:------------------|:-----:|:-----------------:|
|
||||
| 0 | 1 | +2 |
|
||||
| 300 | 2 | +2 |
|
||||
| 900 | 3 | +2 |
|
||||
| 2,700 | 4 | +2 |
|
||||
| 6,500 | 5 | +3 |
|
||||
| 14,000 | 6 | +3 |
|
||||
\n`;
|
||||
}
|
||||
},
|
||||
{
|
||||
name : 'Wide Table',
|
||||
icon : 'fas fa-list',
|
||||
gen : function(){
|
||||
return [
|
||||
'<div class=\'wide\'>',
|
||||
'##### Cookie Tastiness',
|
||||
'| Tastiness | Cookie Type |',
|
||||
'|:----:|:-------------|',
|
||||
'| -5 | Raisin |',
|
||||
'| 8th | Chocolate Chip |',
|
||||
'| 11th | 2 or lower |',
|
||||
'| 14th | 3 or lower |',
|
||||
'| 17th | 4 or lower |',
|
||||
'</div>\n\n'
|
||||
].join('\n');
|
||||
},
|
||||
return dedent`
|
||||
{{wide
|
||||
##### Weapons
|
||||
| Name | Cost | Damage | Weight | Properties |
|
||||
|:------------------------|:-----:|:----------------|--------:|:-----------|
|
||||
| *Simple Melee Weapons* | | | | |
|
||||
|   Club | 1 sp | 1d4 bludgeoning | 2 lb. | Light |
|
||||
|   Dagger | 2 gp | 1d4 piercing | 1 lb. | Finesse |
|
||||
|   Spear | 1 gp | 1d6 piercing | 3 lb. | Thrown |
|
||||
| *Simple Ranged Weapons* | | | | |
|
||||
|   Dart | 5 cp | 1d4 piercig | 1/4 lb. | Finesse |
|
||||
|   Shortbow | 25 gp | 1d6 piercing | 2 lb. | Ammunition |
|
||||
|   Sling | 1 sp | 1d4 bludgeoning | — | Ammunition |
|
||||
}}
|
||||
\n`;
|
||||
}
|
||||
},
|
||||
{
|
||||
name : 'Split Table',
|
||||
icon : 'fas fa-th-large',
|
||||
gen : function(){
|
||||
return [
|
||||
'<div style=\'column-count:2\'>',
|
||||
'| d10 | Damage Type |',
|
||||
'|:---:|:------------|',
|
||||
'| 1 | Acid |',
|
||||
'| 2 | Cold |',
|
||||
'| 3 | Fire |',
|
||||
'| 4 | Force |',
|
||||
'| 5 | Lightning |',
|
||||
'',
|
||||
'```',
|
||||
'```',
|
||||
'',
|
||||
'| d10 | Damage Type |',
|
||||
'|:---:|:------------|',
|
||||
'| 6 | Necrotic |',
|
||||
'| 7 | Poison |',
|
||||
'| 8 | Psychic |',
|
||||
'| 9 | Radiant |',
|
||||
'| 10 | Thunder |',
|
||||
'</div>\n\n',
|
||||
].join('\n');
|
||||
},
|
||||
return dedent`
|
||||
##### Typical Difficulty Classes
|
||||
{{column-count="2"
|
||||
| Task Difficulty | DC |
|
||||
|:----------------|:--:|
|
||||
| Very easy | 5 |
|
||||
| Easy | 10 |
|
||||
| Medium | 15 |
|
||||
|
||||
| Task Difficulty | DC |
|
||||
|:------------------|:--:|
|
||||
| Hard | 20 |
|
||||
| Very hard | 25 |
|
||||
| Nearly impossible | 30 |
|
||||
}}
|
||||
\n`;
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -82,7 +82,7 @@ module.exports = {
|
||||
`- **Components:** ${components}`,
|
||||
`- **Duration:** ${_.sample(['Until dispelled', '1 round', 'Instantaneous', 'Concentration, up to 10 minutes', '1 hour'])}`,
|
||||
'',
|
||||
'A flame, equivalent in brightness to a torch, springs from from an object that you touch. ',
|
||||
'A flame, equivalent in brightness to a torch, springs from an object that you touch. ',
|
||||
'The effect look like a regular flame, but it creates no heat and doesn\'t use oxygen. ',
|
||||
'A *continual flame* can be covered or hidden but not smothered or quenched.',
|
||||
'\n\n\n'
|
||||
|
||||
@@ -55,6 +55,7 @@ const EditPage = createClass({
|
||||
isSaving : false,
|
||||
isPending : false,
|
||||
alertTrashedGoogleBrew : this.props.brew.trashed,
|
||||
alertLoginToTransfer : false,
|
||||
saveGoogle : this.props.brew.googleId ? true : false,
|
||||
confirmGoogleTransfer : false,
|
||||
errors : null,
|
||||
@@ -140,15 +141,25 @@ const EditPage = createClass({
|
||||
},
|
||||
|
||||
handleGoogleClick : function(){
|
||||
console.log('handlegoogleclick');
|
||||
if(!global.account?.googleId) {
|
||||
this.setState({
|
||||
alertLoginToTransfer : true
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState((prevState)=>({
|
||||
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
|
||||
}));
|
||||
this.clearErrors();
|
||||
},
|
||||
|
||||
closeAlerts : function(){
|
||||
closeAlerts : function(event){
|
||||
event.stopPropagation(); //Only handle click once so alert doesn't reopen
|
||||
this.setState({
|
||||
alertTrashedGoogleBrew : false
|
||||
alertTrashedGoogleBrew : false,
|
||||
alertLoginToTransfer : false,
|
||||
confirmGoogleTransfer : false
|
||||
});
|
||||
},
|
||||
|
||||
@@ -187,7 +198,7 @@ const EditPage = createClass({
|
||||
.catch((err)=>{
|
||||
console.log(err.status === 401
|
||||
? 'Not signed in!'
|
||||
: 'Error Saving to Google!');
|
||||
: 'Error Transferring to Google!');
|
||||
this.setState({ errors: err, saveGoogle: false });
|
||||
});
|
||||
|
||||
@@ -210,7 +221,7 @@ const EditPage = createClass({
|
||||
console.log(err.status === 401
|
||||
? 'Not signed in!'
|
||||
: 'Error Saving to Google!');
|
||||
this.setState({ errors: err, saveGoogle: false });
|
||||
this.setState({ errors: err });
|
||||
return;
|
||||
});
|
||||
|
||||
@@ -260,39 +271,44 @@ const EditPage = createClass({
|
||||
},
|
||||
|
||||
renderGoogleDriveIcon : function(){
|
||||
if(this.state.saveGoogle) {
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
|
||||
<img src={googleDriveActive} alt='googleDriveActive' />
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
|
||||
{this.state.saveGoogle
|
||||
? <img src={googleDriveActive} alt='googleDriveActive'/>
|
||||
: <img src={googleDriveInactive} alt='googleDriveInactive'/>
|
||||
}
|
||||
|
||||
{this.state.confirmGoogleTransfer &&
|
||||
<div className='errorContainer'>
|
||||
Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?<br />
|
||||
<div className='confirm' onClick={this.toggleGoogleStorage}>
|
||||
Yes
|
||||
</div>
|
||||
<div className='deny'>
|
||||
No
|
||||
</div>
|
||||
{this.state.confirmGoogleTransfer &&
|
||||
<div className='errorContainer' onClick={this.closeAlerts}>
|
||||
{ this.state.saveGoogle
|
||||
? `Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?`
|
||||
: `Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?`
|
||||
}
|
||||
<br />
|
||||
<div className='confirm' onClick={this.toggleGoogleStorage}>
|
||||
Yes
|
||||
</div>
|
||||
}
|
||||
</Nav.item>;
|
||||
} else {
|
||||
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
|
||||
<img src={googleDriveInactive} alt='googleDriveInactive' />
|
||||
<div className='deny'>
|
||||
No
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{this.state.confirmGoogleTransfer &&
|
||||
<div className='errorContainer'>
|
||||
Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?<br />
|
||||
<div className='confirm' onClick={this.toggleGoogleStorage}>
|
||||
Yes
|
||||
</div>
|
||||
<div className='deny'>
|
||||
No
|
||||
{this.state.alertLoginToTransfer &&
|
||||
<div className='errorContainer' onClick={this.closeAlerts}>
|
||||
You must be signed in to a Google account to transfer
|
||||
between the homebrewery and Google Drive!
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`http://naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
<div className='confirm'>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
<div className='deny'>
|
||||
Not Now
|
||||
</div>
|
||||
}
|
||||
</Nav.item>;
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</Nav.item>;
|
||||
},
|
||||
|
||||
renderSaveButton : function(){
|
||||
@@ -311,7 +327,7 @@ const EditPage = createClass({
|
||||
to save this to<br />Google Drive!<br />
|
||||
<a target='_blank' rel='noopener noreferrer'
|
||||
href={`http://naturalcrit.com/login?redirect=${this.state.url}`}>
|
||||
<div className='confirm' onClick={this.toggleGoogleStorage}>
|
||||
<div className='confirm'>
|
||||
Sign In
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -43,14 +43,14 @@ const NewPage = createClass({
|
||||
getInitialState : function() {
|
||||
return {
|
||||
brew : {
|
||||
text : this.props.brew.text,
|
||||
text : this.props.brew.text || '',
|
||||
gDrive : false,
|
||||
title : '',
|
||||
description : '',
|
||||
tags : '',
|
||||
title : this.props.brew.title || '',
|
||||
description : this.props.brew.description || '',
|
||||
tags : this.props.brew.tags || '',
|
||||
published : false,
|
||||
authors : [],
|
||||
systems : []
|
||||
systems : this.props.brew.systems || []
|
||||
},
|
||||
|
||||
isSaving : false,
|
||||
|
||||
BIN
client/homebrew/phbStyle/images/descriptiveBorder.png
Normal file
|
After Width: | Height: | Size: 311 B |
BIN
client/homebrew/phbStyle/images/footerAccent.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
client/homebrew/phbStyle/images/frameBorder.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
client/homebrew/phbStyle/images/monsterBorderFancy.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
client/homebrew/phbStyle/images/monsterBorderLegacy.png
Normal file
|
After Width: | Height: | Size: 135 B |
BIN
client/homebrew/phbStyle/images/noteBorder.png
Normal file
|
After Width: | Height: | Size: 274 B |
BIN
client/homebrew/phbStyle/images/parchmentBackground.jpg
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
client/homebrew/phbStyle/images/parchmentBackgroundGrayscale.jpg
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
client/homebrew/phbStyle/images/redTriangle.png
Normal file
|
After Width: | Height: | Size: 864 B |
@@ -7,7 +7,7 @@
|
||||
@headerUnderline : #c9ad6a;
|
||||
@horizontalRule : #9c2b1b;
|
||||
@headerText : #58180D;
|
||||
@monsterStatBackground : #FDF1DC;
|
||||
@monsterStatBackground : #EEDBAB;
|
||||
@page { margin: 0; }
|
||||
body {
|
||||
counter-reset : phb-page-numbers;
|
||||
@@ -17,7 +17,11 @@ body {
|
||||
}
|
||||
.useSansSerif(){
|
||||
font-family : ScalySansRemake;
|
||||
font-size : 10pt;
|
||||
font-size : 0.325cm;
|
||||
line-height : 1.2em;
|
||||
p,dl,ul {
|
||||
line-height : 1.2em;
|
||||
}
|
||||
em{
|
||||
font-style : italic;
|
||||
}
|
||||
@@ -29,14 +33,14 @@ body {
|
||||
.useColumns(@multiplier : 1){
|
||||
column-count : 2;
|
||||
column-fill : auto;
|
||||
column-gap : 1cm;
|
||||
column-gap : 0.9cm;
|
||||
column-width : 8cm * @multiplier;
|
||||
-webkit-column-count : 2;
|
||||
-moz-column-count : 2;
|
||||
-webkit-column-width : 8cm * @multiplier;
|
||||
-moz-column-width : 8cm * @multiplier;
|
||||
-webkit-column-gap : 1cm;
|
||||
-moz-column-gap : 1cm;
|
||||
-webkit-column-gap : 0.9cm;
|
||||
-moz-column-gap : 0.9cm;
|
||||
}
|
||||
.phb3{
|
||||
.useColumns();
|
||||
@@ -47,11 +51,11 @@ body {
|
||||
overflow : hidden;
|
||||
height : 279.4mm;
|
||||
width : 215.9mm;
|
||||
padding : 1.0cm 1.7cm 1.5cm;
|
||||
padding : 1.4cm 1.9cm 1.7cm;
|
||||
background-color : @background;
|
||||
background-image : @backgroundImage;
|
||||
font-family : BookInsanityRemake;
|
||||
font-size : 0.317cm;
|
||||
font-size : 0.34cm;
|
||||
text-rendering : optimizeLegibility;
|
||||
page-break-before : always;
|
||||
page-break-after : always;
|
||||
@@ -59,11 +63,11 @@ body {
|
||||
// * BASE
|
||||
// *****************************/
|
||||
p{
|
||||
overflow-wrap : break-word;
|
||||
padding-top : 0em;
|
||||
line-height : 1.3em;
|
||||
overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
|
||||
margin-bottom : 1em;
|
||||
line-height : 1.3em;
|
||||
&+p{
|
||||
padding-top : 0em;
|
||||
margin-top : -1em;
|
||||
}
|
||||
}
|
||||
ul{
|
||||
@@ -89,7 +93,7 @@ body {
|
||||
}
|
||||
strong{
|
||||
font-weight : bold;
|
||||
letter-spacing : 0.03em;
|
||||
letter-spacing : -0.02em;
|
||||
}
|
||||
em{
|
||||
font-style : italic;
|
||||
@@ -108,15 +112,14 @@ body {
|
||||
// * HEADERS
|
||||
// *****************************/
|
||||
h1,h2,h3,h4{
|
||||
margin-top : 0.2em;
|
||||
margin-bottom : 0.2em;
|
||||
font-family : MrEavesRemake;
|
||||
font-weight : 800;
|
||||
color : @headerText;
|
||||
}
|
||||
h1{
|
||||
margin-bottom : 0.18cm;
|
||||
column-span : all;
|
||||
font-size : 0.987cm;
|
||||
font-size : 0.89cm;
|
||||
-webkit-column-span : all;
|
||||
-moz-column-span : all;
|
||||
&+p::first-letter{
|
||||
@@ -136,23 +139,28 @@ body {
|
||||
color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
&+p::first-line{
|
||||
font-size : .385cm;
|
||||
font-variant : small-caps;
|
||||
}
|
||||
}
|
||||
h2{
|
||||
font-size : 0.705cm;
|
||||
margin-top : 0px;
|
||||
margin-bottom : 0.05cm;
|
||||
font-size : 0.75cm;
|
||||
}
|
||||
h3{
|
||||
font-size : 0.529cm;
|
||||
margin-top : -0.1cm;
|
||||
margin-bottom : 0.1cm;
|
||||
font-size : 0.575cm;
|
||||
border-bottom : 2px solid @headerUnderline;
|
||||
}
|
||||
h4{
|
||||
margin-bottom : 0.00em;
|
||||
margin-top : -0.02cm;
|
||||
margin-bottom : 0.02cm;
|
||||
font-size : 0.458cm;
|
||||
}
|
||||
h5{
|
||||
margin-bottom : 0.2em;
|
||||
margin-top : -0.02cm;
|
||||
margin-bottom : 0.02cm;
|
||||
font-family : ScalySansSmallCapsRemake;
|
||||
font-size : 0.423cm;
|
||||
font-weight : 900;
|
||||
@@ -164,21 +172,18 @@ body {
|
||||
.useSansSerif();
|
||||
width : 100%;
|
||||
margin-bottom : 1em;
|
||||
font-size : 10pt;
|
||||
thead{
|
||||
display: table-row-group;
|
||||
font-weight : 800;
|
||||
th{
|
||||
vertical-align : bottom;
|
||||
padding-bottom : 0.3em;
|
||||
padding-right : 0.1em;
|
||||
padding-left : 0.1em;
|
||||
padding : 0.14em 0.4em;
|
||||
}
|
||||
}
|
||||
tbody{
|
||||
tr{
|
||||
td{
|
||||
padding : 0.3em 0.1em;
|
||||
padding : 0.14em 0.4em;
|
||||
}
|
||||
&:nth-child(odd){
|
||||
background-color : @noteGreen;
|
||||
@@ -213,66 +218,101 @@ body {
|
||||
//*****************************
|
||||
// * MONSTER STAT BLOCK
|
||||
// *****************************/
|
||||
hr+blockquote{
|
||||
position : relative;
|
||||
padding-top : 15px;
|
||||
background-color : @monsterStatBackground;
|
||||
border-style : solid;
|
||||
border-width : 10px;
|
||||
border-image : @monsterBorderImage 10;
|
||||
|
||||
.monster {
|
||||
&.frame {
|
||||
border-style : solid;
|
||||
border-width : 7px 6px;
|
||||
background-color : @monsterStatBackground;
|
||||
background-image : @monsterBlockBackground;
|
||||
border-image : @monsterBorderImage 14 round;
|
||||
border-image-outset : 0px 2px;
|
||||
background-blend-mode : overlay;
|
||||
background-attachment : fixed;
|
||||
box-shadow : 1px 4px 14px #888;
|
||||
padding : 4px 2px;
|
||||
margin : 0px -6px 1em;
|
||||
}
|
||||
.useSansSerif();
|
||||
//-webkit-transform : translateZ(0); //Prevents shadows from breaking across columns, but breaks internal columns...
|
||||
position : relative;
|
||||
padding : 0px;
|
||||
margin-bottom : 1em;
|
||||
|
||||
p{
|
||||
margin-bottom : 0.3cm;
|
||||
}
|
||||
p+p {
|
||||
margin-top : 0; //May not be needed
|
||||
text-indent : 0;
|
||||
}
|
||||
p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
//Headers
|
||||
h2{
|
||||
margin-top : -8px;
|
||||
margin-bottom : 0px;
|
||||
&+p{
|
||||
padding-bottom : 0px;
|
||||
font-size : 0.62cm;
|
||||
line-height : 1em;
|
||||
margin : 0;
|
||||
&+p {
|
||||
font-size : 0.304cm; //Monster size and type subtext
|
||||
margin-bottom : 0;
|
||||
}
|
||||
}
|
||||
h3{
|
||||
font-family : ScalySansRemake;
|
||||
font-weight : 400;
|
||||
border-bottom : 1px solid @headerText;
|
||||
}
|
||||
hr+ul{
|
||||
color : @headerText;
|
||||
}
|
||||
ul{
|
||||
.useSansSerif();
|
||||
padding-left : 1em;
|
||||
font-size : 0.352cm;
|
||||
}
|
||||
// Monster Ability table
|
||||
hr+table{
|
||||
margin : 0;
|
||||
column-span : 1;
|
||||
background-color : transparent;
|
||||
border-style : none;
|
||||
border-image : none;
|
||||
-webkit-column-span : 1;
|
||||
tbody{
|
||||
tr:nth-child(odd), tr:nth-child(even){
|
||||
background-color : transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
table{
|
||||
color : @headerText;
|
||||
}
|
||||
p+p{
|
||||
margin-top : 0em;
|
||||
padding-bottom : 0.5em;
|
||||
text-indent : 0em;
|
||||
font-family : ScalySansRemake;
|
||||
font-weight : 800;
|
||||
font-variant : small-caps;
|
||||
border-bottom : 2px solid @headerText;
|
||||
margin-top : 0.05cm;
|
||||
padding-bottom : 0.05cm;
|
||||
}
|
||||
|
||||
//Triangle dividers
|
||||
hr{
|
||||
visibility : visible;
|
||||
height : 6px;
|
||||
margin : 4px 0px;
|
||||
margin : 0.12cm 0cm;
|
||||
background-image : @redTriangleImage;
|
||||
background-size : 100% 100%;
|
||||
border : none;
|
||||
}
|
||||
|
||||
//Attribute Lists
|
||||
dl {
|
||||
.useSansSerif();
|
||||
color : @headerText;
|
||||
padding-left :1.3em;
|
||||
text-indent :-1.3em;
|
||||
}
|
||||
dd {
|
||||
text-indent : 0px;
|
||||
}
|
||||
|
||||
// Monster Ability table
|
||||
hr + table:first-of-type{
|
||||
margin : 0;
|
||||
column-span : 1;
|
||||
color : @headerText;
|
||||
background-color : transparent;
|
||||
border-style : none;
|
||||
border-image : none;
|
||||
-webkit-column-span : 1;
|
||||
tr {
|
||||
background-color : transparent;
|
||||
}
|
||||
td,th {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Full Width
|
||||
.monster.wide{
|
||||
.useColumns(0.96);
|
||||
}
|
||||
|
||||
hr+hr+blockquote{
|
||||
.useColumns(0.96);
|
||||
}
|
||||
@@ -485,9 +525,15 @@ body {
|
||||
// * MUSTACHE DIVS/SPANS
|
||||
// *****************************/
|
||||
.phb3 {
|
||||
.block {
|
||||
break-inside : avoid;
|
||||
}
|
||||
.inline-block {
|
||||
display : block;
|
||||
}
|
||||
div {
|
||||
column-gap : 0.5cm; //Default spacing if a div uses multicolumns
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************
|
||||
@@ -512,6 +558,9 @@ body {
|
||||
// *****************************/
|
||||
.phb3 {
|
||||
.blank {
|
||||
height: 0.8em;
|
||||
height: 0.75em;
|
||||
}
|
||||
p + .blank {
|
||||
margin-top: -1em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ body {
|
||||
background-color : @monsterStatBackground;
|
||||
border-style : solid;
|
||||
border-width : 10px;
|
||||
border-image : @monsterBorderImage 10;
|
||||
border-image : @monsterBorderImageLegacy 10;
|
||||
h2{
|
||||
margin-top : -8px;
|
||||
margin-bottom : 0px;
|
||||
|
||||
2354
package-lock.json
generated
32
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "2.11.1",
|
||||
"version": "2.11.2",
|
||||
"engines": {
|
||||
"node": "14.15.x"
|
||||
},
|
||||
@@ -40,40 +40,42 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.13.10",
|
||||
"@babel/preset-env": "^7.13.10",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/preset-env": "^7.14.0",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"body-parser": "^1.19.0",
|
||||
"classnames": "^2.2.6",
|
||||
"codemirror": "^5.59.4",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.61.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"create-react-class": "^15.7.0",
|
||||
"dedent-tabs": "^0.9.0",
|
||||
"express": "^4.17.1",
|
||||
"express-async-handler": "^1.1.4",
|
||||
"express-static-gzip": "2.1.1",
|
||||
"fs-extra": "9.1.0",
|
||||
"googleapis": "67.1.1",
|
||||
"googleapis": "73.0.0",
|
||||
"jwt-simple": "^0.5.6",
|
||||
"less": "^3.13.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"mongoose": "^5.12.0",
|
||||
"nanoid": "3.1.21",
|
||||
"marked": "2.0.3",
|
||||
"markedLegacy": "npm:marked@^0.3.19",
|
||||
"marked": "2.0.1",
|
||||
"moment": "^2.29.1",
|
||||
"mongoose": "^5.12.7",
|
||||
"nanoid": "3.1.22",
|
||||
"nconf": "^0.11.2",
|
||||
"prop-types": "15.7.2",
|
||||
"query-string": "6.14.1",
|
||||
"query-string": "7.0.0",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-frame-component": "4.1.3",
|
||||
"react-router-dom": "5.2.0",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"superagent": "^6.1.0",
|
||||
"vitreum": "github:calculuschild/vitreum#21a8e1c9421f1d3a3b474c12f480feb2fbd28c5b"
|
||||
"vitreum": "git+https://github.com/calculuschild/vitreum.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.21.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"pico-check": "^2.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
262
server.js
@@ -8,6 +8,23 @@ const homebrewApi = require('./server/homebrew.api.js');
|
||||
const GoogleActions = require('./server/googleActions.js');
|
||||
const serveCompressedStaticAssets = require('./server/static-assets.mv.js');
|
||||
const sanitizeFilename = require('sanitize-filename');
|
||||
const asyncHandler = require('express-async-handler');
|
||||
|
||||
//Get the brew object from the HB database or Google Drive
|
||||
const getBrewFromId = asyncHandler(async (id, accessType)=>{
|
||||
if(accessType !== 'edit' && accessType !== 'share')
|
||||
throw ('Invalid Access Type when getting brew');
|
||||
let brew;
|
||||
if(id.length > 12) {
|
||||
const googleId = id.slice(0, -12);
|
||||
id = id.slice(-12);
|
||||
brew = await GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, id, accessType);
|
||||
} else {
|
||||
brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id });
|
||||
brew.sanatize(true);
|
||||
}
|
||||
return brew;
|
||||
});
|
||||
|
||||
app.use('/', serveCompressedStaticAssets(`${__dirname}/build`));
|
||||
|
||||
@@ -65,84 +82,33 @@ app.get('/robots.txt', (req, res)=>{
|
||||
return res.sendFile(`${__dirname}/robots.txt`);
|
||||
});
|
||||
|
||||
|
||||
//Source page
|
||||
app.get('/source/:id', (req, res)=>{
|
||||
if(req.params.id.length > 12) {
|
||||
const googleId = req.params.id.slice(0, -12);
|
||||
const shareId = req.params.id.slice(-12);
|
||||
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
|
||||
.then((brew)=>{
|
||||
const replaceStrings = { '&': '&', '<': '<', '>': '>' };
|
||||
let text = brew.text;
|
||||
for (const replaceStr in replaceStrings) {
|
||||
text = text.replaceAll(replaceStr, replaceStrings[replaceStr]);
|
||||
}
|
||||
text = `<code><pre style="white-space: pre-wrap;">${text}</pre></code>`;
|
||||
res.status(200).send(text);
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send('Can\'t get brew from Google');
|
||||
});
|
||||
} else {
|
||||
HomebrewModel.get({ shareId: req.params.id })
|
||||
.then((brew)=>{
|
||||
const replaceStrings = { '&': '&', '<': '<', '>': '>' };
|
||||
let text = brew.text;
|
||||
for (const replaceStr in replaceStrings) {
|
||||
text = text.replaceAll(replaceStr, replaceStrings[replaceStr]);
|
||||
}
|
||||
text = `<code><pre style="white-space: pre-wrap;">${text}</pre></code>`;
|
||||
res.status(200).send(text);
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(404).send('Could not find Homebrew with that id');
|
||||
});
|
||||
app.get('/source/:id', asyncHandler(async (req, res)=>{
|
||||
const brew = await getBrewFromId(req.params.id, 'share');
|
||||
|
||||
const replaceStrings = { '&': '&', '<': '<', '>': '>' };
|
||||
let text = brew.text;
|
||||
for (const replaceStr in replaceStrings) {
|
||||
text = text.replaceAll(replaceStr, replaceStrings[replaceStr]);
|
||||
}
|
||||
});
|
||||
text = `<code><pre style="white-space: pre-wrap;">${text}</pre></code>`;
|
||||
res.status(200).send(text);
|
||||
}));
|
||||
|
||||
//Download brew source page
|
||||
app.get('/download/:id', (req, res)=>{
|
||||
app.get('/download/:id', asyncHandler(async (req, res)=>{
|
||||
const brew = await getBrewFromId(req.params.id, 'share');
|
||||
const prefix = 'HB - ';
|
||||
|
||||
if(req.params.id.length > 12) {
|
||||
const googleId = req.params.id.slice(0, -12);
|
||||
const shareId = req.params.id.slice(-12);
|
||||
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
|
||||
.then((brew)=>{
|
||||
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
|
||||
if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
|
||||
res.set({
|
||||
'Cache-Control' : 'no-cache',
|
||||
'Content-Type' : 'text/plain',
|
||||
'Content-Disposition' : `attachment; filename="${fileName}.txt"`
|
||||
});
|
||||
res.status(200).send(brew.text);
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send('Can\'t get brew from Google');
|
||||
});
|
||||
} else {
|
||||
HomebrewModel.get({ shareId: req.params.id })
|
||||
.then((brew)=>{
|
||||
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
|
||||
if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
|
||||
res.set({
|
||||
'Cache-Control' : 'no-cache',
|
||||
'Content-Type' : 'text/plain',
|
||||
'Content-Disposition' : `attachment; filename="${fileName}.txt"`
|
||||
});
|
||||
res.status(200).send(brew.text);
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(404).send('Could not find Homebrew with that id');
|
||||
});
|
||||
}
|
||||
});
|
||||
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
|
||||
if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
|
||||
res.set({
|
||||
'Cache-Control' : 'no-cache',
|
||||
'Content-Type' : 'text/plain',
|
||||
'Content-Disposition' : `attachment; filename="${fileName}.txt"`
|
||||
});
|
||||
res.status(200).send(brew.text);
|
||||
}));
|
||||
|
||||
//User Page
|
||||
app.get('/user/:username', async (req, res, next)=>{
|
||||
@@ -170,123 +136,45 @@ app.get('/user/:username', async (req, res, next)=>{
|
||||
});
|
||||
|
||||
//Edit Page
|
||||
app.get('/edit/:id', (req, res, next)=>{
|
||||
app.get('/edit/:id', asyncHandler(async (req, res, next)=>{
|
||||
res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save.
|
||||
if(req.params.id.length > 12) {
|
||||
const googleId = req.params.id.slice(0, -12);
|
||||
const editId = req.params.id.slice(-12);
|
||||
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, editId, 'edit')
|
||||
.then((brew)=>{
|
||||
req.brew = brew; //TODO Need to sanitize later
|
||||
return next();
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send('Can\'t get brew from Google');
|
||||
});
|
||||
} else {
|
||||
HomebrewModel.get({ editId: req.params.id })
|
||||
.then((brew)=>{
|
||||
req.brew = brew.sanatize();
|
||||
return next();
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send(`Can't get that`);
|
||||
});
|
||||
}
|
||||
});
|
||||
const brew = await getBrewFromId(req.params.id, 'edit');
|
||||
req.brew = brew;
|
||||
return next();
|
||||
}));
|
||||
|
||||
//New Page
|
||||
app.get('/new/:id', (req, res, next)=>{
|
||||
if(req.params.id.length > 12) {
|
||||
const googleId = req.params.id.slice(0, -12);
|
||||
const shareId = req.params.id.slice(-12);
|
||||
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
|
||||
.then((brew)=>{
|
||||
req.brew = brew; //TODO Need to sanitize later
|
||||
return next();
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send('Can\'t get brew from Google');
|
||||
});
|
||||
} else {
|
||||
HomebrewModel.get({ shareId: req.params.id })
|
||||
.then((brew)=>{
|
||||
req.brew = brew;
|
||||
return next();
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send(`Can't get that`);
|
||||
});
|
||||
}
|
||||
});
|
||||
app.get('/new/:id', asyncHandler(async (req, res, next)=>{
|
||||
const brew = await getBrewFromId(req.params.id, 'share');
|
||||
req.brew = brew;
|
||||
return next();
|
||||
}));
|
||||
|
||||
//Share Page
|
||||
app.get('/share/:id', (req, res, next)=>{
|
||||
app.get('/share/:id', asyncHandler(async (req, res, next)=>{
|
||||
const brew = await getBrewFromId(req.params.id, 'share');
|
||||
|
||||
if(req.params.id.length > 12) {
|
||||
const googleId = req.params.id.slice(0, -12);
|
||||
const shareId = req.params.id.slice(-12);
|
||||
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
|
||||
.then((brew)=>{
|
||||
GoogleActions.increaseView(googleId, shareId, 'share', brew);
|
||||
return brew;
|
||||
})
|
||||
.then((brew)=>{
|
||||
req.brew = brew; //TODO Need to sanitize later
|
||||
return next();
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send('Can\'t get brew from Google');
|
||||
});
|
||||
await GoogleActions.increaseView(googleId, shareId, 'share', brew)
|
||||
.catch((err)=>{next(err);});
|
||||
} else {
|
||||
HomebrewModel.get({ shareId: req.params.id })
|
||||
.then((brew)=>{
|
||||
return brew.increaseView();
|
||||
})
|
||||
.then((brew)=>{
|
||||
req.brew = brew.sanatize(true);
|
||||
return next();
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send(`Can't get that`);
|
||||
});
|
||||
await brew.increaseView();
|
||||
}
|
||||
});
|
||||
|
||||
req.brew = brew;
|
||||
return next();
|
||||
}));
|
||||
|
||||
//Print Page
|
||||
app.get('/print/:id', (req, res, next)=>{
|
||||
if(req.params.id.length > 12) {
|
||||
const googleId = req.params.id.slice(0, -12);
|
||||
const shareId = req.params.id.slice(-12);
|
||||
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
|
||||
.then((brew)=>{
|
||||
req.brew = brew; //TODO Need to sanitize later
|
||||
return next();
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send('Can\'t get brew from Google');
|
||||
});
|
||||
} else {
|
||||
HomebrewModel.get({ shareId: req.params.id })
|
||||
.then((brew)=>{
|
||||
req.brew = brew.sanatize(true);
|
||||
return next();
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(400).send(`Can't get that`);
|
||||
});
|
||||
}
|
||||
});
|
||||
app.get('/print/:id', asyncHandler(async (req, res, next)=>{
|
||||
const brew = await getBrewFromId(req.params.id, 'share');
|
||||
req.brew = brew;
|
||||
return next();
|
||||
}));
|
||||
|
||||
//Render the page
|
||||
//const render = require('.build/render');
|
||||
const templateFn = require('./client/template.js');
|
||||
app.use((req, res)=>{
|
||||
const props = {
|
||||
@@ -303,11 +191,35 @@ app.use((req, res)=>{
|
||||
templateFn('homebrew', title = req.brew ? req.brew.title : '', props)
|
||||
.then((page)=>{ res.send(page); })
|
||||
.catch((err)=>{
|
||||
console.log('TEMPLATE ERROR');
|
||||
console.log(err);
|
||||
return res.sendStatus(500);
|
||||
});
|
||||
});
|
||||
|
||||
//v=====----- Error-Handling Middleware -----=====v//
|
||||
//Format Errors so all fields will be sent
|
||||
const replaceErrors = (key, value)=>{
|
||||
if(value instanceof Error) {
|
||||
const error = {};
|
||||
Object.getOwnPropertyNames(value).forEach(function (key) {
|
||||
error[key] = value[key];
|
||||
});
|
||||
return error;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const getPureError = (error)=>{
|
||||
return JSON.parse(JSON.stringify(error, replaceErrors));
|
||||
};
|
||||
|
||||
app.use((err, req, res, next)=>{
|
||||
const status = err.status || 500;
|
||||
console.error(err);
|
||||
res.status(status).send(getPureError(err));
|
||||
});
|
||||
//^=====--------------------------------------=====^//
|
||||
|
||||
const PORT = process.env.PORT || config.get('web_port') || 8000;
|
||||
app.listen(PORT);
|
||||
|
||||
@@ -240,6 +240,7 @@ GoogleActions = {
|
||||
},
|
||||
|
||||
readFileMetadata : async (auth, id, accessId, accessType)=>{
|
||||
|
||||
const drive = google.drive({ version: 'v3', auth: auth });
|
||||
|
||||
const obj = await drive.files.get({
|
||||
@@ -248,7 +249,7 @@ GoogleActions = {
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log('Error loading from Google');
|
||||
console.error(err);
|
||||
throw (err);
|
||||
return;
|
||||
});
|
||||
|
||||
@@ -345,7 +346,10 @@ GoogleActions = {
|
||||
increaseView : async (id, accessId, accessType, brew)=>{
|
||||
//service account because this is modifying another user's file properties
|
||||
//so we need extended scope
|
||||
const keys = JSON.parse(config.get('service_account'));
|
||||
const 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'];
|
||||
|
||||
|
||||
@@ -190,7 +190,6 @@ module.exports = {
|
||||
.replace(/(<dt>.*<\/dt><dd>.*<\/dd>\n?)+/gm, `<dl>$1</dl>\n\n`)
|
||||
.replace(/^}}/gm, '\n}}')
|
||||
.replace(/^({{[^\n]*)$/gm, '$1\n');
|
||||
console.log(rawBrewText);
|
||||
return Markdown(
|
||||
sanatizeScriptTags(rawBrewText),
|
||||
{ renderer: renderer }
|
||||
|
||||