mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-24 18:32:41 +00:00
Merge branch 'master' into remove-google-get-during-update
This commit is contained in:
@@ -27,7 +27,7 @@ jobs:
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
|
||||
- node/install-npm
|
||||
- run: sudo npm install -g npm@8.10.0
|
||||
- node/install-packages:
|
||||
app-dir: ~/homebrewery
|
||||
cache-path: node_modules
|
||||
@@ -55,13 +55,13 @@ jobs:
|
||||
at: .
|
||||
|
||||
# run tests!
|
||||
- run:
|
||||
- run:
|
||||
name: Test - Basic
|
||||
command: npm run test:basic
|
||||
- run:
|
||||
- run:
|
||||
name: Test - Mustache Spans
|
||||
command: npm run test:mustache-span
|
||||
- run:
|
||||
- run:
|
||||
name: Test - Routes
|
||||
command: npm run test:route
|
||||
|
||||
@@ -71,4 +71,4 @@ workflows:
|
||||
- build
|
||||
- test:
|
||||
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!
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: "Please confirm:"
|
||||
options:
|
||||
- label: I have searched the Issues tracker for any duplicate requests and found none.
|
||||
required: true
|
||||
value: "Please be sure to search for any close matches to your request in the GitHub Issues tracker before opening a new request, thanks!"
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/general_issue.yml
vendored
7
.github/ISSUE_TEMPLATE/general_issue.yml
vendored
@@ -4,14 +4,15 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please include as much information as possible.
|
||||
- type: checkboxes
|
||||
- type: dropdown
|
||||
id: renderer
|
||||
attributes:
|
||||
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).
|
||||
options:
|
||||
- label: Legacy
|
||||
- label: v3
|
||||
- v3
|
||||
- Legacy
|
||||
- Both
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
17
changelog.md
17
changelog.md
@@ -45,6 +45,21 @@ pre {
|
||||
## changelog
|
||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||
|
||||
### 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
|
||||
{{taskList
|
||||
|
||||
@@ -76,8 +91,6 @@ Fixes issues [#2135](https://github.com/naturalcrit/homebrewery/issues/2135)
|
||||
Fixes issues [#2427](https://github.com/naturalcrit/homebrewery/issues/2427)
|
||||
|
||||
|
||||
|
||||
|
||||
##### Gazook:
|
||||
|
||||
* [x] Several updates to bug reporting and error popups
|
||||
|
||||
@@ -137,9 +137,17 @@ const Editor = createClass({
|
||||
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||
}
|
||||
|
||||
// Highlight injectors {style}
|
||||
if(line.includes('{') && line.includes('}')){
|
||||
const regex = /(?<!{){(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g;
|
||||
let match;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'injection' });
|
||||
}
|
||||
}
|
||||
// Highlight inline spans {{content}}
|
||||
if(line.includes('{{') && line.includes('}}')){
|
||||
const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
|
||||
const regex = /{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g;
|
||||
let match;
|
||||
let blockCount = 0;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
@@ -158,7 +166,7 @@ const Editor = createClass({
|
||||
// Highlight block divs {{\n Content \n}}
|
||||
let endCh = line.length+1;
|
||||
|
||||
const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/);
|
||||
const match = line.match(/^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/);
|
||||
if(match)
|
||||
endCh = match.index+match[0].length;
|
||||
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
font-weight : bold;
|
||||
//font-style: italic;
|
||||
}
|
||||
.injection{
|
||||
color : green;
|
||||
font-weight : bold;
|
||||
}
|
||||
}
|
||||
|
||||
.brewJump{
|
||||
|
||||
@@ -11,6 +11,7 @@ const SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||
const NewPage = require('./pages/newPage/newPage.jsx');
|
||||
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||
const PrintPage = require('./pages/printPage/printPage.jsx');
|
||||
const AccountPage = require('./pages/accountPage/accountPage.jsx');
|
||||
|
||||
const WithRoute = (props)=>{
|
||||
const params = useParams();
|
||||
@@ -61,24 +62,27 @@ const Homebrew = createClass({
|
||||
},
|
||||
|
||||
render : function (){
|
||||
return <Router location={this.props.url}>
|
||||
<div className='homebrew'>
|
||||
<Routes>
|
||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={this.props.brew} />} />
|
||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} />} />
|
||||
<Route path='/new' element={<WithRoute el={NewPage}/>} />
|
||||
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} />
|
||||
<Route path='/print/:id' element={<WithRoute el={PrintPage} brew={this.props.brew} />} />
|
||||
<Route path='/print' element={<WithRoute el={PrintPage} />} />
|
||||
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>;
|
||||
return (
|
||||
<Router location={this.props.url}>
|
||||
<div className='homebrew'>
|
||||
<Routes>
|
||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={this.props.brew} />} />
|
||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} />} />
|
||||
<Route path='/new' element={<WithRoute el={NewPage}/>} />
|
||||
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} />
|
||||
<Route path='/print/:id' element={<WithRoute el={PrintPage} brew={this.props.brew} />} />
|
||||
<Route path='/print' element={<WithRoute el={PrintPage} />} />
|
||||
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||
<Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} uiItems={this.props.brew.uiItems} />} />
|
||||
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -76,6 +76,14 @@ const Account = createClass({
|
||||
>
|
||||
brews
|
||||
</Nav.item>
|
||||
<Nav.item
|
||||
className='account'
|
||||
color='orange'
|
||||
icon='fas fa-user'
|
||||
href='/account'
|
||||
>
|
||||
account
|
||||
</Nav.item>
|
||||
<Nav.item
|
||||
className='logout'
|
||||
color='red'
|
||||
|
||||
@@ -20,6 +20,12 @@ module.exports = function(props){
|
||||
rel='noopener noreferrer'>
|
||||
report issue
|
||||
</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'
|
||||
href='/migrate'
|
||||
newTab={true}
|
||||
|
||||
@@ -115,8 +115,36 @@
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
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{
|
||||
background-color : @blue;
|
||||
|
||||
.clear{
|
||||
display : grid;
|
||||
place-content : center;
|
||||
}
|
||||
}
|
||||
.title{
|
||||
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(){
|
||||
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 ]'}>
|
||||
<span className='title'>{brew.title || '[ no title ]'}</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>;
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -26,7 +26,29 @@
|
||||
font-size : 1.3em;
|
||||
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{
|
||||
|
||||
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.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) }, ()=>{
|
||||
this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{
|
||||
if(this.state.autoSave){
|
||||
this.trySave();
|
||||
} else {
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
module.exports = async(name, title = '', props = {})=>{
|
||||
const HOMEBREWERY_PUBLIC_URL=props.config.publicUrl;
|
||||
const template = async function(name, title='', props = {}){
|
||||
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 `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<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=${`/${name}/bundle.css`} rel='stylesheet' />
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />
|
||||
<meta property="og:title" content="${props.brew?.title || 'Homebrewery - Untitled Brew'}">
|
||||
<meta property="og:url" content="${HOMEBREWERY_PUBLIC_URL}/${props.brew?.shareId ? `share/${props.brew.shareId}` : ''}">
|
||||
<meta property="og:image" content="${props.brew?.thumbnail || `${HOMEBREWERY_PUBLIC_URL}/thumbnail.png`}">
|
||||
<meta property="og:description" content="${props.brew?.description || 'No description.'}">
|
||||
<meta property="og:site_name" content="The Homebrewery - Make your Homebrew content look legit!">
|
||||
<meta property="og:type" content="article">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title>
|
||||
</head>
|
||||
<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>
|
||||
`;
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<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=${`/${name}/bundle.css`} rel='stylesheet' />
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />
|
||||
${ogMetaTags}
|
||||
<meta name="twitter:card" content="summary">
|
||||
<title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title>
|
||||
</head>
|
||||
<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;
|
||||
17680
package-lock.json
generated
17680
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "3.3.0",
|
||||
"version": "3.3.1",
|
||||
"engines": {
|
||||
"node": "16.11.x"
|
||||
},
|
||||
@@ -60,27 +60,28 @@
|
||||
"codemirror": "^5.65.6",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"create-react-class": "^15.7.0",
|
||||
"dedent-tabs": "^0.10.1",
|
||||
"dedent-tabs": "^0.10.2",
|
||||
"express": "^4.18.2",
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-static-gzip": "2.1.7",
|
||||
"fs-extra": "10.1.0",
|
||||
"googleapis": "108.0.0",
|
||||
"googleapis": "109.0.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jwt-simple": "^0.5.6",
|
||||
"less": "^3.13.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "4.1.1",
|
||||
"marked": "4.2.2",
|
||||
"marked-extended-tables": "^1.0.5",
|
||||
"markedLegacy": "npm:marked@^0.3.19",
|
||||
"moment": "^2.29.4",
|
||||
"mongoose": "^6.7.0",
|
||||
"nanoid": "3.3.4",
|
||||
"nconf": "^0.12.0",
|
||||
"npm": "^8.10.0",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-frame-component": "4.1.3",
|
||||
"react-router-dom": "6.4.2",
|
||||
"react-router-dom": "6.4.3",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"superagent": "^6.1.0",
|
||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||
|
||||
129
server/app.js
129
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
|
||||
process.chdir(`${__dirname}/..`);
|
||||
|
||||
@@ -87,16 +87,32 @@ app.get('/', (req, res, next)=>{
|
||||
req.brew = {
|
||||
text : welcomeText,
|
||||
renderer : 'V3'
|
||||
},
|
||||
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : 'Homepage',
|
||||
description : 'Homepage',
|
||||
thumbnail : `${config.get('publicUrl')}/thumbnail.png`,
|
||||
type : 'website'
|
||||
};
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
return next();
|
||||
});
|
||||
|
||||
//Home page v3
|
||||
//Home page Legacy
|
||||
app.get('/legacy', (req, res, next)=>{
|
||||
req.brew = {
|
||||
text : welcomeTextLegacy,
|
||||
renderer : 'legacy'
|
||||
},
|
||||
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : 'Homepage (Legacy)',
|
||||
description : 'Homepage',
|
||||
thumbnail : `${config.get('publicUrl')}/thumbnail.png`,
|
||||
type : 'website'
|
||||
};
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
return next();
|
||||
@@ -107,6 +123,14 @@ app.get('/migrate', (req, res, next)=>{
|
||||
req.brew = {
|
||||
text : migrateText,
|
||||
renderer : 'V3'
|
||||
},
|
||||
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : 'v3 Migration Guide',
|
||||
description : 'A brief guide to converting Legacy documents to the v3 renderer.',
|
||||
thumbnail : `${config.get('publicUrl')}/thumbnail.png`,
|
||||
type : 'website'
|
||||
};
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
return next();
|
||||
@@ -118,6 +142,14 @@ app.get('/changelog', async (req, res, next)=>{
|
||||
title : 'Changelog',
|
||||
text : changelogText,
|
||||
renderer : 'V3'
|
||||
},
|
||||
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : 'Changelog',
|
||||
description : 'Development changelog.',
|
||||
thumbnail : null,
|
||||
type : 'website'
|
||||
};
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
return next();
|
||||
@@ -129,7 +161,16 @@ app.get('/faq', async (req, res, next)=>{
|
||||
title : 'FAQ',
|
||||
text : faqText,
|
||||
renderer : 'V3'
|
||||
},
|
||||
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : 'FAQ',
|
||||
description : 'Frequently Asked Questions',
|
||||
thumbnail : `${config.get('publicUrl')}/thumbnail.png`,
|
||||
type : 'website'
|
||||
};
|
||||
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
return next();
|
||||
});
|
||||
@@ -167,6 +208,14 @@ app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{
|
||||
app.get('/user/:username', async (req, res, next)=>{
|
||||
const ownAccount = req.account && (req.account.username == req.params.username);
|
||||
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : `${req.params.username}'s Collection`,
|
||||
description : 'View my collection of homebrew on the Homebrewery.',
|
||||
image : null,
|
||||
type : 'website' // or 'profile'?
|
||||
};
|
||||
|
||||
const fields = [
|
||||
'googleId',
|
||||
'title',
|
||||
@@ -224,6 +273,14 @@ app.get('/user/:username', async (req, res, next)=>{
|
||||
//Edit Page
|
||||
app.get('/edit/:id', asyncHandler(getBrew('edit')), (req, res, next)=>{
|
||||
req.brew = req.brew.toObject ? req.brew.toObject() : req.brew;
|
||||
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : req.brew.title || 'Untitled Brew',
|
||||
description : req.brew.description || 'No description.',
|
||||
image : req.brew.thumbnail || null,
|
||||
type : 'article'
|
||||
};
|
||||
sanitizeBrew(req.brew, 'edit');
|
||||
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.
|
||||
@@ -235,6 +292,13 @@ app.get('/new/:id', asyncHandler(getBrew('share')), (req, res, next)=>{
|
||||
sanitizeBrew(req.brew, 'share');
|
||||
splitTextStyleAndMetadata(req.brew);
|
||||
req.brew.title = `CLONE - ${req.brew.title}`;
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : 'New',
|
||||
description : 'Start crafting your homebrew on the Homebrewery!',
|
||||
image : null,
|
||||
type : 'website'
|
||||
};
|
||||
return next();
|
||||
});
|
||||
|
||||
@@ -242,6 +306,14 @@ app.get('/new/:id', asyncHandler(getBrew('share')), (req, res, next)=>{
|
||||
app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
|
||||
const { brew } = req;
|
||||
|
||||
req.ogMeta = {
|
||||
siteName : 'The Homebrewery - Make your Homebrew content look legit!',
|
||||
title : req.brew.title || 'Untitled Brew',
|
||||
description : req.brew.description || 'No description.',
|
||||
image : req.brew.thumbnail || null,
|
||||
type : 'article'
|
||||
};
|
||||
|
||||
if(req.params.id.length > 12 && !brew._id) {
|
||||
const googleId = req.params.id.slice(0, -12);
|
||||
const shareId = req.params.id.slice(-12);
|
||||
@@ -262,6 +334,54 @@ app.get('/print/:id', asyncHandler(getBrew('share')), (req, res, 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);
|
||||
} 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;
|
||||
return next();
|
||||
}));
|
||||
|
||||
|
||||
const nodeEnv = config.get('node_env');
|
||||
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
|
||||
// Local only
|
||||
@@ -276,8 +396,6 @@ if(isLocalEnvironment){
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Render the page
|
||||
const templateFn = require('./../client/template.js');
|
||||
app.use(asyncHandler(async (req, res, next)=>{
|
||||
@@ -296,7 +414,8 @@ app.use(asyncHandler(async (req, res, next)=>{
|
||||
account : req.account,
|
||||
enable_v3 : config.get('enable_v3'),
|
||||
enable_themes : config.get('enable_themes'),
|
||||
config : configuration
|
||||
config : configuration,
|
||||
ogMeta : req.ogMeta
|
||||
};
|
||||
const title = req.brew ? req.brew.title : '';
|
||||
const page = await templateFn('homebrew', title, props)
|
||||
|
||||
@@ -32,7 +32,7 @@ const mustacheSpans = {
|
||||
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||
tokenizer(src, tokens) {
|
||||
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);
|
||||
if(match) {
|
||||
//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
|
||||
tokenizer(src, tokens) {
|
||||
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);
|
||||
if(match) {
|
||||
//Find closing delimiter
|
||||
@@ -130,7 +130,7 @@ const mustacheInjectInline = {
|
||||
level : 'inline',
|
||||
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||
tokenizer(src, tokens) {
|
||||
const inlineRegex = /^ *{((?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*)}/g;
|
||||
const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g;
|
||||
const match = inlineRegex.exec(src);
|
||||
if(match) {
|
||||
const lastToken = tokens[tokens.length - 1];
|
||||
@@ -165,7 +165,7 @@ const mustacheInjectBlock = {
|
||||
level : 'block',
|
||||
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
|
||||
tokenizer(src, tokens) {
|
||||
const inlineRegex = /^ *{((?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*)}/ym;
|
||||
const inlineRegex = /^ *{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/ym;
|
||||
const match = inlineRegex.exec(src);
|
||||
if(match) {
|
||||
const lastToken = tokens[tokens.length - 1];
|
||||
|
||||
@@ -9,8 +9,8 @@ describe('Tests for static pages', ()=>{
|
||||
return app.get('/').expect(200);
|
||||
});
|
||||
|
||||
it('Home page v3 works', ()=>{
|
||||
return app.get('/v3_preview').expect(200);
|
||||
it('Home page legacy works', ()=>{
|
||||
return app.get('/legacy').expect(200);
|
||||
});
|
||||
|
||||
it('Changelog page works', ()=>{
|
||||
|
||||
@@ -82,7 +82,7 @@ body {
|
||||
// * BASE
|
||||
// *****************************/
|
||||
|
||||
:where(.page){
|
||||
.page{
|
||||
p{
|
||||
overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
|
||||
display : block;
|
||||
@@ -214,6 +214,7 @@ body {
|
||||
table{
|
||||
.useSansSerif();
|
||||
width : 100%;
|
||||
line-height : 16px;
|
||||
& + * {
|
||||
margin-top : 0.325cm;
|
||||
}
|
||||
@@ -224,7 +225,7 @@ body {
|
||||
vertical-align : bottom;
|
||||
//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
|
||||
//line-height : 16px; // PDF to render at same height until Chrome 108
|
||||
}
|
||||
}
|
||||
tbody{
|
||||
@@ -232,7 +233,7 @@ body {
|
||||
td{
|
||||
//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
|
||||
//line-height : 16px; // PDF to render at same height until Chrome 108
|
||||
}
|
||||
&:nth-child(odd){
|
||||
background-color : var(--HB_Color_Accent);
|
||||
@@ -240,9 +241,6 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
div table:has(~table) { // Divs with side-by-side tables add an extra line
|
||||
margin-bottom: -0.325cm; // of vertical space at bottom. This works around it.
|
||||
}
|
||||
//*****************************
|
||||
// * NOTE
|
||||
// *****************************/
|
||||
@@ -598,7 +596,7 @@ body {
|
||||
//*****************************
|
||||
// * SPELL LIST
|
||||
// *****************************/
|
||||
:where(.page) .spellList{
|
||||
.page .spellList{
|
||||
.useSansSerif();
|
||||
column-count : 2;
|
||||
ul+h5{
|
||||
@@ -625,7 +623,7 @@ body {
|
||||
//*****************************
|
||||
// * CLASS TABLE
|
||||
// *****************************/
|
||||
:where(.page) .classTable{
|
||||
.page .classTable{
|
||||
th[colspan]:not([rowspan]) {
|
||||
white-space : nowrap;
|
||||
}
|
||||
@@ -682,7 +680,7 @@ body {
|
||||
//*****************************
|
||||
// * TABLE OF CONTENTS
|
||||
// *****************************/
|
||||
:where(.page) {
|
||||
.page {
|
||||
&:has(.toc):after {
|
||||
display: none;
|
||||
}
|
||||
@@ -759,7 +757,7 @@ body {
|
||||
//*****************************
|
||||
// * DEFINITION LISTS
|
||||
// *****************************/
|
||||
:where(.page) {
|
||||
.page {
|
||||
dl {
|
||||
line-height : 1.25em;
|
||||
padding-left : 1em;
|
||||
@@ -789,10 +787,10 @@ body {
|
||||
//*****************************
|
||||
// * WIDE
|
||||
// *****************************/
|
||||
:where(.page) .wide{
|
||||
.page .wide{
|
||||
margin-bottom : 0.325cm;
|
||||
}
|
||||
|
||||
:where(.page) h1 + *{
|
||||
.page h1 + *{
|
||||
margin-top : 0;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ body {
|
||||
//*****************************
|
||||
// * BASE
|
||||
// *****************************/
|
||||
:where(.page){
|
||||
.page{
|
||||
p{
|
||||
overflow-wrap : break-word;
|
||||
display : block;
|
||||
@@ -77,13 +77,7 @@ body {
|
||||
img{
|
||||
z-index : -1;
|
||||
}
|
||||
:not(:where(.wide,.columnSplit,.blank,hr,h1)) + :where(h2,h3,h4,h5,h6,table,dl,.block) {
|
||||
margin-top : 0.325cm; //NOTE: MAKE ALL MARGINS TOP-ONLY FOR BEST RESULTS WITH COLUMN BREAKS. USE * + * STYLE SELECTORS
|
||||
}
|
||||
|
||||
:is(h1,h3,h3,h4,h5,h6) + * {
|
||||
margin-top : 0;
|
||||
}
|
||||
//*****************************
|
||||
// * HEADERS
|
||||
// *****************************/
|
||||
@@ -116,6 +110,9 @@ body {
|
||||
font-weight : bold;
|
||||
}
|
||||
}
|
||||
div:not(.columnWrapper) > table + table { // Side-by-side tables should not
|
||||
margin-top : 0; // have vertical spacing.
|
||||
}
|
||||
|
||||
/* Watermark */
|
||||
.watermark {
|
||||
@@ -218,7 +215,7 @@ body {
|
||||
//*****************************
|
||||
// * MUSTACHE DIVS/SPANS
|
||||
// *****************************/
|
||||
:where(.page) {
|
||||
.page {
|
||||
.block {
|
||||
break-inside : avoid;
|
||||
display : inline-block;
|
||||
@@ -233,7 +230,7 @@ body {
|
||||
//*****************************
|
||||
// * DEFINITION LISTS
|
||||
// *****************************/
|
||||
:where(.page) {
|
||||
.page {
|
||||
dl {
|
||||
padding-left : 1em;
|
||||
white-space : pre-line;
|
||||
@@ -253,17 +250,20 @@ body {
|
||||
//*****************************
|
||||
// * BLANK LINE
|
||||
// *****************************/
|
||||
:where(.page) {
|
||||
.page {
|
||||
.blank {
|
||||
height : 1em;
|
||||
margin-top : 0;
|
||||
& + * {
|
||||
margin-top : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************
|
||||
// * WIDE
|
||||
// *****************************/
|
||||
:where(.page) {
|
||||
.page {
|
||||
.wide{
|
||||
column-span : all;
|
||||
display : block;
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
//*****************************
|
||||
// * BASE
|
||||
// *****************************/
|
||||
:where(.page){
|
||||
.page{
|
||||
color : var(--HB_Color_Text);
|
||||
font-family : ReenieBeanie;
|
||||
font-size : 0.53cm;
|
||||
@@ -554,6 +554,6 @@
|
||||
//*****************************
|
||||
// * WIDE
|
||||
// *****************************/
|
||||
:where(.page) .wide {
|
||||
.page .wide {
|
||||
margin-bottom : 0.45cm;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user