mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-05 21:02:43 +00:00
Merge branch 'master' into fixThemesDefault
This commit is contained in:
@@ -27,7 +27,7 @@ jobs:
|
|||||||
# fallback to using the latest cache if no exact match is found
|
# fallback to using the latest cache if no exact match is found
|
||||||
- v1-dependencies-
|
- v1-dependencies-
|
||||||
|
|
||||||
- node/install-npm
|
- run: sudo npm install -g npm@8.10.0
|
||||||
- node/install-packages:
|
- node/install-packages:
|
||||||
app-dir: ~/homebrewery
|
app-dir: ~/homebrewery
|
||||||
cache-path: node_modules
|
cache-path: node_modules
|
||||||
@@ -55,13 +55,13 @@ jobs:
|
|||||||
at: .
|
at: .
|
||||||
|
|
||||||
# run tests!
|
# run tests!
|
||||||
- run:
|
- run:
|
||||||
name: Test - Basic
|
name: Test - Basic
|
||||||
command: npm run test:basic
|
command: npm run test:basic
|
||||||
- run:
|
- run:
|
||||||
name: Test - Mustache Spans
|
name: Test - Mustache Spans
|
||||||
command: npm run test:mustache-span
|
command: npm run test:mustache-span
|
||||||
- run:
|
- run:
|
||||||
name: Test - Routes
|
name: Test - Routes
|
||||||
command: npm run test:route
|
command: npm run test:route
|
||||||
|
|
||||||
@@ -71,4 +71,4 @@ workflows:
|
|||||||
- build
|
- build
|
||||||
- test:
|
- test:
|
||||||
requires:
|
requires:
|
||||||
- build
|
- build
|
||||||
|
|||||||
8
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
8
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -12,10 +12,6 @@ body:
|
|||||||
description: The best feature requests provide an explanation of the current issue and then an explanation of how it could be improved. Screenshots/images can be pasted right in as well!
|
description: The best feature requests provide an explanation of the current issue and then an explanation of how it could be improved. Screenshots/images can be pasted right in as well!
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: checkboxes
|
- type: markdown
|
||||||
id: terms
|
|
||||||
attributes:
|
attributes:
|
||||||
label: "Please confirm:"
|
value: "Please be sure to search for any close matches to your request in the GitHub Issues tracker before opening a new request, thanks!"
|
||||||
options:
|
|
||||||
- label: I have searched the Issues tracker for any duplicate requests and found none.
|
|
||||||
required: true
|
|
||||||
|
|||||||
7
.github/ISSUE_TEMPLATE/general_issue.yml
vendored
7
.github/ISSUE_TEMPLATE/general_issue.yml
vendored
@@ -4,14 +4,15 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: Please include as much information as possible.
|
value: Please include as much information as possible.
|
||||||
- type: checkboxes
|
- type: dropdown
|
||||||
id: renderer
|
id: renderer
|
||||||
attributes:
|
attributes:
|
||||||
label: Renderer
|
label: Renderer
|
||||||
description: Which renderer does this issue occur on? If you are unsure, you can check the renderer in the Properties Editor (click the "i" in the Snippet Menu bar above the editor).
|
description: Which renderer does this issue occur on? If you are unsure, you can check the renderer in the Properties Editor (click the "i" in the Snippet Menu bar above the editor).
|
||||||
options:
|
options:
|
||||||
- label: Legacy
|
- v3
|
||||||
- label: v3
|
- Legacy
|
||||||
|
- Both
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
|||||||
76
changelog.md
76
changelog.md
@@ -1,7 +1,6 @@
|
|||||||
```css
|
```css
|
||||||
h5 {
|
h5 {
|
||||||
font-size: .35cm !important;
|
font-size: .35cm !important;
|
||||||
margin-top: 0.3cm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page ul ul {
|
.page ul ul {
|
||||||
@@ -45,6 +44,75 @@ pre {
|
|||||||
## 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).
|
||||||
|
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
}}
|
||||||
|
|
||||||
|
### Monday 28/11/2022 - v3.4.0
|
||||||
|
{{taskList
|
||||||
|
|
||||||
|
##### G-Ambatte
|
||||||
|
|
||||||
|
* [x] Fix for Chrome v108 handling of page size
|
||||||
|
|
||||||
|
Fixes issues [#2445](https://github.com/naturalcrit/homebrewery/issues/2445), [#2516](https://github.com/naturalcrit/homebrewery/issues/2516)
|
||||||
|
|
||||||
|
* [x] New account page with some user info, at {{openSans **USERNAME {{fa,fa-user}} → ACCOUNT {{fa,fa-user}}**}}
|
||||||
|
|
||||||
|
Fixes issues [#2049](https://github.com/naturalcrit/homebrewery/issues/2049), [#2043](https://github.com/naturalcrit/homebrewery/issues/2043)
|
||||||
|
|
||||||
|
* [x] Fix "Published/Private Brews" buttons on userpage
|
||||||
|
|
||||||
|
Fixes issues [#2449](https://github.com/naturalcrit/homebrewery/issues/2449)
|
||||||
|
|
||||||
|
##### Gazook
|
||||||
|
|
||||||
|
* [x] Make autosave default on for new users
|
||||||
|
|
||||||
|
* [x] Added link to our FAQ at {{openSans **NEED HELP? {{fa,fa-question-circle}} → FAQ {{fa,fa-question-circle}}**}}
|
||||||
|
|
||||||
|
* [x] Fix curly blocks freezing with long property lists
|
||||||
|
|
||||||
|
Fixes issues [#2393](https://github.com/naturalcrit/homebrewery/issues/2393)
|
||||||
|
|
||||||
|
* [x] Items can now be removed from {{openSans **RECENT BREWS** {{fas,fa-history}} }}
|
||||||
|
|
||||||
|
Fixes issues [#1918](https://github.com/naturalcrit/homebrewery/issues/1918)
|
||||||
|
|
||||||
|
* [x] Curly injector syntax `{blue}` highlighting in editor
|
||||||
|
|
||||||
|
Fixes issues [#1670](https://github.com/naturalcrit/homebrewery/issues/1670)
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
|
### Thursday 28/10/2022 - v3.3.1
|
||||||
|
{{taskList
|
||||||
|
|
||||||
|
##### Calculuschild
|
||||||
|
|
||||||
|
* [x] Fixes to several broken CSS styles from v3.3.0
|
||||||
|
|
||||||
|
Fixes issues [#2468](https://github.com/naturalcrit/homebrewery/issues/2468)
|
||||||
|
|
||||||
|
##### Jeddai
|
||||||
|
|
||||||
|
* [x] Reduce size of thumbnails on social media links
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
### Friday 19/10/2022 - v3.3.0
|
### Friday 19/10/2022 - v3.3.0
|
||||||
{{taskList
|
{{taskList
|
||||||
|
|
||||||
@@ -74,10 +142,12 @@ Fixes issues [#2135](https://github.com/naturalcrit/homebrewery/issues/2135)
|
|||||||
* [x] Fix brew settings being lost on first save
|
* [x] Fix brew settings being lost on first save
|
||||||
|
|
||||||
Fixes issues [#2427](https://github.com/naturalcrit/homebrewery/issues/2427)
|
Fixes issues [#2427](https://github.com/naturalcrit/homebrewery/issues/2427)
|
||||||
|
}}
|
||||||
|
|
||||||
|
\column
|
||||||
|
|
||||||
|
|
||||||
|
{{taskList
|
||||||
|
|
||||||
##### Gazook:
|
##### Gazook:
|
||||||
|
|
||||||
* [x] Several updates to bug reporting and error popups
|
* [x] Several updates to bug reporting and error popups
|
||||||
|
|||||||
@@ -137,9 +137,17 @@ const Editor = createClass({
|
|||||||
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highlight injectors {style}
|
||||||
|
if(line.includes('{') && line.includes('}')){
|
||||||
|
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm;
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(line)) != null) {
|
||||||
|
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' });
|
||||||
|
}
|
||||||
|
}
|
||||||
// Highlight inline spans {{content}}
|
// Highlight inline spans {{content}}
|
||||||
if(line.includes('{{') && line.includes('}}')){
|
if(line.includes('{{') && line.includes('}}')){
|
||||||
const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
|
const regex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g;
|
||||||
let match;
|
let match;
|
||||||
let blockCount = 0;
|
let blockCount = 0;
|
||||||
while ((match = regex.exec(line)) != null) {
|
while ((match = regex.exec(line)) != null) {
|
||||||
@@ -158,7 +166,7 @@ const Editor = createClass({
|
|||||||
// Highlight block divs {{\n Content \n}}
|
// Highlight block divs {{\n Content \n}}
|
||||||
let endCh = line.length+1;
|
let endCh = line.length+1;
|
||||||
|
|
||||||
const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/);
|
const match = line.match(/^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/);
|
||||||
if(match)
|
if(match)
|
||||||
endCh = match.index+match[0].length;
|
endCh = match.index+match[0].length;
|
||||||
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
||||||
|
|||||||
@@ -29,6 +29,10 @@
|
|||||||
font-weight : bold;
|
font-weight : bold;
|
||||||
//font-style: italic;
|
//font-style: italic;
|
||||||
}
|
}
|
||||||
|
.injection{
|
||||||
|
color : green;
|
||||||
|
font-weight : bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.brewJump{
|
.brewJump{
|
||||||
|
|||||||
@@ -9,11 +9,18 @@ const Nav = require('naturalcrit/nav/nav.jsx');
|
|||||||
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
|
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
|
||||||
|
|
||||||
const Themes = require('themes/themes.json');
|
const Themes = require('themes/themes.json');
|
||||||
|
const validations = require('./validations.js');
|
||||||
|
|
||||||
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
|
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
|
||||||
|
|
||||||
const homebreweryThumbnail = require('../../thumbnail.png');
|
const homebreweryThumbnail = require('../../thumbnail.png');
|
||||||
|
|
||||||
|
const callIfExists = (val, fn, ...args)=>{
|
||||||
|
if(val[fn]) {
|
||||||
|
val[fn](...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const MetadataEditor = createClass({
|
const MetadataEditor = createClass({
|
||||||
displayName : 'MetadataEditor',
|
displayName : 'MetadataEditor',
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
@@ -22,6 +29,7 @@ const MetadataEditor = createClass({
|
|||||||
editId : null,
|
editId : null,
|
||||||
title : '',
|
title : '',
|
||||||
description : '',
|
description : '',
|
||||||
|
thumbnail : '',
|
||||||
tags : [],
|
tags : [],
|
||||||
published : false,
|
published : false,
|
||||||
authors : [],
|
authors : [],
|
||||||
@@ -51,11 +59,27 @@ const MetadataEditor = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleFieldChange : function(name, e){
|
handleFieldChange : function(name, e){
|
||||||
this.props.onChange({
|
// load validation rules, and check input value against them
|
||||||
...this.props.metadata,
|
const inputRules = validations[name] ?? [];
|
||||||
[name] : e.target.value
|
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
|
||||||
});
|
|
||||||
|
// if no validation rules, save to props
|
||||||
|
if(validationErr.length === 0){
|
||||||
|
callIfExists(e.target, 'setCustomValidity', '');
|
||||||
|
this.props.onChange({
|
||||||
|
...this.props.metadata,
|
||||||
|
[name] : e.target.value
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// if validation issues, display built-in browser error popup with each error.
|
||||||
|
const errMessage = validationErr.map((err)=>{
|
||||||
|
return `- ${err}`;
|
||||||
|
}).join('\n');
|
||||||
|
callIfExists(e.target, 'setCustomValidity', errMessage);
|
||||||
|
callIfExists(e.target, 'reportValidity');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSystem : function(system, e){
|
handleSystem : function(system, e){
|
||||||
if(e.target.checked){
|
if(e.target.checked){
|
||||||
this.props.metadata.systems.push(system);
|
this.props.metadata.systems.push(system);
|
||||||
@@ -64,6 +88,7 @@ const MetadataEditor = createClass({
|
|||||||
}
|
}
|
||||||
this.props.onChange(this.props.metadata);
|
this.props.onChange(this.props.metadata);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleRenderer : function(renderer, e){
|
handleRenderer : function(renderer, e){
|
||||||
if(e.target.checked){
|
if(e.target.checked){
|
||||||
this.props.metadata.renderer = renderer;
|
this.props.metadata.renderer = renderer;
|
||||||
@@ -228,21 +253,21 @@ const MetadataEditor = createClass({
|
|||||||
<div className='field title'>
|
<div className='field title'>
|
||||||
<label>title</label>
|
<label>title</label>
|
||||||
<input type='text' className='value'
|
<input type='text' className='value'
|
||||||
value={this.props.metadata.title}
|
defaultValue={this.props.metadata.title}
|
||||||
onChange={(e)=>this.handleFieldChange('title', e)} />
|
onChange={(e)=>this.handleFieldChange('title', e)} />
|
||||||
</div>
|
</div>
|
||||||
<div className='field-group'>
|
<div className='field-group'>
|
||||||
<div className='field-column'>
|
<div className='field-column'>
|
||||||
<div className='field description'>
|
<div className='field description'>
|
||||||
<label>description</label>
|
<label>description</label>
|
||||||
<textarea value={this.props.metadata.description} className='value'
|
<textarea defaultValue={this.props.metadata.description} className='value'
|
||||||
onChange={(e)=>this.handleFieldChange('description', e)} />
|
onChange={(e)=>this.handleFieldChange('description', e)} />
|
||||||
</div>
|
</div>
|
||||||
<div className='field thumbnail'>
|
<div className='field thumbnail'>
|
||||||
<label>thumbnail</label>
|
<label>thumbnail</label>
|
||||||
<input type='text'
|
<input type='text'
|
||||||
value={this.props.metadata.thumbnail}
|
defaultValue={this.props.metadata.thumbnail}
|
||||||
placeholder='my.thumbnail.url'
|
placeholder='https://my.thumbnail.url'
|
||||||
className='value'
|
className='value'
|
||||||
onChange={(e)=>this.handleFieldChange('thumbnail', e)} />
|
onChange={(e)=>this.handleFieldChange('thumbnail', e)} />
|
||||||
<button className='display' onClick={this.toggleThumbnailDisplay}>
|
<button className='display' onClick={this.toggleThumbnailDisplay}>
|
||||||
|
|||||||
@@ -42,6 +42,12 @@
|
|||||||
&>.value{
|
&>.value{
|
||||||
flex : 1 1 auto;
|
flex : 1 1 auto;
|
||||||
width : 50px;
|
width : 50px;
|
||||||
|
&:invalid {
|
||||||
|
background : #ffb9b9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input[type='text'], textarea {
|
||||||
|
border : 1px solid gray;
|
||||||
}
|
}
|
||||||
&.thumbnail{
|
&.thumbnail{
|
||||||
height : 1.4em;
|
height : 1.4em;
|
||||||
|
|||||||
34
client/homebrew/editor/metadataEditor/validations.js
Normal file
34
client/homebrew/editor/metadataEditor/validations.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
module.exports = {
|
||||||
|
title : [
|
||||||
|
(value)=>{
|
||||||
|
return value?.length > 100 ? 'Max title length of 100 characters' : null;
|
||||||
|
}
|
||||||
|
],
|
||||||
|
description : [
|
||||||
|
(value)=>{
|
||||||
|
return value?.length > 500 ? 'Max description length of 500 characters.' : null;
|
||||||
|
}
|
||||||
|
],
|
||||||
|
thumbnail : [
|
||||||
|
(value)=>{
|
||||||
|
return value?.length > 256 ? 'Max URL length of 256 characters.' : null;
|
||||||
|
},
|
||||||
|
(value)=>{
|
||||||
|
if(value?.length == 0){return null;}
|
||||||
|
try {
|
||||||
|
Boolean(new URL(value));
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return 'Must be a valid URL';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
language : [
|
||||||
|
(value)=>{
|
||||||
|
return new RegExp(/[a-z]{2,3}(-.*)?/).test(value || '') === false ? 'Invalid language code.' : null;
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ const SharePage = require('./pages/sharePage/sharePage.jsx');
|
|||||||
const NewPage = require('./pages/newPage/newPage.jsx');
|
const NewPage = require('./pages/newPage/newPage.jsx');
|
||||||
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||||
const PrintPage = require('./pages/printPage/printPage.jsx');
|
const PrintPage = require('./pages/printPage/printPage.jsx');
|
||||||
|
const AccountPage = require('./pages/accountPage/accountPage.jsx');
|
||||||
|
|
||||||
const WithRoute = (props)=>{
|
const WithRoute = (props)=>{
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
@@ -61,24 +62,27 @@ const Homebrew = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function (){
|
render : function (){
|
||||||
return <Router location={this.props.url}>
|
return (
|
||||||
<div className='homebrew'>
|
<Router location={this.props.url}>
|
||||||
<Routes>
|
<div className='homebrew'>
|
||||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={this.props.brew} />} />
|
<Routes>
|
||||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={this.props.brew} />} />
|
||||||
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} />} />
|
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||||
<Route path='/new' element={<WithRoute el={NewPage}/>} />
|
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} />} />
|
||||||
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} />
|
<Route path='/new' element={<WithRoute el={NewPage}/>} />
|
||||||
<Route path='/print/:id' element={<WithRoute el={PrintPage} brew={this.props.brew} />} />
|
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} />
|
||||||
<Route path='/print' element={<WithRoute el={PrintPage} />} />
|
<Route path='/print/:id' element={<WithRoute el={PrintPage} brew={this.props.brew} />} />
|
||||||
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
<Route path='/print' element={<WithRoute el={PrintPage} />} />
|
||||||
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||||
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||||
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
<Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} uiItems={this.props.brew.uiItems} />} />
|
||||||
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||||
</Routes>
|
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||||
</div>
|
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||||
</Router>;
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,14 @@ const Account = createClass({
|
|||||||
>
|
>
|
||||||
brews
|
brews
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
|
<Nav.item
|
||||||
|
className='account'
|
||||||
|
color='orange'
|
||||||
|
icon='fas fa-user'
|
||||||
|
href='/account'
|
||||||
|
>
|
||||||
|
account
|
||||||
|
</Nav.item>
|
||||||
<Nav.item
|
<Nav.item
|
||||||
className='logout'
|
className='logout'
|
||||||
color='red'
|
color='red'
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ module.exports = function(props){
|
|||||||
rel='noopener noreferrer'>
|
rel='noopener noreferrer'>
|
||||||
report issue
|
report issue
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
|
<Nav.item color='green' icon='fas fa-question-circle'
|
||||||
|
href='/faq'
|
||||||
|
newTab={true}
|
||||||
|
rel='noopener noreferrer'>
|
||||||
|
FAQ
|
||||||
|
</Nav.item>
|
||||||
<Nav.item color='blue' icon='fas fa-fw fa-file-import'
|
<Nav.item color='blue' icon='fas fa-fw fa-file-import'
|
||||||
href='/migrate'
|
href='/migrate'
|
||||||
newTab={true}
|
newTab={true}
|
||||||
|
|||||||
@@ -115,8 +115,36 @@
|
|||||||
color : white;
|
color : white;
|
||||||
text-decoration : none;
|
text-decoration : none;
|
||||||
border-top : 1px solid #888;
|
border-top : 1px solid #888;
|
||||||
|
overflow : clip;
|
||||||
|
.clear{
|
||||||
|
display : none;
|
||||||
|
position : absolute;
|
||||||
|
top : 50%;
|
||||||
|
transform : translateY(-50%);
|
||||||
|
right : 0px;
|
||||||
|
width : 20px;
|
||||||
|
height : 100%;
|
||||||
|
background-color : #333;
|
||||||
|
opacity : 70%;
|
||||||
|
border-radius : 3px;
|
||||||
|
&:hover {
|
||||||
|
opacity : 100%;
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
text-align : center;
|
||||||
|
font-size : 10px;
|
||||||
|
margin : 0;
|
||||||
|
height :100%;
|
||||||
|
width :100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
&:hover{
|
&:hover{
|
||||||
background-color : @blue;
|
background-color : @blue;
|
||||||
|
|
||||||
|
.clear{
|
||||||
|
display : grid;
|
||||||
|
place-content : center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.title{
|
.title{
|
||||||
display : inline-block;
|
display : inline-block;
|
||||||
|
|||||||
@@ -119,6 +119,25 @@ const RecentItems = createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
removeItem : function(url, evt){
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
||||||
|
let viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
|
||||||
|
|
||||||
|
edited = edited.filter((item)=>{ return (item.url !== url);});
|
||||||
|
viewed = viewed.filter((item)=>{ return (item.url !== url);});
|
||||||
|
|
||||||
|
localStorage.setItem(EDIT_KEY, JSON.stringify(edited));
|
||||||
|
localStorage.setItem(VIEW_KEY, JSON.stringify(viewed));
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
edit : edited,
|
||||||
|
view : viewed
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
renderDropdown : function(){
|
renderDropdown : function(){
|
||||||
if(!this.state.showDropdown) return null;
|
if(!this.state.showDropdown) return null;
|
||||||
|
|
||||||
@@ -127,6 +146,7 @@ const RecentItems = createClass({
|
|||||||
return <a href={brew.url} className='item' key={`${brew.id}-${i}`} target='_blank' rel='noopener noreferrer' title={brew.title || '[ no title ]'}>
|
return <a href={brew.url} className='item' key={`${brew.id}-${i}`} target='_blank' rel='noopener noreferrer' title={brew.title || '[ no title ]'}>
|
||||||
<span className='title'>{brew.title || '[ no title ]'}</span>
|
<span className='title'>{brew.title || '[ no title ]'}</span>
|
||||||
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||||
|
<div className='clear' title='Remove from Recents' onClick={(e)=>{this.removeItem(`${brew.url}`, e);}}><i className='fas fa-times'></i></div>
|
||||||
</a>;
|
</a>;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
71
client/homebrew/pages/accountPage/accountPage.jsx
Normal file
71
client/homebrew/pages/accountPage/accountPage.jsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const cx = require('classnames');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
const UIPage = require('../basePages/uiPage/uiPage.jsx');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Navbar = require('../../navbar/navbar.jsx');
|
||||||
|
|
||||||
|
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||||
|
const Account = require('../../navbar/account.navitem.jsx');
|
||||||
|
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
|
||||||
|
const HelpNavItem = require('../../navbar/help.navitem.jsx');
|
||||||
|
|
||||||
|
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
|
||||||
|
|
||||||
|
const AccountPage = createClass({
|
||||||
|
displayName : 'AccountPage',
|
||||||
|
getDefaultProps : function() {
|
||||||
|
return {
|
||||||
|
brew : {},
|
||||||
|
uiItems : {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getInitialState : function() {
|
||||||
|
return {
|
||||||
|
uiItems : this.props.uiItems
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
renderNavItems : function() {
|
||||||
|
return <Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<NewBrew />
|
||||||
|
<HelpNavItem />
|
||||||
|
<RecentNavItem />
|
||||||
|
<Account />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>;
|
||||||
|
},
|
||||||
|
|
||||||
|
renderUiItems : function() {
|
||||||
|
// console.log(this.props.uiItems);
|
||||||
|
return <>
|
||||||
|
<div className='dataGroup'>
|
||||||
|
<h1>Account Information <i className='fas fa-user'></i></h1>
|
||||||
|
<p><strong>Username: </strong> {this.props.uiItems.username || 'No user currently logged in'}</p>
|
||||||
|
<p><strong>Last Login: </strong> {moment(this.props.uiItems.issued).format('dddd, MMMM Do YYYY, h:mm:ss a ZZ') || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div className='dataGroup'>
|
||||||
|
<h3>Homebrewery Information <NaturalCritIcon /></h3>
|
||||||
|
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount || '-'}</p>
|
||||||
|
</div>
|
||||||
|
<div className='dataGroup'>
|
||||||
|
<h3>Google Information <i className='fab fa-google-drive'></i></h3>
|
||||||
|
<p><strong>Linked to Google: </strong> {this.props.uiItems.googleId ? 'YES' : 'NO'}</p>
|
||||||
|
{this.props.uiItems.googleId ? <p><strong>Brews on Google Drive: </strong> {this.props.uiItems.fileCount || '-'}</p> : '' }
|
||||||
|
</div>
|
||||||
|
</>;
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <UIPage brew={this.props.brew}>
|
||||||
|
{this.renderUiItems()}
|
||||||
|
</UIPage>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = AccountPage;
|
||||||
@@ -117,7 +117,7 @@ const BrewItem = createClass({
|
|||||||
<i className='fas fa-tags'/>
|
<i className='fas fa-tags'/>
|
||||||
{brew.tags.map((tag, idx)=>{
|
{brew.tags.map((tag, idx)=>{
|
||||||
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
|
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
|
||||||
return <span className={matches[1]}>{matches[2]}</span>;
|
return <span key={idx} className={matches[1]}>{matches[2]}</span>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</> : <></>
|
</> : <></>
|
||||||
|
|||||||
@@ -26,7 +26,29 @@
|
|||||||
font-size : 1.3em;
|
font-size : 1.3em;
|
||||||
font-style : italic;
|
font-style : italic;
|
||||||
}
|
}
|
||||||
|
.brewCollection {
|
||||||
|
h1:hover{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.active::before, .inactive::before {
|
||||||
|
font-family: 'Font Awesome 5 Free';
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 0.6cm;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
.active {
|
||||||
|
color: var(--HB_Color_HeaderText);
|
||||||
|
}
|
||||||
|
.active::before {
|
||||||
|
content: '\f107';
|
||||||
|
}
|
||||||
|
.inactive {
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
.inactive::before {
|
||||||
|
content: '\f105';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sort-container{
|
.sort-container{
|
||||||
|
|||||||
38
client/homebrew/pages/basePages/uiPage/uiPage.jsx
Normal file
38
client/homebrew/pages/basePages/uiPage/uiPage.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
require('./uiPage.less');
|
||||||
|
const React = require('react');
|
||||||
|
const createClass = require('create-react-class');
|
||||||
|
|
||||||
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
const Navbar = require('../../../navbar/navbar.jsx');
|
||||||
|
const NewBrewItem = require('../../../navbar/newbrew.navitem.jsx');
|
||||||
|
const HelpNavItem = require('../../../navbar/help.navitem.jsx');
|
||||||
|
const RecentNavItem = require('../../../navbar/recent.navitem.jsx').both;
|
||||||
|
const Account = require('../../../navbar/account.navitem.jsx');
|
||||||
|
|
||||||
|
|
||||||
|
const UIPage = createClass({
|
||||||
|
displayName : 'UIPage',
|
||||||
|
|
||||||
|
render : function(){
|
||||||
|
return <div className='uiPage sitePage'>
|
||||||
|
<Navbar>
|
||||||
|
<Nav.section>
|
||||||
|
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
||||||
|
</Nav.section>
|
||||||
|
|
||||||
|
<Nav.section>
|
||||||
|
<NewBrewItem />
|
||||||
|
<HelpNavItem />
|
||||||
|
<RecentNavItem />
|
||||||
|
<Account />
|
||||||
|
</Nav.section>
|
||||||
|
</Navbar>
|
||||||
|
|
||||||
|
<div className='content'>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = UIPage;
|
||||||
47
client/homebrew/pages/basePages/uiPage/uiPage.less
Normal file
47
client/homebrew/pages/basePages/uiPage/uiPage.less
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.uiPage{
|
||||||
|
.content{
|
||||||
|
overflow-y : hidden;
|
||||||
|
width : 90vw;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 25px;
|
||||||
|
padding: 2% 4%;
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height: 1.8em;
|
||||||
|
.dataGroup{
|
||||||
|
padding: 6px 20px 15px;
|
||||||
|
border: 2px solid black;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 5px 0px;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4{
|
||||||
|
font-weight: 900;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0.5em 30% 0.25em 0;
|
||||||
|
border-bottom: 2px solid slategrey;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
border-bottom: 2px solid darkslategrey;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1.75em;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
svg {
|
||||||
|
width: 19px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ const EditPage = createClass({
|
|||||||
|
|
||||||
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')) }, ()=>{
|
this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{
|
||||||
if(this.state.autoSave){
|
if(this.state.autoSave){
|
||||||
this.trySave();
|
this.trySave();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ const PrintPage = createClass({
|
|||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<Meta name='robots' content='noindex, nofollow' />
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
|
<link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/>
|
||||||
{baseThemePath &&
|
{baseThemePath &&
|
||||||
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/>
|
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,31 @@
|
|||||||
module.exports = async(name, title = '', props = {})=>{
|
const template = async function(name, title='', props = {}){
|
||||||
const HOMEBREWERY_PUBLIC_URL=props.config.publicUrl;
|
const ogTags = [];
|
||||||
|
const ogMeta = props.ogMeta ?? {};
|
||||||
|
Object.entries(ogMeta).forEach(([key, value])=>{
|
||||||
|
if(!value) return;
|
||||||
|
const tag = `<meta property="og:${key}" content="${value}">`;
|
||||||
|
ogTags.push(tag);
|
||||||
|
});
|
||||||
|
const ogMetaTags = ogTags.join('\n');
|
||||||
|
|
||||||
return `
|
return `<!DOCTYPE html>
|
||||||
<!DOCTYPE html>
|
<html>
|
||||||
<html>
|
<head>
|
||||||
<head>
|
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
|
||||||
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
|
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
<link href=${`/${name}/bundle.css`} rel='stylesheet' />
|
||||||
<link href=${`/${name}/bundle.css`} rel='stylesheet' />
|
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />
|
||||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />
|
${ogMetaTags}
|
||||||
<meta property="og:title" content="${props.brew?.title || 'Homebrewery - Untitled Brew'}">
|
<meta name="twitter:card" content="summary">
|
||||||
<meta property="og:url" content="${HOMEBREWERY_PUBLIC_URL}/${props.brew?.shareId ? `share/${props.brew.shareId}` : ''}">
|
<title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title>
|
||||||
<meta property="og:image" content="${props.brew?.thumbnail || `${HOMEBREWERY_PUBLIC_URL}/thumbnail.png`}">
|
</head>
|
||||||
<meta property="og:description" content="${props.brew?.description || 'No description.'}">
|
<body>
|
||||||
<meta property="og:site_name" content="The Homebrewery - Make your Homebrew content look legit!">
|
<main id="reactRoot">${require(`../build/${name}/ssr.js`)(props)}</main>
|
||||||
<meta property="og:type" content="article">
|
<script src=${`/${name}/bundle.js`}></script>
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<script>start_app(${JSON.stringify(props)})</script>
|
||||||
<title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title>
|
</body>
|
||||||
</head>
|
</html>
|
||||||
<body>
|
`;
|
||||||
<main id="reactRoot">${require(`../build/${name}/ssr.js`)(props)}</main>
|
|
||||||
<script src=${`/${name}/bundle.js`}></script>
|
|
||||||
<script>start_app(${JSON.stringify(props)})</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = template;
|
||||||
18651
package-lock.json
generated
18651
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "3.3.0",
|
"version": "3.4.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.11.x"
|
"node": "16.11.x"
|
||||||
},
|
},
|
||||||
@@ -51,8 +51,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.19.3",
|
"@babel/core": "^7.20.5",
|
||||||
"@babel/plugin-transform-runtime": "^7.19.1",
|
"@babel/plugin-transform-runtime": "^7.19.6",
|
||||||
"@babel/preset-env": "^7.19.4",
|
"@babel/preset-env": "^7.19.4",
|
||||||
"@babel/preset-react": "^7.18.6",
|
"@babel/preset-react": "^7.18.6",
|
||||||
"body-parser": "^1.20.1",
|
"body-parser": "^1.20.1",
|
||||||
@@ -60,35 +60,36 @@
|
|||||||
"codemirror": "^5.65.6",
|
"codemirror": "^5.65.6",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"create-react-class": "^15.7.0",
|
"create-react-class": "^15.7.0",
|
||||||
"dedent-tabs": "^0.10.1",
|
"dedent-tabs": "^0.10.2",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "2.1.7",
|
"express-static-gzip": "2.1.7",
|
||||||
"fs-extra": "10.1.0",
|
"fs-extra": "11.1.0",
|
||||||
"googleapis": "108.0.0",
|
"googleapis": "109.0.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
"less": "^3.13.1",
|
"less": "^3.13.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "4.1.1",
|
"marked": "4.2.4",
|
||||||
"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.6.5",
|
"mongoose": "^6.8.0",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"nconf": "^0.12.0",
|
"nconf": "^0.12.0",
|
||||||
"react": "^16.14.0",
|
"npm": "^8.10.0",
|
||||||
"react-dom": "^16.14.0",
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
"react-frame-component": "4.1.3",
|
"react-frame-component": "4.1.3",
|
||||||
"react-router-dom": "6.4.2",
|
"react-router-dom": "6.4.5",
|
||||||
"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.25.0",
|
"eslint": "^8.29.0",
|
||||||
"eslint-plugin-react": "^7.31.10",
|
"eslint-plugin-react": "^7.31.11",
|
||||||
"jest": "^29.2.1",
|
"jest": "^29.2.2",
|
||||||
"supertest": "^6.3.0"
|
"supertest": "^6.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
138
server/app.js
138
server/app.js
@@ -1,4 +1,4 @@
|
|||||||
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
|
/*eslint max-lines: ["warn", {"max": 400, "skipBlankLines": true, "skipComments": true}]*/
|
||||||
// Set working directory to project root
|
// Set working directory to project root
|
||||||
process.chdir(`${__dirname}/..`);
|
process.chdir(`${__dirname}/..`);
|
||||||
|
|
||||||
@@ -76,6 +76,14 @@ const faqText = require('fs').readFileSync('faq.md', 'utf8');
|
|||||||
|
|
||||||
String.prototype.replaceAll = function(s, r){return this.split(s).join(r);};
|
String.prototype.replaceAll = function(s, r){return this.split(s).join(r);};
|
||||||
|
|
||||||
|
const defaultMetaTags = {
|
||||||
|
site_name : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||||
|
title : 'The Homebrewery',
|
||||||
|
description : 'A NaturalCrit Tool for creating authentic Homebrews using Markdown.',
|
||||||
|
image : `${config.get('publicUrl')}/thumbnail.png`,
|
||||||
|
type : 'website'
|
||||||
|
};
|
||||||
|
|
||||||
//Robots.txt
|
//Robots.txt
|
||||||
app.get('/robots.txt', (req, res)=>{
|
app.get('/robots.txt', (req, res)=>{
|
||||||
return res.sendFile(`robots.txt`, { root: process.cwd() });
|
return res.sendFile(`robots.txt`, { root: process.cwd() });
|
||||||
@@ -86,17 +94,29 @@ app.get('/', (req, res, next)=>{
|
|||||||
req.brew = {
|
req.brew = {
|
||||||
text : welcomeText,
|
text : welcomeText,
|
||||||
renderer : 'V3'
|
renderer : 'V3'
|
||||||
|
},
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : 'Homepage',
|
||||||
|
description : 'Homepage'
|
||||||
};
|
};
|
||||||
|
|
||||||
splitTextStyleAndMetadata(req.brew);
|
splitTextStyleAndMetadata(req.brew);
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Home page v3
|
//Home page Legacy
|
||||||
app.get('/legacy', (req, res, next)=>{
|
app.get('/legacy', (req, res, next)=>{
|
||||||
req.brew = {
|
req.brew = {
|
||||||
text : welcomeTextLegacy,
|
text : welcomeTextLegacy,
|
||||||
renderer : 'legacy'
|
renderer : 'legacy'
|
||||||
|
},
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : 'Homepage (Legacy)',
|
||||||
|
description : 'Homepage'
|
||||||
};
|
};
|
||||||
|
|
||||||
splitTextStyleAndMetadata(req.brew);
|
splitTextStyleAndMetadata(req.brew);
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
@@ -106,7 +126,13 @@ app.get('/migrate', (req, res, next)=>{
|
|||||||
req.brew = {
|
req.brew = {
|
||||||
text : migrateText,
|
text : migrateText,
|
||||||
renderer : 'V3'
|
renderer : 'V3'
|
||||||
|
},
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : 'v3 Migration Guide',
|
||||||
|
description : 'A brief guide to converting Legacy documents to the v3 renderer.'
|
||||||
};
|
};
|
||||||
|
|
||||||
splitTextStyleAndMetadata(req.brew);
|
splitTextStyleAndMetadata(req.brew);
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
@@ -117,7 +143,13 @@ app.get('/changelog', async (req, res, next)=>{
|
|||||||
title : 'Changelog',
|
title : 'Changelog',
|
||||||
text : changelogText,
|
text : changelogText,
|
||||||
renderer : 'V3'
|
renderer : 'V3'
|
||||||
|
},
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : 'Changelog',
|
||||||
|
description : 'Development changelog.'
|
||||||
};
|
};
|
||||||
|
|
||||||
splitTextStyleAndMetadata(req.brew);
|
splitTextStyleAndMetadata(req.brew);
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
@@ -128,7 +160,13 @@ app.get('/faq', async (req, res, next)=>{
|
|||||||
title : 'FAQ',
|
title : 'FAQ',
|
||||||
text : faqText,
|
text : faqText,
|
||||||
renderer : 'V3'
|
renderer : 'V3'
|
||||||
|
},
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : 'FAQ',
|
||||||
|
description : 'Frequently Asked Questions'
|
||||||
};
|
};
|
||||||
|
|
||||||
splitTextStyleAndMetadata(req.brew);
|
splitTextStyleAndMetadata(req.brew);
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
@@ -152,12 +190,19 @@ app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{
|
|||||||
sanitizeBrew(brew, 'share');
|
sanitizeBrew(brew, 'share');
|
||||||
const prefix = 'HB - ';
|
const prefix = 'HB - ';
|
||||||
|
|
||||||
|
const encodeRFC3986ValueChars = (str)=>{
|
||||||
|
return (
|
||||||
|
encodeURIComponent(str)
|
||||||
|
.replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
|
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
|
||||||
if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
|
if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
|
||||||
res.set({
|
res.set({
|
||||||
'Cache-Control' : 'no-cache',
|
'Cache-Control' : 'no-cache',
|
||||||
'Content-Type' : 'text/plain',
|
'Content-Type' : 'text/plain',
|
||||||
'Content-Disposition' : `attachment; filename="${fileName}.txt"`
|
'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt`
|
||||||
});
|
});
|
||||||
res.status(200).send(brew.text);
|
res.status(200).send(brew.text);
|
||||||
});
|
});
|
||||||
@@ -166,6 +211,12 @@ app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{
|
|||||||
app.get('/user/:username', async (req, res, next)=>{
|
app.get('/user/:username', async (req, res, next)=>{
|
||||||
const ownAccount = req.account && (req.account.username == req.params.username);
|
const ownAccount = req.account && (req.account.username == req.params.username);
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : `${req.params.username}'s Collection`,
|
||||||
|
description : 'View my collection of homebrew on the Homebrewery.'
|
||||||
|
// type : could be 'profile'?
|
||||||
|
};
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
'googleId',
|
'googleId',
|
||||||
'title',
|
'title',
|
||||||
@@ -223,6 +274,15 @@ app.get('/user/:username', async (req, res, next)=>{
|
|||||||
//Edit Page
|
//Edit Page
|
||||||
app.get('/edit/:id', asyncHandler(getBrew('edit')), (req, res, next)=>{
|
app.get('/edit/:id', asyncHandler(getBrew('edit')), (req, res, next)=>{
|
||||||
req.brew = req.brew.toObject ? req.brew.toObject() : req.brew;
|
req.brew = req.brew.toObject ? req.brew.toObject() : req.brew;
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : req.brew.title || 'Untitled Brew',
|
||||||
|
description : req.brew.description || 'No description.',
|
||||||
|
image : req.brew.thumbnail || defaultMetaTags.image,
|
||||||
|
|
||||||
|
type : 'article'
|
||||||
|
};
|
||||||
|
|
||||||
sanitizeBrew(req.brew, 'edit');
|
sanitizeBrew(req.brew, 'edit');
|
||||||
splitTextStyleAndMetadata(req.brew);
|
splitTextStyleAndMetadata(req.brew);
|
||||||
res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save.
|
res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save.
|
||||||
@@ -234,6 +294,12 @@ 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}`;
|
req.brew.title = `CLONE - ${req.brew.title}`;
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : 'New',
|
||||||
|
description : 'Start crafting your homebrew on the Homebrewery!'
|
||||||
|
};
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -241,6 +307,13 @@ app.get('/new/:id', asyncHandler(getBrew('share')), (req, res, next)=>{
|
|||||||
app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
|
app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
|
||||||
const { brew } = req;
|
const { brew } = req;
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : req.brew.title || 'Untitled Brew',
|
||||||
|
description : req.brew.description || 'No description.',
|
||||||
|
image : req.brew.thumbnail || defaultMetaTags.image,
|
||||||
|
type : 'article'
|
||||||
|
};
|
||||||
|
|
||||||
if(req.params.id.length > 12 && !brew._id) {
|
if(req.params.id.length > 12 && !brew._id) {
|
||||||
const googleId = req.params.id.slice(0, -12);
|
const googleId = req.params.id.slice(0, -12);
|
||||||
const shareId = req.params.id.slice(-12);
|
const shareId = req.params.id.slice(-12);
|
||||||
@@ -261,6 +334,60 @@ app.get('/print/:id', asyncHandler(getBrew('share')), (req, res, next)=>{
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Account Page
|
||||||
|
app.get('/account', asyncHandler(async (req, res, next)=>{
|
||||||
|
const data = {};
|
||||||
|
data.title = 'Account Information Page';
|
||||||
|
|
||||||
|
let auth;
|
||||||
|
let files;
|
||||||
|
if(req.account) {
|
||||||
|
if(req.account.googleId) {
|
||||||
|
try {
|
||||||
|
auth = await GoogleActions.authCheck(req.account, res, false);
|
||||||
|
} catch (e) {
|
||||||
|
auth = undefined;
|
||||||
|
console.log('Google auth check failed!');
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
if(auth.credentials.access_token) {
|
||||||
|
try {
|
||||||
|
files = await GoogleActions.listGoogleBrews(auth);
|
||||||
|
} catch (e) {
|
||||||
|
files = undefined;
|
||||||
|
console.log('List Google files failed!');
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = { authors: req.account.username, googleId: { $exists: false } };
|
||||||
|
const brews = await HomebrewModel.find(query, 'id')
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
data.uiItems = {
|
||||||
|
username : req.account.username,
|
||||||
|
issued : req.account.issued,
|
||||||
|
mongoCount : brews.length,
|
||||||
|
googleId : Boolean(req.account.googleId),
|
||||||
|
authCheck : Boolean(req.account.googleId && auth.credentials.access_token),
|
||||||
|
fileCount : files?.length || '-'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
req.brew = data;
|
||||||
|
|
||||||
|
req.ogMeta = { ...defaultMetaTags,
|
||||||
|
title : `Account Page`,
|
||||||
|
description : null
|
||||||
|
};
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
const nodeEnv = config.get('node_env');
|
const nodeEnv = config.get('node_env');
|
||||||
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
|
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
|
||||||
// Local only
|
// Local only
|
||||||
@@ -275,8 +402,6 @@ if(isLocalEnvironment){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Render the page
|
//Render the page
|
||||||
const templateFn = require('./../client/template.js');
|
const templateFn = require('./../client/template.js');
|
||||||
app.use(asyncHandler(async (req, res, next)=>{
|
app.use(asyncHandler(async (req, res, next)=>{
|
||||||
@@ -295,7 +420,8 @@ app.use(asyncHandler(async (req, res, next)=>{
|
|||||||
account : req.account,
|
account : req.account,
|
||||||
enable_v3 : config.get('enable_v3'),
|
enable_v3 : config.get('enable_v3'),
|
||||||
enable_themes : config.get('enable_themes'),
|
enable_themes : config.get('enable_themes'),
|
||||||
config : configuration
|
config : configuration,
|
||||||
|
ogMeta : req.ogMeta
|
||||||
};
|
};
|
||||||
const title = req.brew ? req.brew.title : '';
|
const title = req.brew ? req.brew.title : '';
|
||||||
const page = await templateFn('homebrew', title, props)
|
const page = await templateFn('homebrew', title, props)
|
||||||
|
|||||||
@@ -5,24 +5,28 @@ const { nanoid } = require('nanoid');
|
|||||||
const token = require('./token.js');
|
const token = require('./token.js');
|
||||||
const config = require('./config.js');
|
const config = require('./config.js');
|
||||||
|
|
||||||
const keys = typeof(config.get('service_account')) == 'string' ?
|
|
||||||
JSON.parse(config.get('service_account')) :
|
|
||||||
config.get('service_account');
|
|
||||||
let serviceAuth;
|
let serviceAuth;
|
||||||
try {
|
if(!config.get('service_account')){
|
||||||
serviceAuth = google.auth.fromJSON(keys);
|
console.log('No Google Service Account in config files - Google Drive integration will not be available.');
|
||||||
serviceAuth.scopes = [
|
} else {
|
||||||
'https://www.googleapis.com/auth/drive'
|
const keys = typeof(config.get('service_account')) == 'string' ?
|
||||||
];
|
JSON.parse(config.get('service_account')) :
|
||||||
} catch (err) {
|
config.get('service_account');
|
||||||
console.warn(err);
|
|
||||||
console.log('Please make sure that a Google Service Account is set up properly in your config files.');
|
try {
|
||||||
|
serviceAuth = google.auth.fromJSON(keys);
|
||||||
|
serviceAuth.scopes = ['https://www.googleapis.com/auth/drive'];
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
console.log('Please make sure the Google Service Account is set up properly in your config files.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
google.options({ auth: serviceAuth || config.get('google_api_key') });
|
google.options({ auth: serviceAuth || config.get('google_api_key') });
|
||||||
|
|
||||||
const GoogleActions = {
|
const GoogleActions = {
|
||||||
|
|
||||||
authCheck : (account, res)=>{
|
authCheck : (account, res, updateTokens=true)=>{
|
||||||
if(!account || !account.googleId){ // If not signed into Google
|
if(!account || !account.googleId){ // If not signed into Google
|
||||||
const err = new Error('Not Signed In');
|
const err = new Error('Not Signed In');
|
||||||
err.status = 401;
|
err.status = 401;
|
||||||
@@ -40,7 +44,7 @@ const GoogleActions = {
|
|||||||
refresh_token : account.googleRefreshToken
|
refresh_token : account.googleRefreshToken
|
||||||
});
|
});
|
||||||
|
|
||||||
oAuth2Client.on('tokens', (tokens)=>{
|
updateTokens && oAuth2Client.on('tokens', (tokens)=>{
|
||||||
if(tokens.refresh_token) {
|
if(tokens.refresh_token) {
|
||||||
account.googleRefreshToken = tokens.refresh_token;
|
account.googleRefreshToken = tokens.refresh_token;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const mustacheSpans = {
|
|||||||
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token
|
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token
|
||||||
const inlineRegex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
|
const inlineRegex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g;
|
||||||
const match = completeSpan.exec(src);
|
const match = completeSpan.exec(src);
|
||||||
if(match) {
|
if(match) {
|
||||||
//Find closing delimiter
|
//Find closing delimiter
|
||||||
@@ -82,7 +82,7 @@ const mustacheDivs = {
|
|||||||
start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
|
const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
|
||||||
const blockRegex = /^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/gm;
|
const blockRegex = /^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/gm;
|
||||||
const match = completeBlock.exec(src);
|
const match = completeBlock.exec(src);
|
||||||
if(match) {
|
if(match) {
|
||||||
//Find closing delimiter
|
//Find closing delimiter
|
||||||
@@ -130,7 +130,7 @@ const mustacheInjectInline = {
|
|||||||
level : 'inline',
|
level : 'inline',
|
||||||
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const inlineRegex = /^ *{((?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*)}/g;
|
const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g;
|
||||||
const match = inlineRegex.exec(src);
|
const match = inlineRegex.exec(src);
|
||||||
if(match) {
|
if(match) {
|
||||||
const lastToken = tokens[tokens.length - 1];
|
const lastToken = tokens[tokens.length - 1];
|
||||||
@@ -165,7 +165,7 @@ const mustacheInjectBlock = {
|
|||||||
level : 'block',
|
level : 'block',
|
||||||
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||||
tokenizer(src, tokens) {
|
tokenizer(src, tokens) {
|
||||||
const inlineRegex = /^ *{((?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*)}/ym;
|
const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/ym;
|
||||||
const match = inlineRegex.exec(src);
|
const match = inlineRegex.exec(src);
|
||||||
if(match) {
|
if(match) {
|
||||||
const lastToken = tokens[tokens.length - 1];
|
const lastToken = tokens[tokens.length - 1];
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ describe('Tests for static pages', ()=>{
|
|||||||
return app.get('/').expect(200);
|
return app.get('/').expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Home page v3 works', ()=>{
|
it('Home page legacy works', ()=>{
|
||||||
return app.get('/v3_preview').expect(200);
|
return app.get('/legacy').expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Changelog page works', ()=>{
|
it('Changelog page works', ()=>{
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ body {
|
|||||||
text-rendering : optimizeLegibility;
|
text-rendering : optimizeLegibility;
|
||||||
page-break-before : always;
|
page-break-before : always;
|
||||||
page-break-after : always;
|
page-break-after : always;
|
||||||
|
contain : size;
|
||||||
//*****************************
|
//*****************************
|
||||||
// * BASE
|
// * BASE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
|
|||||||
@@ -77,9 +77,12 @@ body {
|
|||||||
text-rendering : optimizeLegibility;
|
text-rendering : optimizeLegibility;
|
||||||
page-break-before : always;
|
page-break-before : always;
|
||||||
page-break-after : always;
|
page-break-after : always;
|
||||||
|
}
|
||||||
//*****************************
|
//*****************************
|
||||||
// * BASE
|
// * BASE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
|
|
||||||
|
.page{
|
||||||
p{
|
p{
|
||||||
overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
|
overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
|
||||||
display : block;
|
display : block;
|
||||||
@@ -155,9 +158,9 @@ body {
|
|||||||
padding-bottom : 2px;
|
padding-bottom : 2px;
|
||||||
margin-bottom : -20px;
|
margin-bottom : -20px;
|
||||||
background-image : linear-gradient(-45deg, #322814, #998250, #322814);
|
background-image : linear-gradient(-45deg, #322814, #998250, #322814);
|
||||||
background-clip : text;
|
background-clip : text;
|
||||||
-webkit-background-clip : text;
|
-webkit-background-clip : text;
|
||||||
color : rgba(0, 0, 0, 0);
|
color : rgba(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
&+p::first-line{
|
&+p::first-line{
|
||||||
font-variant : small-caps;
|
font-variant : small-caps;
|
||||||
@@ -175,12 +178,24 @@ body {
|
|||||||
font-size : 0.575cm;
|
font-size : 0.575cm;
|
||||||
border-bottom : 2px solid var(--HB_Color_HeaderUnderline);;
|
border-bottom : 2px solid var(--HB_Color_HeaderUnderline);;
|
||||||
line-height : 0.995em; //Font is misaligned. Shift up slightly
|
line-height : 0.995em; //Font is misaligned. Shift up slightly
|
||||||
|
& + * {
|
||||||
|
margin-top: 0.17cm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
* + h3 {
|
||||||
|
margin-top : 0.155cm; //(0.325 - 0.17)
|
||||||
}
|
}
|
||||||
h4{
|
h4{
|
||||||
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly
|
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly
|
||||||
//margin-bottom : 0.02cm;
|
//margin-bottom : 0.02cm;
|
||||||
font-size : 0.458cm;
|
font-size : 0.458cm;
|
||||||
line-height : 0.971em; //Font is misaligned. Shift up slightly
|
line-height : 0.971em; //Font is misaligned. Shift up slightly
|
||||||
|
& + * {
|
||||||
|
margin-top: 0.09cm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
* + h4 {
|
||||||
|
margin-top : 0.235cm; //(0.325 - 0.09)
|
||||||
}
|
}
|
||||||
h5{
|
h5{
|
||||||
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly
|
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly
|
||||||
@@ -199,6 +214,7 @@ body {
|
|||||||
table{
|
table{
|
||||||
.useSansSerif();
|
.useSansSerif();
|
||||||
width : 100%;
|
width : 100%;
|
||||||
|
line-height : 16px;
|
||||||
& + * {
|
& + * {
|
||||||
margin-top : 0.325cm;
|
margin-top : 0.325cm;
|
||||||
}
|
}
|
||||||
@@ -207,15 +223,17 @@ body {
|
|||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
th{
|
th{
|
||||||
vertical-align : bottom;
|
vertical-align : bottom;
|
||||||
padding : 0.14em 0.4em;
|
//padding : 0.14em 0.4em;
|
||||||
|
padding : 0px 1.5px; // Both of these are temporary, just to force
|
||||||
|
//line-height : 16px; // PDF to render at same height until Chrome 108
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tbody{
|
tbody{
|
||||||
tr{
|
tr{
|
||||||
td{
|
td{
|
||||||
//padding : 0.14em 0.4em;
|
//padding : 0.14em 0.4em;
|
||||||
padding : 0px 5px; // Both of these are temporary, just to force
|
padding : 0px 1.5px; // Both of these are temporary, just to force
|
||||||
height : 16px; // PDF to render at same height until Chrome 108
|
//line-height : 16px; // PDF to render at same height until Chrome 108
|
||||||
}
|
}
|
||||||
&:nth-child(odd){
|
&:nth-child(odd){
|
||||||
background-color : var(--HB_Color_Accent);
|
background-color : var(--HB_Color_Accent);
|
||||||
@@ -627,6 +645,9 @@ body {
|
|||||||
&.wide:first-child {
|
&.wide:first-child {
|
||||||
margin-top: 0.12cm;
|
margin-top: 0.12cm;
|
||||||
}
|
}
|
||||||
|
& + * {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.decoration {
|
&.decoration {
|
||||||
position:relative;
|
position:relative;
|
||||||
@@ -733,24 +754,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//*****************************
|
|
||||||
// * MUSTACHE DIVS/SPANS
|
|
||||||
// *****************************/
|
|
||||||
.page {
|
|
||||||
.block {
|
|
||||||
break-inside : avoid;
|
|
||||||
display : inline-block;
|
|
||||||
.page :where(&) {
|
|
||||||
width : 100%;
|
|
||||||
}
|
|
||||||
//-webkit-transform : translateZ(0); //Prevents shadows from breaking across columns
|
|
||||||
}
|
|
||||||
.inline-block {
|
|
||||||
display : inline-block;
|
|
||||||
text-indent : initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************
|
//*****************************
|
||||||
// * DEFINITION LISTS
|
// * DEFINITION LISTS
|
||||||
// *****************************/
|
// *****************************/
|
||||||
@@ -781,26 +784,13 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//*****************************
|
|
||||||
// * BLANK LINE
|
|
||||||
// *****************************/
|
|
||||||
.page {
|
|
||||||
.blank {
|
|
||||||
height : 1em;
|
|
||||||
margin-top : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************
|
//*****************************
|
||||||
// * WIDE
|
// * WIDE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
.page .wide{
|
.page .wide{
|
||||||
column-span : all;
|
margin-bottom : 0.325cm;
|
||||||
-webkit-column-span : all;
|
}
|
||||||
-moz-column-span : all;
|
|
||||||
display : block;
|
.page h1 + *{
|
||||||
margin-bottom : 0.34cm;
|
margin-top : 0;
|
||||||
&+* {
|
|
||||||
margin-top : 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,12 @@ body {
|
|||||||
text-rendering : optimizeLegibility;
|
text-rendering : optimizeLegibility;
|
||||||
page-break-before : always;
|
page-break-before : always;
|
||||||
page-break-after : always;
|
page-break-after : always;
|
||||||
|
contain : size;
|
||||||
}
|
}
|
||||||
//*****************************
|
//*****************************
|
||||||
// * BASE
|
// * BASE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
:where(.page){
|
.page{
|
||||||
p{
|
p{
|
||||||
overflow-wrap : break-word;
|
overflow-wrap : break-word;
|
||||||
display : block;
|
display : block;
|
||||||
@@ -77,13 +78,7 @@ body {
|
|||||||
img{
|
img{
|
||||||
z-index : -1;
|
z-index : -1;
|
||||||
}
|
}
|
||||||
:not(:where(.wide,.columnSplit,.blank,hr)) + :where(h1,h2,h3,h4,h5,h6,table,dl,.block) {
|
|
||||||
margin-top : 1em; //NOTE: MAKE ALL MARGINS TOP-ONLY FOR BEST RESULTS WITH COLUMN BREAKS. USE * + * STYLE SELECTORS
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(h1,h3,h3,h4,h5,h6) + * {
|
|
||||||
margin-top : 0;
|
|
||||||
}
|
|
||||||
//*****************************
|
//*****************************
|
||||||
// * HEADERS
|
// * HEADERS
|
||||||
// *****************************/
|
// *****************************/
|
||||||
@@ -116,6 +111,9 @@ body {
|
|||||||
font-weight : bold;
|
font-weight : bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
div:not(.columnWrapper) > table + table { // Side-by-side tables should not
|
||||||
|
margin-top : 0; // have vertical spacing.
|
||||||
|
}
|
||||||
|
|
||||||
/* Watermark */
|
/* Watermark */
|
||||||
.watermark {
|
.watermark {
|
||||||
@@ -191,6 +189,10 @@ body {
|
|||||||
-webkit-column-break-after : always;
|
-webkit-column-break-after : always;
|
||||||
break-after : always;
|
break-after : always;
|
||||||
-moz-column-break-after : always;
|
-moz-column-break-after : always;
|
||||||
|
margin-top : 0;
|
||||||
|
& + * {
|
||||||
|
margin-top : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//Avoid breaking up
|
//Avoid breaking up
|
||||||
blockquote,table{
|
blockquote,table{
|
||||||
@@ -214,13 +216,11 @@ body {
|
|||||||
//*****************************
|
//*****************************
|
||||||
// * MUSTACHE DIVS/SPANS
|
// * MUSTACHE DIVS/SPANS
|
||||||
// *****************************/
|
// *****************************/
|
||||||
:where(.page) {
|
.page {
|
||||||
.block {
|
.block {
|
||||||
break-inside : avoid;
|
break-inside : avoid;
|
||||||
display : inline-block;
|
display : inline-block;
|
||||||
.page :where(&) {
|
width : 100%;
|
||||||
width : 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.inline-block {
|
.inline-block {
|
||||||
display : inline-block;
|
display : inline-block;
|
||||||
@@ -231,7 +231,7 @@ body {
|
|||||||
//*****************************
|
//*****************************
|
||||||
// * DEFINITION LISTS
|
// * DEFINITION LISTS
|
||||||
// *****************************/
|
// *****************************/
|
||||||
:where(.page) {
|
.page {
|
||||||
dl {
|
dl {
|
||||||
padding-left : 1em;
|
padding-left : 1em;
|
||||||
white-space : pre-line;
|
white-space : pre-line;
|
||||||
@@ -251,17 +251,20 @@ body {
|
|||||||
//*****************************
|
//*****************************
|
||||||
// * BLANK LINE
|
// * BLANK LINE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
:where(.page) {
|
.page {
|
||||||
.blank {
|
.blank {
|
||||||
height : 1em;
|
height : 1em;
|
||||||
margin-top : 0;
|
margin-top : 0;
|
||||||
|
& + * {
|
||||||
|
margin-top : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//*****************************
|
//*****************************
|
||||||
// * WIDE
|
// * WIDE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
:where(.page) {
|
.page {
|
||||||
.wide{
|
.wide{
|
||||||
column-span : all;
|
column-span : all;
|
||||||
display : block;
|
display : block;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
//*****************************
|
//*****************************
|
||||||
// * BASE
|
// * BASE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
:where(.page){
|
.page{
|
||||||
color : var(--HB_Color_Text);
|
color : var(--HB_Color_Text);
|
||||||
font-family : ReenieBeanie;
|
font-family : ReenieBeanie;
|
||||||
font-size : 0.53cm;
|
font-size : 0.53cm;
|
||||||
@@ -554,6 +554,6 @@
|
|||||||
//*****************************
|
//*****************************
|
||||||
// * WIDE
|
// * WIDE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
:where(.page) .wide {
|
.page .wide {
|
||||||
margin-bottom : 0.45cm;
|
margin-bottom : 0.45cm;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user