0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-05 21:02:43 +00:00

Merge branch 'master' into experimentalNotificationDB

This commit is contained in:
G.Ambatte
2023-02-14 13:00:02 +13:00
committed by GitHub
45 changed files with 4124 additions and 3962 deletions

View File

@@ -21,24 +21,29 @@ below.
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/), version v16 or higher.
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 the 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. If you install any version over 6.0, you will have to install [MongoDB Shell](https://www.mongodb.com/try/download/shell).
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\6.0\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. 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. 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" three times to close all the windows. 1. Click "OK" three times to close all the windows.
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.insertOne({"title":"test"})` to create a blank document. You should see `{
acknowledged: true,
insertedId: ObjectId("63c2fce9e5ac5a94fe2410cf")
}`
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]):
@@ -51,11 +56,19 @@ git clone https://github.com/naturalcrit/homebrewery.git
Second, you will need to add the environment variable `NODE_ENV=local` to allow Second, you will need to add the environment variable `NODE_ENV=local` to allow
the project to run locally. the project to run locally.
You can set this temporarily in your shell of choice: You can set this **temporarily** (until you close the terminal) in your shell of choice with admin privileges:
* 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 / macOS: `export NODE_ENV=local` * Linux / macOS: `export NODE_ENV=local`
If you want to add this variable **permanently** the steps are as follows:
1. Search in Windows for "Advanced system settings" and open it.
1. Click "Environment variables".
1. In System Variables, click "New"
1. Click "New" and write `NODE_ENV` as a name and `local` as the value.
1. Click "OK" three times to close all the windows.
This can be undone at any time if needed.
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:
@@ -65,6 +78,13 @@ it using the two commands:
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.
If you had any issue at all, here are some links that may be useful:
- [Course](https://learn.mongodb.com/courses/m103-basic-cluster-administration) on cluster administration, useful for beginners
- [Mongo community forums](https://www.mongodb.com/community/forums/)
- Useful Stack Overflow links for your most probable errors: [1](https://stackoverflow.com/questions/44962540/mongod-and-mongo-commands-not-working-on-windows-10), [2](https://stackoverflow.com/questions/15053893/mongo-command-not-recognized-when-trying-to-connect-to-a-mongodb-server/41507803#41507803), [3](https://stackoverflow.com/questions/51224959/mongo-is-not-recognized-as-an-internal-or-external-command-operable-program-o)
If you still have problems, post in [Our Subreddit](https://www.reddit.com/r/homebrewery/) and we will help you.
### Running the application via Docker ### Running the application via Docker
Please see the docs here: [README.DOCKER.md](./README.DOCKER.md) Please see the docs here: [README.DOCKER.md](./README.DOCKER.md)

View File

@@ -52,16 +52,20 @@ pre {
font-family: 'Open Sans'; font-family: 'Open Sans';
font-size: 0.9em; font-size: 0.9em;
} }
.page {
padding-bottom: 1.5cm;
}
``` ```
## 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).
### v3.6.0 ### Friday 23/01/2023 - v3.6.0
{{taskList {{taskList
##### calculuschild ##### calculuschild
* [x] Fix Google Drive brews sometimes duplicating * [x] Fix Google Drive brews sometimes duplicating
Fixes issues [#2603](https://github.com/naturalcrit/homebrewery/issues/2603) Fixes issues [#2603](https://github.com/naturalcrit/homebrewery/issues/2603)
@@ -69,9 +73,17 @@ Fixes issues [#2603](https://github.com/naturalcrit/homebrewery/issues/2603)
* [x] Add unit tests with full coverage for the Homebrewery API * [x] Add unit tests with full coverage for the Homebrewery API
* [x] Add message to refresh the browser if the user is missing an update to the Homebrewery
Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583)
##### G-Ambatte ##### G-Ambatte
* [x] Add Themes directory to development server watchlist. * [x] Auto-compile Themes CSS on development server
##### 5e-Cleric
* [x] Fix cloned brews inheriting the parent view count
}} }}
### Friday 23/12/2022 - v3.5.0 ### Friday 23/12/2022 - v3.5.0
@@ -89,22 +101,6 @@ Fixes issues [#1987](https://github.com/naturalcrit/homebrewery/issues/1987)
\page \page
### Monday 05/12/2022 - v3.4.1
{{taskList
##### G-Ambatte
* [x] Fix Account page incorrect last login time
Fixes issues [#2521](https://github.com/naturalcrit/homebrewery/issues/2521)
##### Gazook
* [x] Fix crashing on iOS and Safari browsers
Fixes issues [#2531](https://github.com/naturalcrit/homebrewery/issues/2531)
}}
### Saturday 10/12/2022 - v3.4.2 ### Saturday 10/12/2022 - v3.4.2
{{taskList {{taskList

View File

@@ -134,7 +134,7 @@ const BrewRenderer = createClass({
renderStyle : function() { renderStyle : function() {
if(!this.props.style) return; if(!this.props.style) return;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.style} </style>` }} />; return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.props.style}\n} </style>` }} />;
}, },
renderPage : function(pageText, index){ renderPage : function(pageText, index){

View File

@@ -32,6 +32,7 @@ const Editor = createClass({
onTextChange : ()=>{}, onTextChange : ()=>{},
onStyleChange : ()=>{}, onStyleChange : ()=>{},
onMetaChange : ()=>{}, onMetaChange : ()=>{},
reportError : ()=>{},
renderer : 'legacy' renderer : 'legacy'
}; };
@@ -291,7 +292,8 @@ const Editor = createClass({
rerenderParent={this.rerenderParent} /> rerenderParent={this.rerenderParent} />
<MetadataEditor <MetadataEditor
metadata={this.props.brew} metadata={this.props.brew}
onChange={this.props.onMetaChange} /> onChange={this.props.onMetaChange}
reportError={this.props.reportError}/>
</>; </>;
} }
}, },

View File

@@ -4,7 +4,7 @@ const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const request = require('superagent'); const request = require('../../utils/request-middleware.js');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx'); const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
@@ -37,7 +37,8 @@ const MetadataEditor = createClass({
renderer : 'legacy', renderer : 'legacy',
theme : '5ePHB' theme : '5ePHB'
}, },
onChange : ()=>{} onChange : ()=>{},
reportError : ()=>{}
}; };
}, },
@@ -121,8 +122,12 @@ const MetadataEditor = createClass({
request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`) request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`)
.send() .send()
.end(function(err, res){ .end((err, res)=>{
window.location.href = '/'; if(err) {
this.props.reportError(err);
} else {
window.location.href = '/';
}
}); });
}, },
@@ -184,6 +189,10 @@ const MetadataEditor = createClass({
return <div className='item' key={''} onClick={()=>this.handleTheme(theme)} title={''}> return <div className='item' key={''} onClick={()=>this.handleTheme(theme)} title={''}>
{`${theme.renderer} : ${theme.name}`} {`${theme.renderer} : ${theme.name}`}
<img src={`/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`}/> <img src={`/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`}/>
<div className='preview'>
<h6>{`${theme.name}`} preview</h6>
<img src={`/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`}/>
</div>
</div>; </div>;
}); });
}; };
@@ -193,14 +202,14 @@ const MetadataEditor = createClass({
if(this.props.metadata.renderer == 'legacy') { if(this.props.metadata.renderer == 'legacy') {
dropdown = dropdown =
<Nav.dropdown className='disabled' trigger='disabled'> <Nav.dropdown className='disabled value' trigger='disabled'>
<div> <div>
{`Themes are not supported in the Legacy Renderer`} <i className='fas fa-caret-down'></i> {`Themes are not supported in the Legacy Renderer`} <i className='fas fa-caret-down'></i>
</div> </div>
</Nav.dropdown>; </Nav.dropdown>;
} else { } else {
dropdown = dropdown =
<Nav.dropdown trigger='click'> <Nav.dropdown className='value' trigger='click'>
<div> <div>
{`${_.upperFirst(currentTheme.renderer)} : ${currentTheme.name}`} <i className='fas fa-caret-down'></i> {`${_.upperFirst(currentTheme.renderer)} : ${currentTheme.name}`} <i className='fas fa-caret-down'></i>
</div> </div>

View File

@@ -35,7 +35,6 @@
flex-direction: column; flex-direction: column;
flex: 5 0 200px; flex: 5 0 200px;
gap: 10px; gap: 10px;
} }
.field{ .field{
display : flex; display : flex;
@@ -159,7 +158,6 @@
font-size : 13.33px; font-size : 13.33px;
.navDropdownContainer { .navDropdownContainer {
background-color : white; background-color : white;
width : 100%;
position : relative; position : relative;
z-index : 500; z-index : 500;
&.disabled { &.disabled {
@@ -182,24 +180,51 @@
} }
.navDropdown { .navDropdown {
box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3); box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3);
position : absolute; position : absolute;
width : 100%; width : 100%;
.item { .item {
padding : 3px 3px; padding : 3px 3px;
border-top : 1px solid rgb(118, 118, 118); border-top : 1px solid rgb(118, 118, 118);
position : relative; position : relative;
overflow : hidden; overflow : visible;
background-color : white; background-color : white;
.preview {
display : flex;
flex-direction: column;
background : #ccc;
border-radius : 5px;
box-shadow : 0 0 5px black;
width : 200px;
color :black;
position : absolute;
top : 0;
right : 0;
opacity : 0;
transition : opacity 250ms ease;
z-index : 1;
overflow :hidden;
h6 {
font-weight : 900;
padding-inline:1em;
padding-block :.5em;
border-bottom :2px solid hsl(0,0%,40%);
}
}
&:hover { &:hover {
background-color : @blue; background-color : @blue;
color : white; color : white;
} }
img { &:hover > .preview {
mask-image : linear-gradient(90deg, transparent, black 20%); opacity: 1;
}
>img {
mask-image : linear-gradient(90deg, transparent, black 20%);
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%); -webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
position : absolute; position : absolute;
left : ~"max(100px, 100% - 300px)"; right : 0;
top : 0px; top : 0px;
width : 50%;
height : 100%;
} }
} }
} }

View File

@@ -163,15 +163,22 @@ const SnippetGroup = createClass({
onSnippetClick : function(){}, onSnippetClick : function(){},
}; };
}, },
handleSnippetClick : function(snippet){ handleSnippetClick : function(e, snippet){
e.stopPropagation();
this.props.onSnippetClick(execute(snippet.gen, this.props.brew)); this.props.onSnippetClick(execute(snippet.gen, this.props.brew));
}, },
renderSnippets : function(){ renderSnippets : function(snippets){
return _.map(this.props.snippets, (snippet)=>{ return _.map(snippets, (snippet)=>{
return <div className='snippet' key={snippet.name} onClick={()=>this.handleSnippetClick(snippet)}> return <div className='snippet' key={snippet.name} onClick={(e)=>this.handleSnippetClick(e, snippet)}>
<i className={snippet.icon} /> <i className={snippet.icon} />
{snippet.name} {snippet.name}
{snippet.subsnippets && <>
<i className='fas fa-caret-right'></i>
<div className='dropdown side'>
{this.renderSnippets(snippet.subsnippets)}
</div></>}
</div>; </div>;
}); });
}, },
@@ -182,7 +189,7 @@ const SnippetGroup = createClass({
<span className='groupName'>{this.props.groupName}</span> <span className='groupName'>{this.props.groupName}</span>
</div> </div>
<div className='dropdown'> <div className='dropdown'>
{this.renderSnippets()} {this.renderSnippets(this.props.snippets)}
</div> </div>
</div>; </div>;
}, },

View File

@@ -1,4 +1,4 @@
@import (less) './client/icons/customIcons.less';
.snippetBar{ .snippetBar{
@menuHeight : 25px; @menuHeight : 25px;
position : relative; position : relative;
@@ -83,7 +83,7 @@
.snippetGroup{ .snippetGroup{
border-right : 1px solid black; border-right : 1px solid black;
&:hover{ &:hover{
.dropdown{ &>.dropdown{
visibility : visible; visibility : visible;
} }
} }
@@ -97,15 +97,30 @@
background-color : #ddd; background-color : #ddd;
.snippet{ .snippet{
.animate(background-color); .animate(background-color);
width : max-content;
padding : 5px; padding : 5px;
cursor : pointer; cursor : pointer;
font-size : 10px; font-size : 10px;
i{ i{
margin-right : 8px; margin-right : 8px;
font-size : 1.2em; font-size : 1.2em;
height : 1.2em;
&~i{
margin-right: 0;
margin-left: 8px;
}
} }
&:hover{ &:hover{
background-color : #999; background-color : #999;
&>.dropdown{
visibility : visible;
&.side {
left: 100%;
top: 0%;
margin-left:0;
box-shadow: -1px 1px 2px 0px #999;
}
}
} }
} }
} }

View File

@@ -0,0 +1,85 @@
require('./error-navitem.less');
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const createClass = require('create-react-class');
const ErrorNavItem = createClass({
getDefaultProps : function() {
return {
error : '',
parent : null
};
},
render : function() {
const clearError = ()=>{
const state = {
error : null
};
if(this.props.parent.state.isSaving) {
state.isSaving = false;
}
this.props.parent.setState(state);
};
const error = this.props.error;
const response = error.response;
const status = response.status;
const message = response.body?.message;
let errMsg = '';
try {
errMsg += `${error.toString()}\n\n`;
errMsg += `\`\`\`\n${error.stack}\n`;
errMsg += `${JSON.stringify(response.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch (e){}
if(status === 409) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Conflict: please refresh to get latest changes'}
</div>
</Nav.item>;
} else if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'}
</div>
</Nav.item>;
}
if(response.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer' href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
}
});
module.exports = ErrorNavItem;

View File

@@ -0,0 +1,77 @@
.navItem {
&.error {
position : relative;
background-color : @red;
}
.errorContainer{
animation-name: glideDown;
animation-duration: 0.4s;
position : absolute;
top : 100%;
left : 50%;
z-index : 1000;
width : 140px;
padding : 3px;
color : white;
background-color : #333;
border : 3px solid #444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
text-align : center;
font-size : 10px;
font-weight : 800;
text-transform : uppercase;
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
}
}

View File

@@ -4,7 +4,7 @@ const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const moment = require('moment'); const moment = require('moment');
const request = require('superagent'); const request = require('../../../../utils/request-middleware.js');
const googleDriveIcon = require('../../../../googleDrive.png'); const googleDriveIcon = require('../../../../googleDrive.png');
const dedent = require('dedent-tabs').default; const dedent = require('dedent-tabs').default;
@@ -18,7 +18,8 @@ const BrewItem = createClass({
description : '', description : '',
authors : [], authors : [],
stubbed : true stubbed : true
} },
reportError : ()=>{}
}; };
}, },
@@ -33,8 +34,12 @@ const BrewItem = createClass({
request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`) request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`)
.send() .send()
.end(function(err, res){ .end((err, res)=>{
location.reload(); if(err) {
this.props.reportError(err);
} else {
location.reload();
}
}); });
}, },

View File

@@ -23,7 +23,8 @@ const ListPage = createClass({
brews : [] brews : []
} }
], ],
navItems : <></> navItems : <></>,
reportError : null
}; };
}, },
getInitialState : function() { getInitialState : function() {
@@ -81,7 +82,7 @@ const ListPage = createClass({
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>; if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
return _.map(brews, (brew, idx)=>{ return _.map(brews, (brew, idx)=>{
return <BrewItem brew={brew} key={idx}/>; return <BrewItem brew={brew} key={idx} reportError={this.props.reportError}/>;
}); });
}, },
@@ -218,12 +219,13 @@ const ListPage = createClass({
render : function(){ render : function(){
return <div className='listPage sitePage'> return <div className='listPage sitePage'>
<style>@layer V3_5ePHB, bundle;</style>
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet'/> <link href='/themes/V3/5ePHB/style.css' rel='stylesheet'/>
{this.props.navItems} {this.props.navItems}
{this.renderSortOptions()} {this.renderSortOptions()}
<div className='content V3'> <div className='content V3'>
<div className='phb page'> <div className='page'>
{this.renderBrewCollection(this.state.brewCollection)} {this.renderBrewCollection(this.state.brewCollection)}
</div> </div>
</div> </div>

View File

@@ -10,14 +10,14 @@
-moz-column-width : auto; -moz-column-width : auto;
-webkit-column-gap : auto; -webkit-column-gap : auto;
-moz-column-gap : auto; -moz-column-gap : auto;
height : auto;
min-height : 279.4mm;
margin : 20px auto;
} }
.listPage{ .listPage{
.content{ .content{
.phb{ .page{
.noColumns(); .noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer
height : auto;
min-height : 279.4mm;
margin : 20px auto;
&::after{ &::after{
display : none; display : none;
} }

View File

@@ -3,7 +3,7 @@ require('./editPage.less');
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 request = require('superagent'); const request = require('../../utils/request-middleware.js');
const { Meta } = require('vitreum/headtags'); const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
@@ -12,6 +12,7 @@ const Navbar = require('../../navbar/navbar.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx'); const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx');
const PrintLink = require('../../navbar/print.navitem.jsx'); const PrintLink = require('../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
@@ -45,7 +46,7 @@ const EditPage = createClass({
alertLoginToTransfer : false, alertLoginToTransfer : false,
saveGoogle : this.props.brew.googleId ? true : false, saveGoogle : this.props.brew.googleId ? true : false,
confirmGoogleTransfer : false, confirmGoogleTransfer : false,
errors : null, error : null,
htmlErrors : Markdown.validate(this.props.brew.text), htmlErrors : Markdown.validate(this.props.brew.text),
url : '', url : '',
autoSave : true, autoSave : true,
@@ -60,7 +61,6 @@ const EditPage = createClass({
url : window.location.href url : window.location.href
}); });
this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy
this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{ this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{
@@ -157,7 +157,10 @@ const EditPage = createClass({
this.setState((prevState)=>({ this.setState((prevState)=>({
confirmGoogleTransfer : !prevState.confirmGoogleTransfer confirmGoogleTransfer : !prevState.confirmGoogleTransfer
})); }));
this.clearErrors(); this.setState({
error : null,
isSaving : false
});
}, },
closeAlerts : function(event){ closeAlerts : function(event){
@@ -173,24 +176,16 @@ const EditPage = createClass({
this.setState((prevState)=>({ this.setState((prevState)=>({
saveGoogle : !prevState.saveGoogle, saveGoogle : !prevState.saveGoogle,
isSaving : false, isSaving : false,
errors : null error : null
}), ()=>this.save()); }), ()=>this.save());
}, },
clearErrors : function(){
this.setState({
errors : null,
isSaving : false
});
},
save : async function(){ save : async function(){
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
this.setState((prevState)=>({ this.setState((prevState)=>({
isSaving : true, isSaving : true,
errors : null, error : null,
htmlErrors : Markdown.validate(prevState.brew.text) htmlErrors : Markdown.validate(prevState.brew.text)
})); }));
@@ -205,8 +200,9 @@ const EditPage = createClass({
.send(brew) .send(brew)
.catch((err)=>{ .catch((err)=>{
console.log('Error Updating Local Brew'); console.log('Error Updating Local Brew');
this.setState({ errors: err }); this.setState({ error: err });
}); });
if(!res) return;
this.savedBrew = res.body; this.savedBrew = res.body;
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
@@ -266,77 +262,6 @@ const EditPage = createClass({
}, },
renderSaveButton : function(){ renderSaveButton : function(){
if(this.state.errors){
let errMsg = '';
try {
errMsg += `${this.state.errors.toString()}\n\n`;
errMsg += `\`\`\`\n${this.state.errors.stack}\n`;
errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
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.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}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<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.error.status === 409) {
const message = this.state.errors.response.body?.message;
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
{message ? message : 'Conflict: please refresh to get latest changes'}
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer'
href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
}
if(this.state.autoSaveWarning && this.hasChanges()){ if(this.state.autoSaveWarning && this.hasChanges()){
this.setAutosaveWarning(); this.setAutosaveWarning();
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60); const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
@@ -380,6 +305,12 @@ const EditPage = createClass({
this.warningTimer; this.warningTimer;
}, },
errorReported : function(error) {
this.setState({
error
});
},
renderAutoSaveButton : function(){ renderAutoSaveButton : function(){
return <Nav.item onClick={this.handleAutoSave}> return <Nav.item onClick={this.handleAutoSave}>
Autosave <i className={this.state.autoSave ? 'fas fa-power-off active' : 'fas fa-power-off'}></i> Autosave <i className={this.state.autoSave ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
@@ -424,10 +355,13 @@ const EditPage = createClass({
<Nav.section> <Nav.section>
{this.renderGoogleDriveIcon()} {this.renderGoogleDriveIcon()}
<Nav.dropdown className='save-menu'> {this.state.error ?
{this.renderSaveButton()} <ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
{this.renderAutoSaveButton()} <Nav.dropdown className='save-menu'>
</Nav.dropdown> {this.renderSaveButton()}
{this.renderAutoSaveButton()}
</Nav.dropdown>
}
<NewBrew /> <NewBrew />
<HelpNavItem/> <HelpNavItem/>
<Nav.dropdown> <Nav.dropdown>
@@ -465,6 +399,7 @@ const EditPage = createClass({
onTextChange={this.handleTextChange} onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange} onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange} onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer} renderer={this.state.brew.renderer}
/> />
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} errors={this.state.htmlErrors} /> <BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} errors={this.state.htmlErrors} />

View File

@@ -13,10 +13,6 @@
cursor : initial; cursor : initial;
color : #666; color : #666;
} }
&.error{
position : relative;
background-color : @red;
}
} }
.googleDriveStorage { .googleDriveStorage {
position : relative; position : relative;
@@ -26,74 +22,4 @@
padding : 0px; padding : 0px;
margin : -5px; margin : -5px;
} }
.errorContainer{
animation-name: glideDown;
animation-duration: 0.4s;
position : absolute;
top : 100%;
left : 50%;
z-index : 500;
width : 140px;
padding : 3px;
color : white;
background-color : #333;
border : 3px solid #444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
text-align : center;
font-size : 10px;
font-weight : 800;
text-transform : uppercase;
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
}
} }

View File

@@ -3,7 +3,7 @@ const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const request = require('superagent'); const request = require('../../utils/request-middleware.js');
const { Meta } = require('vitreum/headtags'); const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
@@ -12,6 +12,7 @@ const NewBrewItem = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const AccountNavItem = require('../../navbar/account.navitem.jsx'); const AccountNavItem = require('../../navbar/account.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
@@ -31,14 +32,18 @@ const HomePage = createClass({
getInitialState : function() { getInitialState : function() {
return { return {
brew : this.props.brew, brew : this.props.brew,
welcomeText : this.props.brew.text welcomeText : this.props.brew.text,
error : undefined
}; };
}, },
handleSave : function(){ handleSave : function(){
request.post('/api') request.post('/api')
.send(this.state.brew) .send(this.state.brew)
.end((err, res)=>{ .end((err, res)=>{
if(err) return; if(err) {
this.setState({ error: err });
return;
}
const brew = res.body; const brew = res.body;
window.location = `/edit/${brew.editId}`; window.location = `/edit/${brew.editId}`;
}); });
@@ -54,6 +59,10 @@ const HomePage = createClass({
renderNavbar : function(){ renderNavbar : function(){
return <Navbar ver={this.props.ver}> return <Navbar ver={this.props.ver}>
<Nav.section> <Nav.section>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
null
}
<NewBrewItem /> <NewBrewItem />
<HelpNavItem /> <HelpNavItem />
<RecentNavItem /> <RecentNavItem />

View File

@@ -40,4 +40,11 @@
right : 350px; right : 350px;
} }
} }
.navItem.save{
background-color: @orange;
&:hover{
background-color: @green;
}
}
} }

View File

@@ -3,13 +3,14 @@ require('./newPage.less');
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 request = require('superagent'); const request = require('../../utils/request-middleware.js');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx'); const Navbar = require('../../navbar/navbar.jsx');
const AccountNavItem = require('../../navbar/account.navitem.jsx'); const AccountNavItem = require('../../navbar/account.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const HelpNavItem = require('../../navbar/help.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx');
@@ -39,7 +40,7 @@ const NewPage = createClass({
brew : brew, brew : brew,
isSaving : false, isSaving : false,
saveGoogle : (global.account && global.account.googleId ? true : false), saveGoogle : (global.account && global.account.googleId ? true : false),
errors : null, error : null,
htmlErrors : Markdown.validate(brew.text) htmlErrors : Markdown.validate(brew.text)
}; };
}, },
@@ -122,14 +123,6 @@ const NewPage = createClass({
})); }));
}, },
clearErrors : function(){
this.setState({
errors : null,
isSaving : false
});
},
save : async function(){ save : async function(){
this.setState({ this.setState({
isSaving : true isSaving : true
@@ -152,7 +145,7 @@ const NewPage = createClass({
.send(brew) .send(brew)
.catch((err)=>{ .catch((err)=>{
console.log(err); console.log(err);
this.setState({ isSaving: false, errors: err }); this.setState({ isSaving: false, error: err });
}); });
if(!res) return; if(!res) return;
@@ -164,67 +157,6 @@ const NewPage = createClass({
}, },
renderSaveButton : function(){ renderSaveButton : function(){
if(this.state.errors){
let errMsg = '';
try {
errMsg += `${this.state.errors.toString()}\n\n`;
errMsg += `\`\`\`\n${this.state.errors.stack}\n`;
errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
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.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}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<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>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer'
href={`https://github.com/naturalcrit/homebrewery/issues/new?body=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
}
if(this.state.isSaving){ if(this.state.isSaving){
return <Nav.item icon='fas fa-spinner fa-spin' className='save'> return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
save... save...
@@ -254,7 +186,10 @@ const NewPage = createClass({
</Nav.section> </Nav.section>
<Nav.section> <Nav.section>
{this.renderSaveButton()} {this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
this.renderSaveButton()
}
{this.renderLocalPrintButton()} {this.renderLocalPrintButton()}
<HelpNavItem /> <HelpNavItem />
<RecentNavItem /> <RecentNavItem />

View File

@@ -4,79 +4,5 @@
&:hover{ &:hover{
background-color: @green; background-color: @green;
} }
&.error{
position : relative;
background-color : @red;
}
}
.errorContainer{
animation-name: glideDown;
animation-duration: 0.4s;
position : absolute;
top : 100%;
left : 50%;
z-index : 100000;
width : 140px;
padding : 3px;
color : white;
background-color : #333;
border : 3px solid #444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
text-align : center;
font-size : 10px;
font-weight : 800;
text-transform : uppercase;
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
} }
} }

View File

@@ -60,7 +60,7 @@ const PrintPage = createClass({
renderStyle : function() { renderStyle : function() {
if(!this.state.brew.style) return; if(!this.state.brew.style) return;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.state.brew.style} </style>` }} />; return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.state.brew.style}\n} </style>` }} />;
}, },
renderPages : function(){ renderPages : function(){

View File

@@ -12,6 +12,7 @@ const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const Account = require('../../navbar/account.navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx'); const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const UserPage = createClass({ const UserPage = createClass({
displayName : 'UserPage', displayName : 'UserPage',
@@ -19,7 +20,8 @@ const UserPage = createClass({
return { return {
username : '', username : '',
brews : [], brews : [],
query : '' query : '',
error : null
}; };
}, },
getInitialState : function() { getInitialState : function() {
@@ -50,10 +52,19 @@ const UserPage = createClass({
brewCollection : brewCollection brewCollection : brewCollection
}; };
}, },
errorReported : function(error) {
this.setState({
error
});
},
navItems : function() { navItems : function() {
return <Navbar> return <Navbar>
<Nav.section> <Nav.section>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
null
}
<NewBrew /> <NewBrew />
<HelpNavItem /> <HelpNavItem />
<RecentNavItem /> <RecentNavItem />
@@ -63,7 +74,7 @@ const UserPage = createClass({
}, },
render : function(){ render : function(){
return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query}></ListPage>; return <ListPage brewCollection={this.state.brewCollection} navItems={this.navItems()} query={this.props.query} reportError={this.errorReported}></ListPage>;
} }
}); });

View File

@@ -0,0 +1,12 @@
const request = require('superagent');
const addHeader = (request)=>request.set('Homebrewery-Version', global.version);
const requestMiddleware = {
get : (path)=>addHeader(request.get(path)),
put : (path)=>addHeader(request.put(path)),
post : (path)=>addHeader(request.post(path)),
delete : (path)=>addHeader(request.delete(path)),
};
module.exports = requestMiddleware;

View File

@@ -0,0 +1,15 @@
.fac {
display : inline-block;
}
.position-top-left {
content: url('../icons/position-top-left.svg');
}
.position-top-right {
content: url('../icons/position-top-right.svg');
}
.position-bottom-left {
content: url('../icons/position-bottom-left.svg');
}
.position-bottom-right {
content: url('../icons/position-bottom-right.svg');
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 495"><path fill-opacity=".995" d="M495 135.49V0H359.51v135.49M482.72 11.37v113.26H371.9V11.37zM135.49 315.245v-135.49H0v135.49m123.21-124.12v113.26H12.39v-113.26zm12.28-55.635V0H0v135.49M123.21 11.37v113.26H12.39V11.37zm192.035 124.12V0h-135.49v135.49m123.21-124.12v113.26h-110.82V11.37zm12.28 303.875v-135.49h-135.49v135.49m123.21-124.12v113.26h-110.82v-113.26zM495 315.245v-135.49H359.51v135.49m123.21-124.12v113.26H371.9v-113.26zM135.49 495V359.51H0V495Zm179.755 0V359.51h-135.49V495m123.21-124.12v113.26h-110.82V370.88zM495 495V359.51H359.51V495m123.21-124.12v113.26H371.9V370.88z"/></svg>

After

Width:  |  Height:  |  Size: 650 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 495"><g fill-opacity=".995"><path d="M135.49 0H0v135.49h135.49M11.37 12.28h113.26V123.1H11.37zM315.245 359.51h-135.49V495h135.49m-124.12-123.21h113.26v110.82h-113.26zM135.49 359.51H0V495h135.49M11.37 371.79h113.26v110.82H11.37zM135.49 179.755H0v135.49h135.49M11.37 192.035h113.26v110.82H11.37zM315.245 179.755h-135.49v135.49h135.49m-124.12-123.21h113.26v110.82h-113.26zM315.245 0h-135.49v135.49h135.49M191.125 12.28h113.26V123.1h-113.26zM495 359.51H359.51V495H495ZM495 179.755H359.51v135.49H495m-124.12-123.21h113.26v110.82H370.88zM495 0H359.51v135.49H495M370.88 12.28h113.26V123.1H370.88z"/></g></svg>

After

Width:  |  Height:  |  Size: 659 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 495"><path fill-opacity=".995" d="M359.51 495H495V359.51H359.51m124.12 123.21H370.37V371.9h113.26zM179.755 135.49h135.49V0h-135.49m124.12 123.21h-113.26V12.39h113.26zM359.51 135.49H495V0H359.51m124.12 123.21H370.37V12.39h113.26zM359.51 315.245H495v-135.49H359.51m124.12 123.21H370.37v-110.82h113.26zM179.755 315.245h135.49v-135.49h-135.49m124.12 123.21h-113.26v-110.82h113.26zM179.755 495h135.49V359.51h-135.49m124.12 123.21h-113.26V371.9h113.26zM0 135.49h135.49V0H0ZM0 315.245h135.49v-135.49H0m124.12 123.21H10.86v-110.82h113.26zM0 495h135.49V359.51H0m124.12 123.21H10.86V371.9h113.26z"/></svg>

After

Width:  |  Height:  |  Size: 652 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 495"><g fill-opacity=".995"><path d="M0 359.51V495h135.49V359.51M12.28 483.63V370.37H123.1v113.26zM359.51 179.755v135.49H495v-135.49m-123.21 124.12v-113.26h110.82v113.26zM359.51 359.51V495H495V359.51M371.79 483.63V370.37h110.82v113.26zM179.755 359.51V495h135.49V359.51m-123.21 124.12V370.37h110.82v113.26zM179.755 179.755v135.49h135.49v-135.49m-123.21 124.12v-113.26h110.82v113.26zM0 179.755v135.49h135.49v-135.49M12.28 303.875v-113.26H123.1v113.26zM359.51 0v135.49H495V0ZM179.755 0v135.49h135.49V0m-123.21 124.12V10.86h110.82v113.26zM0 0v135.49h135.49V0M12.28 124.12V10.86H123.1v113.26z"/></g></svg>

After

Width:  |  Height:  |  Size: 657 B

3218
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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.5.0", "version": "3.6.0",
"engines": { "engines": {
"node": "16.11.x" "node": "16.11.x"
}, },
@@ -51,10 +51,10 @@
"lines" : 25 "lines" : 25
}, },
"server/homebrew.api.js" : { "server/homebrew.api.js" : {
"statements" : 71, "statements" : 65,
"branches" : 54, "branches" : 50,
"functions" : 66, "functions" : 60,
"lines" : 73 "lines" : 70
} }
} }
}, },
@@ -87,26 +87,26 @@
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
"less": "^3.13.1", "less": "^3.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "4.2.5", "marked": "4.2.12",
"marked-extended-tables": "^1.0.5", "marked-extended-tables": "^1.0.5",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.4", "moment": "^2.29.4",
"mongoose": "^6.8.3", "mongoose": "^6.9.0",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"nconf": "^0.12.0", "nconf": "^0.12.0",
"npm": "^8.10.0", "npm": "^8.10.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-frame-component": "4.1.3", "react-frame-component": "4.1.3",
"react-router-dom": "6.6.1", "react-router-dom": "6.8.0",
"sanitize-filename": "1.6.3", "sanitize-filename": "1.6.3",
"superagent": "^6.1.0", "superagent": "^6.1.0",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.31.0", "eslint": "^8.33.0",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.32.2",
"jest": "^29.2.2", "jest": "^29.4.1",
"supertest": "^6.3.3" "supertest": "^6.3.3"
} }
} }

View File

@@ -20,7 +20,8 @@ const transforms = {
}; };
const build = async ({ bundle, render, ssr })=>{ const build = async ({ bundle, render, ssr })=>{
const css = await lessTransform.generate({ paths: './shared' }); let css = await lessTransform.generate({ paths: './shared' });
css = `@layer bundle {\n${css}\n}`;
await fs.outputFile('./build/homebrew/bundle.css', css); await fs.outputFile('./build/homebrew/bundle.css', css);
await fs.outputFile('./build/homebrew/bundle.js', bundle); await fs.outputFile('./build/homebrew/bundle.js', bundle);
await fs.outputFile('./build/homebrew/ssr.js', ssr); await fs.outputFile('./build/homebrew/ssr.js', ssr);
@@ -72,6 +73,7 @@ fs.emptyDirSync('./build');
themeData.path = dir; themeData.path = dir;
themes.V3[dir] = (themeData); themes.V3[dir] = (themeData);
fs.copy(`./themes/V3/${dir}/dropdownTexture.png`, `./build/themes/V3/${dir}/dropdownTexture.png`); fs.copy(`./themes/V3/${dir}/dropdownTexture.png`, `./build/themes/V3/${dir}/dropdownTexture.png`);
fs.copy(`./themes/V3/${dir}/dropdownPreview.png`, `./build/themes/V3/${dir}/dropdownPreview.png`);
const src = `./themes/V3/${dir}/style.less`; const src = `./themes/V3/${dir}/style.less`;
((outputDirectory)=>{ ((outputDirectory)=>{
less.render(fs.readFileSync(src).toString(), { less.render(fs.readFileSync(src).toString(), {
@@ -95,6 +97,7 @@ fs.emptyDirSync('./build');
// Move assets // Move assets
await fs.copy('./themes/fonts', './build/fonts'); await fs.copy('./themes/fonts', './build/fonts');
await fs.copy('./themes/assets', './build/assets'); await fs.copy('./themes/assets', './build/assets');
await fs.copy('./client/icons', './build/icons');
//v==----------------------------- BUNDLE PACKAGES --------------------------------==v// //v==----------------------------- BUNDLE PACKAGES --------------------------------==v//

View File

@@ -294,7 +294,15 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), (req, res, next)=>{
app.get('/new/:id', asyncHandler(getBrew('share')), (req, res, next)=>{ app.get('/new/:id', asyncHandler(getBrew('share')), (req, res, next)=>{
sanitizeBrew(req.brew, 'share'); sanitizeBrew(req.brew, 'share');
splitTextStyleAndMetadata(req.brew); splitTextStyleAndMetadata(req.brew);
req.brew.title = `CLONE - ${req.brew.title}`; const brew = {
shareId : req.brew.shareId,
title : `CLONE - ${req.brew.title}`,
text : req.brew.text,
style : req.brew.style,
renderer : req.brew.renderer,
theme : req.brew.theme
};
req.brew = _.defaults(brew, DEFAULT_BREW);
req.ogMeta = { ...defaultMetaTags, req.ogMeta = { ...defaultMetaTags,
title : 'New', title : 'New',

View File

@@ -16,6 +16,7 @@ const DEFAULT_BREW = {
tags : [], tags : [],
systems : [], systems : [],
thumbnail : '', thumbnail : '',
views : 0,
published : false, published : false,
pageCount : 1, pageCount : 1,
gDrive : false, gDrive : false,

View File

@@ -338,6 +338,7 @@ If you believe you should have access to this brew, ask the file owner to invite
} }
}; };
router.use('/api', require('./middleware/check-client-version.js'));
router.post('/api', asyncHandler(api.newBrew)); router.post('/api', asyncHandler(api.newBrew));
router.put('/api/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); router.put('/api/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.put('/api/update/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); router.put('/api/update/:id', asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));

View File

@@ -71,7 +71,8 @@ describe('Tests for api', ()=>{
lastViewed : new Date(), lastViewed : new Date(),
version : 1, version : 1,
pageCount : 1, pageCount : 1,
textBin : '' textBin : '',
views : 0
}; };
googleBrew = { googleBrew = {
...hbBrew, ...hbBrew,
@@ -261,7 +262,8 @@ If you believe you should have access to this brew, ask the file owner to invite
gDrive : false, gDrive : false,
style : undefined, style : undefined,
trashed : false, trashed : false,
updatedAt : undefined updatedAt : undefined,
views : 0
}); });
expect(next).toHaveBeenCalled(); expect(next).toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req); expect(api.getId).toHaveBeenCalledWith(req);
@@ -452,7 +454,8 @@ brew`);
thumbnail : '', thumbnail : '',
title : 'asdf', title : 'asdf',
trashed : false, trashed : false,
updatedAt : undefined updatedAt : undefined,
views : 0
}); });
}); });
@@ -510,7 +513,8 @@ brew`);
thumbnail : '', thumbnail : '',
title : 'asdf', title : 'asdf',
trashed : false, trashed : false,
updatedAt : undefined updatedAt : undefined,
views : 0
}); });
}); });

View File

@@ -0,0 +1,12 @@
module.exports = (req, res, next)=>{
const userVersion = req.get('Homebrewery-Version');
const version = require('../../package.json').version;
if(userVersion != version) {
return res.status(412).send({
message : `Client version ${userVersion} is out of date. Please save your changes elsewhere and refresh to pick up client version ${version}.`
});
}
next();
};

View File

@@ -1,497 +1,499 @@
@import (less) './themes/fonts/5e legacy/fonts.less'; @layer Legacy_5ePHB {
@import (less) './themes/assets/assets.less'; @import (less) './themes/fonts/5e legacy/fonts.less';
@import (less) './themes/phb.depricated.less'; @import (less) './themes/assets/assets.less';
//Colors @import (less) './themes/phb.depricated.less';
@background : #EEE5CE; // Light parchment //Colors
@noteGreen : #e0e5c1; // Pastel green @background : #EEE5CE; // Light parchment
@headerUnderline : #c9ad6a; // Gold @noteGreen : #e0e5c1; // Pastel green
@horizontalRule : #9c2b1b; // Maroon @headerUnderline : #c9ad6a; // Gold
@headerText : #58180D; // Dark maroon @horizontalRule : #9c2b1b; // Maroon
@monsterStatBackground : #FDF1DC; // Lighter parchment @headerText : #58180D; // Dark maroon
@captionText : #766649; // Brown @monsterStatBackground : #FDF1DC; // Lighter parchment
@page { margin: 0; } @captionText : #766649; // Brown
body { @page { margin: 0; }
counter-reset : phb-page-numbers; body {
} counter-reset : phb-page-numbers;
*{ }
-webkit-print-color-adjust : exact; *{
} -webkit-print-color-adjust : exact;
.useSansSerif(){ }
font-family : ScalySans; .useSansSerif(){
em{
font-family : ScalySans; font-family : ScalySans;
font-style : italic; em{
} font-family : ScalySans;
strong{ font-style : italic;
font-family : ScalySans; }
font-weight : 800; strong{
letter-spacing : -0.02em; font-family : ScalySans;
} font-weight : 800;
} letter-spacing : -0.02em;
.useColumns(@multiplier : 1){
column-count : 2;
column-fill : auto;
column-gap : 1cm;
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;
}
.phb{
.useColumns();
counter-increment : phb-page-numbers;
position : relative;
z-index : 15;
box-sizing : border-box;
overflow : hidden;
height : 279.4mm;
width : 215.9mm;
padding : 1.0cm 1.7cm;
padding-bottom : 1.5cm;
background-color : @background;
background-image : @backgroundImage;
font-family : BookSanity;
font-size : 0.317cm;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : size;
//*****************************
// * BASE
// *****************************/
p{
padding-bottom : 0.8em;
line-height : 1.269em;
&+p{
margin-top : -0.8em;
} }
} }
ul{ .useColumns(@multiplier : 1){
margin-bottom : 0.8em; column-count : 2;
padding-left : 1.4em; column-fill : auto;
line-height : 1.269em; column-gap : 1cm;
list-style-position : outside; column-width : 8cm * @multiplier;
list-style-type : disc; -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;
} }
ol{ .phb{
margin-bottom : 0.8em; .useColumns();
padding-left : 1.4em; counter-increment : phb-page-numbers;
line-height : 1.269em; position : relative;
list-style-position : outside; z-index : 15;
list-style-type : decimal; box-sizing : border-box;
} overflow : hidden;
//Indents after p or lists height : 279.4mm;
p+p, ul+p, ol+p{ width : 215.9mm;
text-indent : 1em; padding : 1.0cm 1.7cm;
} padding-bottom : 1.5cm;
img{ background-color : @background;
z-index : -1; background-image : @backgroundImage;
} font-family : BookSanity;
strong{ font-size : 0.317cm;
font-weight : bold; text-rendering : optimizeLegibility;
letter-spacing : 0.03em; page-break-before : always;
} page-break-after : always;
em{ contain : size;
font-style : italic; //*****************************
} // * BASE
sup{ // *****************************/
vertical-align : super; p{
font-size : smaller; padding-bottom : 0.8em;
line-height : 0; line-height : 1.269em;
}
sub{
vertical-align : sub;
font-size : smaller;
line-height : 0;
}
//*****************************
// * HEADERS
// *****************************/
h1,h2,h3,h4{
margin-top : 0.2em;
margin-bottom : 0.2em;
font-family : MrJeeves;
font-weight : 800;
color : @headerText;
}
h1{
column-span : all;
font-size : 0.987cm;
-webkit-column-span : all;
-moz-column-span : all;
&+p::first-letter{
float : left;
font-family : Solberry;
font-size : 10em;
color : #222;
line-height : 0.795em;
}
}
h2{
font-size : 0.705cm;
}
h3{
font-size : 0.529cm;
border-bottom : 2px solid @headerUnderline;
}
h4{
margin-bottom : 0.00em;
font-size : 0.458cm;
}
h5{
margin-bottom : 0.2em;
font-family : ScalySansSmallCaps;
font-size : 0.423cm;
font-weight : 900;
}
//*****************************
// * TABLE
// *****************************/
table{
.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;
}
}
tbody{
tr{
td{
padding : 0.3em 0.1em;
}
&:nth-child(odd){
background-color : @noteGreen;
}
}
}
}
//*****************************
// * NOTE
// *****************************/
blockquote{
.useSansSerif();
box-sizing : border-box;
margin-bottom : 1em;
padding : 5px 10px;
background-color : @noteGreen;
border-style : solid;
border-width : 11px;
border-image : @noteBorderImage 11;
border-image-outset : 9px 0px;
box-shadow : 1px 4px 14px #888;
p, ul{
font-size : 0.352cm;
line-height : 1.083em;
}
}
//If a note starts a column, give it space at the top to render border
pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote {
margin-top : 13px;
}
//*****************************
// * MONSTER STAT BLOCK
// *****************************/
hr+blockquote{
position : relative;
padding-top : 15px;
background-color : @monsterStatBackground;
border-style : solid;
border-width : 10px;
border-image : @monsterBorderImageLegacy 10;
h2{
margin-top : -8px;
margin-bottom : 0px;
&+p{ &+p{
padding-bottom : 0px; margin-top : -0.8em;
} }
} }
h3{
font-family : ScalySans;
font-weight : 400;
border-bottom : 1px solid @headerText;
}
hr+ul{
color : @headerText;
}
ul{ ul{
.useSansSerif(); margin-bottom : 0.8em;
padding-left : 1em; padding-left : 1.4em;
font-size : 0.352cm; line-height : 1.269em;
list-style-position : outside;
list-style-type : disc;
} }
// Monster Ability table ol{
hr+table{ margin-bottom : 0.8em;
margin : 0; padding-left : 1.4em;
background-color : transparent; line-height : 1.269em;
border-style : none; list-style-position : outside;
border-image : none; list-style-type : decimal;
}
//Indents after p or lists
p+p, ul+p, ol+p{
text-indent : 1em;
}
img{
z-index : -1;
}
strong{
font-weight : bold;
letter-spacing : 0.03em;
}
em{
font-style : italic;
}
sup{
vertical-align : super;
font-size : smaller;
line-height : 0;
}
sub{
vertical-align : sub;
font-size : smaller;
line-height : 0;
}
//*****************************
// * HEADERS
// *****************************/
h1,h2,h3,h4{
margin-top : 0.2em;
margin-bottom : 0.2em;
font-family : MrJeeves;
font-weight : 800;
color : @headerText;
}
h1{
column-span : all;
font-size : 0.987cm;
-webkit-column-span : all;
-moz-column-span : all;
&+p::first-letter{
float : left;
font-family : Solberry;
font-size : 10em;
color : #222;
line-height : 0.795em;
}
}
h2{
font-size : 0.705cm;
}
h3{
font-size : 0.529cm;
border-bottom : 2px solid @headerUnderline;
}
h4{
margin-bottom : 0.00em;
font-size : 0.458cm;
}
h5{
margin-bottom : 0.2em;
font-family : ScalySansSmallCaps;
font-size : 0.423cm;
font-weight : 900;
}
//*****************************
// * TABLE
// *****************************/
table{
.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;
}
}
tbody{ tbody{
tr:nth-child(odd), tr:nth-child(even){ tr{
background-color : transparent; td{
padding : 0.3em 0.1em;
}
&:nth-child(odd){
background-color : @noteGreen;
}
} }
} }
} }
table{ //*****************************
color : @headerText; // * NOTE
// *****************************/
blockquote{
.useSansSerif();
box-sizing : border-box;
margin-bottom : 1em;
padding : 5px 10px;
background-color : @noteGreen;
border-style : solid;
border-width : 11px;
border-image : @noteBorderImage 11;
border-image-outset : 9px 0px;
box-shadow : 1px 4px 14px #888;
p, ul{
font-size : 0.352cm;
line-height : 1.083em;
}
} }
p+p{ //If a note starts a column, give it space at the top to render border
margin-top : 0em; pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote {
padding-bottom : 0.5em; margin-top : 13px;
text-indent : 0em;
} }
//Triangle dividers //*****************************
hr{ // * MONSTER STAT BLOCK
visibility : visible; // *****************************/
height : 6px; hr+blockquote{
margin : 4px 0px; position : relative;
background-image : @redTriangleImage; padding-top : 15px;
background-size : 100% 100%; background-color : @monsterStatBackground;
border : none; border-style : solid;
border-width : 10px;
border-image : @monsterBorderImageLegacy 10;
h2{
margin-top : -8px;
margin-bottom : 0px;
&+p{
padding-bottom : 0px;
}
}
h3{
font-family : ScalySans;
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;
background-color : transparent;
border-style : none;
border-image : none;
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;
}
//Triangle dividers
hr{
visibility : visible;
height : 6px;
margin : 4px 0px;
background-image : @redTriangleImage;
background-size : 100% 100%;
border : none;
}
} }
} //Full Width
//Full Width hr+hr+blockquote{
hr+hr+blockquote{ .useColumns(0.96);
.useColumns(0.96); }
} //*****************************
//***************************** // * FOOTER
// * FOOTER // *****************************/
// *****************************/
&:after{
content : "";
position : absolute;
bottom : 0px;
left : 0px;
z-index : 100;
height : 50px;
width : 100%;
background-image : @footerAccentImage;
background-size : cover;
}
&:nth-child(even){
&:after{ &:after{
transform : scaleX(-1); content : "";
position : absolute;
bottom : 0px;
left : 0px;
z-index : 100;
height : 50px;
width : 100%;
background-image : @footerAccentImage;
background-size : cover;
}
&:nth-child(even){
&:after{
transform : scaleX(-1);
}
.pageNumber{
left : 2px;
}
.footnote{
left : 80px;
text-align : left;
}
} }
.pageNumber{ .pageNumber{
left : 2px; position : absolute;
right : 2px;
bottom : 22px;
width : 50px;
font-size : 0.9em;
color : #c9ad6a;
text-align : center;
&.auto::after {
content : counter(phb-page-numbers);
}
} }
.footnote{ .footnote{
left : 80px; position : absolute;
text-align : left; right : 80px;
bottom : 32px;
z-index : 150;
width : 200px;
font-size : 0.8em;
color : #c9ad6a;
text-align : right;
} }
} //*****************************
.pageNumber{ // * EXTRAS
position : absolute; // *****************************/
right : 2px; hr{
bottom : 22px; visibility : hidden;
width : 50px; margin : 0px;
font-size : 0.9em; }
color : #c9ad6a; //Modified unorder list, used in spells
text-align : center; hr+ul{
&.auto::after { margin-bottom : 0.5em;
content : counter(phb-page-numbers); padding-left : 1em;
text-indent : -1em;
list-style-type : none;
}
//Column Break
pre, code{
visibility : hidden;
-webkit-column-break-after : always;
break-after : always;
-moz-column-break-after : always;
}
//Avoid breaking up
p,blockquote,table{
z-index : 15;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
//Better spacing for spell blocks
h4+p+hr+ul{
margin-top : -0.5em
}
//Text indent right after table
table+p{
text-indent : 1em;
}
// Nested lists
ul ul,ol ol,ul ol,ol ul{
margin-bottom : 0px;
margin-left : 1.5em;
}
li{
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
} }
}
.footnote{
position : absolute;
right : 80px;
bottom : 32px;
z-index : 150;
width : 200px;
font-size : 0.8em;
color : #c9ad6a;
text-align : right;
} }
//***************************** //*****************************
// * EXTRAS // * SPELL LIST
// *****************************/ // *****************************/
hr{ .phb .spellList{
visibility : hidden; .useSansSerif();
margin : 0px; column-count : 4;
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
ul+h5{
margin-top : 15px;
}
p, ul{
font-size : 0.352cm;
line-height : 1.263em;
}
ul{
margin-bottom : 0.5em;
padding-left : 1em;
text-indent : -1em;
list-style-type : none;
-webkit-column-break-inside : auto;
page-break-inside : auto;
break-inside : auto;
}
} }
//Modified unorder list, used in spells //*****************************
hr+ul{ // * WIDE
margin-bottom : 0.5em; // *****************************/
padding-left : 1em; .phb .wide{
text-indent : -1em; column-span : all;
list-style-type : none; -webkit-column-span : all;
-moz-column-span : all;
} }
//Column Break //*****************************
pre, code{ // * CLASS TABLE
visibility : hidden; // *****************************/
-webkit-column-break-after : always; .phb .classTable{
break-after : always; margin-top : 25px;
-moz-column-break-after : always; margin-bottom : 40px;
border-collapse : separate;
background-color : white;
border : initial;
border-style : solid;
border-image-outset : 25px 17px;
border-image-repeat : stretch;
border-image-slice : 150 200 150 200;
border-image-source : @frameBorderImage;
border-image-width : 47px;
h5{
margin-bottom : 10px;
}
} }
//Avoid breaking up //************************************
p,blockquote,table{ // * DESCRIPTIVE TEXT BOX
z-index : 15; // ************************************/
-webkit-column-break-inside : avoid; .phb .descriptive{
page-break-inside : avoid; margin-bottom : 1em;
break-inside : avoid; background-color : #faf7ea;
font-family : ScalySans;
border-style : solid;
border-width : 7px;
border-image : @descriptiveBoxImage 12 stretch;
border-image-outset : 4px;
box-shadow : 0px 0px 6px #faf7ea;
p{
display : block;
padding-bottom : 0px;
line-height : 1.47em;
}
p + p {
padding-top : .8em;
}
em {
font-family : ScalySans;
font-style : italic;
}
strong {
font-family : ScalySans;
font-weight : 800;
letter-spacing : -0.02em;
}
} }
//Better spacing for spell blocks .phb pre+.descriptive{
h4+p+hr+ul{ margin-top : 8px;
margin-top : -0.5em
} }
//Text indent right after table
table+p{
text-indent : 1em;
}
// Nested lists
ul ul,ol ol,ul ol,ol ul{
margin-bottom : 0px;
margin-left : 1.5em;
}
li{
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
}
//*****************************
// * SPELL LIST
// *****************************/
.phb .spellList{
.useSansSerif();
column-count : 4;
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
ul+h5{
margin-top : 15px;
}
p, ul{
font-size : 0.352cm;
line-height : 1.263em;
}
ul{
margin-bottom : 0.5em;
padding-left : 1em;
text-indent : -1em;
list-style-type : none;
-webkit-column-break-inside : auto;
page-break-inside : auto;
break-inside : auto;
}
}
//*****************************
// * WIDE
// *****************************/
.phb .wide{
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
}
//*****************************
// * CLASS TABLE
// *****************************/
.phb .classTable{
margin-top : 25px;
margin-bottom : 40px;
border-collapse : separate;
background-color : white;
border : initial;
border-style : solid;
border-image-outset : 25px 17px;
border-image-repeat : stretch;
border-image-slice : 150 200 150 200;
border-image-source : @frameBorderImage;
border-image-width : 47px;
h5{
margin-bottom : 10px;
}
}
//************************************
// * DESCRIPTIVE TEXT BOX
// ************************************/
.phb .descriptive{
margin-bottom : 1em;
background-color : #faf7ea;
font-family : ScalySans;
border-style : solid;
border-width : 7px;
border-image : @descriptiveBoxImage 12 stretch;
border-image-outset : 4px;
box-shadow : 0px 0px 6px #faf7ea;
p{
display : block;
padding-bottom : 0px;
line-height : 1.47em;
}
p + p {
padding-top : .8em;
}
em {
font-family : ScalySans;
font-style : italic;
}
strong {
font-family : ScalySans;
font-weight : 800;
letter-spacing : -0.02em;
}
}
.phb pre+.descriptive{
margin-top : 8px;
}
//***************************** //*****************************
// * ARTIST CREDIT BLOCK // * ARTIST CREDIT BLOCK
// *****************************/ // *****************************/
.phb { .phb {
.artist { .artist {
position : absolute; position : absolute;
text-align : center; text-align : center;
font-family : WalterTurncoat;
font-size : 0.27cm;
color : @captionText;
p, p + p {
margin : unset;
text-indent : unset;
line-height : 0.941em;
}
h5 {
font-size : 1.3em;
font-family : WalterTurncoat; font-family : WalterTurncoat;
font-size : 0.27cm;
color : @captionText;
p, p + p {
margin : unset;
text-indent : unset;
line-height : 0.941em;
}
h5 {
font-size : 1.3em;
font-family : WalterTurncoat;
}
a{
color : inherit;
text-decoration : unset;
&:hover {
text-decoration : underline;
}
}
} }
}
//*****************************
// * TABLE OF CONTENTS
// *****************************/
.phb .toc{
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
a{ a{
color : inherit; color : black;
text-decoration : unset; text-decoration : none;
&:hover { &:hover{
text-decoration : underline; text-decoration : underline;
} }
} }
} ul{
} padding-left : 0;
//***************************** list-style-type : none;
// * TABLE OF CONTENTS }
// *****************************/ &>ul>li{
.phb .toc{ margin-bottom : 10px;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
a{
color : black;
text-decoration : none;
&:hover{
text-decoration : underline;
} }
} }
ul{
padding-left : 0;
list-style-type : none;
}
&>ul>li{
margin-bottom : 10px;
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

View File

@@ -1,19 +1,21 @@
:root { @layer V3_5eDMG {
//Colors :root {
--HB_Color_Accent : #EBCEC3; // Salmon //Colors
--HB_Color_Footnotes : #5C5C5C; // Dark gray --HB_Color_Accent : #EBCEC3; // Salmon
} --HB_Color_Footnotes : #5C5C5C; // Dark gray
.page {
background-image : url(/assets/DMG_background.png);
background-size : cover;
&:after {
background-image : url(/assets/DMG_footerAccent.png);
height: 58px;
} }
.footnote { .page {
bottom : 40px; background-image : url(/assets/DMG_background.png);
background-size : cover;
&:after {
background-image : url(/assets/DMG_footerAccent.png);
height: 58px;
}
.footnote {
bottom : 40px;
}
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

View File

@@ -1,276 +1,278 @@
@import (less) './themes/fonts/5e/fonts.less'; @layer V3_Blank {
@import (less) './themes/assets/assets.less'; @import (less) './themes/fonts/5e/fonts.less';
@import (less) './themes/assets/assets.less';
:root { :root {
//Colors //Colors
--HB_Color_Background : #FFFFFF; // White --HB_Color_Background : #FFFFFF; // White
--HB_Color_WatercolorStain : #000000; // Black --HB_Color_WatercolorStain : #000000; // Black
}
@page { margin: 0; }
body {
counter-reset : phb-page-numbers;
}
*{
-webkit-print-color-adjust : exact;
}
.useColumns(@multiplier : 1, @fillMode: balance){
column-fill : @fillMode;
column-count : 2;
}
.columnWrapper{
max-height : 100%;
column-span : all;
columns : inherit;
column-gap : inherit;
}
.page{
.useColumns();
height : 279.4mm;
width : 215.9mm;
padding : 1.4cm 1.9cm 1.7cm;
counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background);
position : relative;
z-index : 15;
box-sizing : border-box;
overflow : hidden;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : size;
}
//*****************************
// * BASE
// *****************************/
.page{
p{
overflow-wrap : break-word;
display : block;
}
strong{
font-weight : bold;
}
em{
font-style : italic;
}
sup{
vertical-align : super;
font-size : smaller;
line-height : 0;
}
sub{
vertical-align : sub;
font-size : smaller;
line-height : 0;
}
ul {
list-style-position : outside; //Needed for multiline list items
list-style-type : disc;
padding-left : 1.4em;
}
ol {
list-style-position : outside;
list-style-type : decimal;
padding-left : 1.4em;
}
img{
z-index : -1;
} }
//***************************** @page { margin: 0; }
// * HEADERS body {
// *****************************/ counter-reset : phb-page-numbers;
h1,h2,h3,h4,h5,h6{
font-weight : bold;
line-height : 1.2em;
} }
h1{ *{
font-size : 2em; -webkit-print-color-adjust : exact;
} }
h2{
font-size : 1.5em; .useColumns(@multiplier : 1, @fillMode: balance){
column-fill : @fillMode;
column-count : 2;
} }
h3{ .columnWrapper{
font-size : 1.17em; max-height : 100%;
column-span : all;
columns : inherit;
column-gap : inherit;
} }
h4{ .page{
font-size : 1em; .useColumns();
height : 279.4mm;
width : 215.9mm;
padding : 1.4cm 1.9cm 1.7cm;
counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background);
position : relative;
z-index : 15;
box-sizing : border-box;
overflow : hidden;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : size;
} }
h5{ //*****************************
font-size : 0.83em; // * BASE
} // *****************************/
//***************************** .page{
// * TABLE p{
// *****************************/ overflow-wrap : break-word;
table{ display : block;
width : 100%; }
thead{ strong{
display : table-row-group;
font-weight : bold; font-weight : bold;
} }
} em{
div:not(.columnWrapper) > table + table { // Side-by-side tables should not font-style : italic;
margin-top : 0; // have vertical spacing. }
} sup{
vertical-align : super;
font-size : smaller;
line-height : 0;
}
sub{
vertical-align : sub;
font-size : smaller;
line-height : 0;
}
ul {
list-style-position : outside; //Needed for multiline list items
list-style-type : disc;
padding-left : 1.4em;
}
ol {
list-style-position : outside;
list-style-type : decimal;
padding-left : 1.4em;
}
img{
z-index : -1;
}
/* Watermark */ //*****************************
.watermark { // * HEADERS
display : grid !important; // *****************************/
place-items : center; h1,h2,h3,h4,h5,h6{
justify-content : center; font-weight : bold;
position : absolute; line-height : 1.2em;
margin : 0; }
top : 0; h1{
left : 0; font-size : 2em;
width : 100%; }
height : 100%; h2{
font-size : 120px; font-size : 1.5em;
text-transform : uppercase; }
color : black; h3{
mix-blend-mode : overlay; font-size : 1.17em;
opacity : 30%; }
transform : rotate(-45deg); h4{
z-index : 500; font-size : 1em;
p { }
margin-bottom : none; h5{
font-size : 0.83em;
}
//*****************************
// * TABLE
// *****************************/
table{
width : 100%;
thead{
display : table-row-group;
font-weight : bold;
}
}
div:not(.columnWrapper) > table + table { // Side-by-side tables should not
margin-top : 0; // have vertical spacing.
}
/* Watermark */
.watermark {
display : grid !important;
place-items : center;
justify-content : center;
position : absolute;
margin : 0;
top : 0;
left : 0;
width : 100%;
height : 100%;
font-size : 120px;
text-transform : uppercase;
color : black;
mix-blend-mode : overlay;
opacity : 30%;
transform : rotate(-45deg);
z-index : 500;
p {
margin-bottom : none;
}
}
/* Watercolor */
[class*="watercolor"] {
position : absolute;
width : 2000px; /* dimensions need to be real big so the user can set */
height : 2000px; /* height or width and the image will maintain aspect ratio */
-webkit-mask-image : var(--wc);
-webkit-mask-size : contain;
-webkit-mask-repeat : no-repeat;
mask-image : var(--wc);
mask-size : contain;
mask-repeat : no-repeat;
background-size : cover;
background-color : var(--HB_Color_WatercolorStain); /*default color*/
--wc : @watercolor1; /*default image*/
z-index : -2;
}
.watercolor1 { --wc : @watercolor1; }
.watercolor2 { --wc : @watercolor2; }
.watercolor3 { --wc : @watercolor3; }
.watercolor4 { --wc : @watercolor4; }
.watercolor5 { --wc : @watercolor5; }
.watercolor6 { --wc : @watercolor6; }
.watercolor7 { --wc : @watercolor7; }
.watercolor8 { --wc : @watercolor8; }
.watercolor9 { --wc : @watercolor9; }
.watercolor10 { --wc : @watercolor10; }
.watercolor11 { --wc : @watercolor11; }
.watercolor12 { --wc : @watercolor12; }
//************************************
// * CODE BLOCKS
// ************************************/
code{
font-family : "Courier New", Courier, monospace;
white-space : pre-wrap;
overflow-wrap : break-word;
}
pre code{
width : 100%;
display : inline-block;
}
//*****************************
// * EXTRAS
// *****************************/
.columnSplit {
visibility : hidden;
-webkit-column-break-after : always;
break-after : always;
-moz-column-break-after : always;
margin-top : 0;
& + * {
margin-top : 0;
}
}
//Avoid breaking up
blockquote,table{
z-index : 15;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
// Nested lists
ul ul,ol ol,ul ol,ol ul{
margin-bottom : 0px;
margin-left : 1.5em;
}
li{
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
} }
} }
/* Watercolor */
[class*="watercolor"] {
position : absolute;
width : 2000px; /* dimensions need to be real big so the user can set */
height : 2000px; /* height or width and the image will maintain aspect ratio */
-webkit-mask-image : var(--wc);
-webkit-mask-size : contain;
-webkit-mask-repeat : no-repeat;
mask-image : var(--wc);
mask-size : contain;
mask-repeat : no-repeat;
background-size : cover;
background-color : var(--HB_Color_WatercolorStain); /*default color*/
--wc : @watercolor1; /*default image*/
z-index : -2;
}
.watercolor1 { --wc : @watercolor1; }
.watercolor2 { --wc : @watercolor2; }
.watercolor3 { --wc : @watercolor3; }
.watercolor4 { --wc : @watercolor4; }
.watercolor5 { --wc : @watercolor5; }
.watercolor6 { --wc : @watercolor6; }
.watercolor7 { --wc : @watercolor7; }
.watercolor8 { --wc : @watercolor8; }
.watercolor9 { --wc : @watercolor9; }
.watercolor10 { --wc : @watercolor10; }
.watercolor11 { --wc : @watercolor11; }
.watercolor12 { --wc : @watercolor12; }
//************************************
// * CODE BLOCKS
// ************************************/
code{
font-family : "Courier New", Courier, monospace;
white-space : pre-wrap;
overflow-wrap : break-word;
}
pre code{
width : 100%;
display : inline-block;
}
//***************************** //*****************************
// * EXTRAS // * MUSTACHE DIVS/SPANS
// *****************************/ // *****************************/
.columnSplit { .page {
visibility : hidden; .block {
-webkit-column-break-after : always; break-inside : avoid;
break-after : always; display : inline-block;
-moz-column-break-after : always; width : 100%;
margin-top : 0; }
& + * { .inline-block {
margin-top : 0; display : inline-block;
text-indent : initial;
} }
} }
//Avoid breaking up
blockquote,table{
z-index : 15;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
// Nested lists
ul ul,ol ol,ul ol,ol ul{
margin-bottom : 0px;
margin-left : 1.5em;
}
li{
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
}
//***************************** //*****************************
// * MUSTACHE DIVS/SPANS // * DEFINITION LISTS
// *****************************/ // *****************************/
.page { .page {
.block { dl {
break-inside : avoid; padding-left : 1em;
display : inline-block; white-space : pre-line;
width : 100%; }
dt {
display : inline;
margin-right : 0.5ch;
margin-left : -1em;
}
dd {
display : inline;
margin-left : 0;
text-indent : 0;
}
} }
.inline-block {
display : inline-block;
text-indent : initial;
}
}
//***************************** //*****************************
// * DEFINITION LISTS // * BLANK LINE
// *****************************/ // *****************************/
.page { .page {
dl { .blank {
padding-left : 1em; height : 1em;
white-space : pre-line;
}
dt {
display : inline;
margin-right : 0.5ch;
margin-left : -1em;
}
dd {
display : inline;
margin-left : 0;
text-indent : 0;
}
}
//*****************************
// * BLANK LINE
// *****************************/
.page {
.blank {
height : 1em;
margin-top : 0;
& + * {
margin-top : 0;
}
}
}
//*****************************
// * WIDE
// *****************************/
.page {
.wide{
column-span : all;
display : block;
margin-bottom : 1em;
&+* {
margin-top : 0; margin-top : 0;
& + * {
margin-top : 0;
}
}
}
//*****************************
// * WIDE
// *****************************/
.page {
.wide{
column-span : all;
display : block;
margin-bottom : 1em;
&+* {
margin-top : 0;
}
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -81,28 +81,28 @@
/* Cover Page */ /* Cover Page */
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed.woff2'); src: url('../../../fonts/5e/Nodesto Caps Condensed.woff2');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Bold.woff2'); src: url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2');
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Italic.woff2'); src: url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2');
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} }
@font-face { @font-face {
font-family: NodestoCapsCondensed; font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2'); src: url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
font-weight: bold; font-weight: bold;
font-style: italic; font-style: italic;
} }