0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-26 09:33:02 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Trevor Buckner
90ceb52ffc Remove unused import 2025-08-11 21:30:51 -04:00
Trevor Buckner
6f9caf0590 Refactor to use BaseEditPage for shared layout
Some Nav buttons missing on the different pages should now appear in all three pages. Unique buttons are still only on those pages for now (/share nav button only appears on the /edit page, etc.)
2025-08-10 22:16:07 -04:00
16 changed files with 715 additions and 671 deletions

View File

@@ -39,8 +39,8 @@ const BrewPage = (props)=>{
index : 0,
...props
};
const pageRef = useRef(null);
const cleanText = safeHTML(props.contents);
const pageRef = useRef(null);
const cleanText = safeHTML(`${props.contents}\n<div class="columnSplit"></div>\n`);
useEffect(()=>{
if(!pageRef.current) return;

View File

@@ -1,138 +1,157 @@
require('./error-navitem.less');
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const createClass = require('create-react-class');
const ErrorNavItem = ({error = '', clearError})=>{
const response = error.response;
const errorCode = error.code
const status = response?.status;
const HBErrorCode = response?.body?.HBErrorCode;
const message = response?.body?.message;
const ErrorNavItem = createClass({
getDefaultProps : function() {
return {
error : '',
parent : null
};
},
render : function() {
const clearError = ()=>{
const state = {
error : null
};
if(this.props.parent.state.isSaving) {
state.isSaving = false;
}
this.props.parent.setState(state);
};
let errMsg = '';
try {
errMsg += `${error.toString()}\n\n`;
errMsg += `\`\`\`\n${error.stack}\n`;
errMsg += `${JSON.stringify(response?.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch (e){}
const error = this.props.error;
const response = error.response;
const status = response?.status;
const errorCode = error.code
const HBErrorCode = response?.body?.HBErrorCode;
const message = response?.body?.message;
let errMsg = '';
try {
errMsg += `${error.toString()}\n\n`;
errMsg += `\`\`\`\n${error.stack}\n`;
errMsg += `${JSON.stringify(response?.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch (e){}
if(status === 409) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Conflict: please refresh to get latest changes'}
</div>
</Nav.item>;
}
if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'}
</div>
</Nav.item>;
}
if(HBErrorCode === '04') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
You are no longer signed in as an author of
this brew! Were you signed out from a different
window? Visit our log in page, then try again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
if(status === 409) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Conflict: please refresh to get latest changes'}
</div>
</div>
</Nav.item>;
}
</Nav.item>;
}
if(response?.body?.errors?.[0].reason == 'storageQuotaExceeded') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Can't save because your Google Drive seems to be full!
</div>
</Nav.item>;
}
if(response?.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'}
</div>
</div>
</Nav.item>;
}
</Nav.item>;
}
if(HBErrorCode === '04') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
You are no longer signed in as an author of
this brew! Were you signed out from a different
window? Visit our log in page, then try again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
if(response?.body?.errors?.[0].reason == 'storageQuotaExceeded') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Can't save because your Google Drive seems to be full!
</div>
</Nav.item>;
}
if(response?.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
if(HBErrorCode === '09') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like there was a problem retreiving
the theme, or a theme that it inherits,
for this brew. Verify that brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> still exists!
</div>
</Nav.item>;
}
if(HBErrorCode === '10') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like the brew you have selected
as a theme is not tagged for use as a
theme. Verify that
brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> has the <span className='lowercase'>meta:theme</span> tag!
</div>
</Nav.item>;
}
if(errorCode === 'ECONNABORTED') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
The request to the server was interrupted or timed out.
This can happen due to a network issue, or if
trying to save a particularly large brew.
Please check your internet connection and try again.
</div>
</Nav.item>;
}
if(HBErrorCode === '09') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like there was a problem retreiving
the theme, or a theme that it inherits,
for this brew. Verify that brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> still exists!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer' href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
}
if(HBErrorCode === '10') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like the brew you have selected
as a theme is not tagged for use as a
theme. Verify that
brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> has the <span className='lowercase'>meta:theme</span> tag!
</div>
</Nav.item>;
}
if(errorCode === 'ECONNABORTED') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
The request to the server was interrupted or timed out.
This can happen due to a network issue, or if
trying to save a particularly large brew.
Please check your internet connection and try again.
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer' href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
};
});
module.exports = ErrorNavItem;

View File

@@ -5,45 +5,33 @@ const { splitTextStyleAndMetadata } = require('../../../shared/helpers.js'); //
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta';
const METAKEY = 'homebrewery-new-meta';
const NewBrew = ()=>{
const handleFileChange = (e)=>{
const file = e.target.files[0];
if(!file) return;
const currentNew = localStorage.getItem(BREWKEY);
if(currentNew && !confirm(
`You have some text in the new brew space, if you load a file that text will be lost, are you sure you want to load the file?`
)) return;
const reader = new FileReader();
reader.onload = (e)=>{
const fileContent = e.target.result;
const newBrew = { text: fileContent, style: '' };
if(fileContent.startsWith('```metadata')) {
splitTextStyleAndMetadata(newBrew);
localStorage.setItem(BREWKEY, newBrew.text);
localStorage.setItem(STYLEKEY, newBrew.style);
localStorage.setItem(METAKEY, JSON.stringify(
_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])
));
window.location.href = '/new';
return;
}
const type = file.name.split('.').pop().toLowerCase();
alert(`This file is invalid: ${!type ? "Missing file extension" :`.${type} files are not supported`}. Only .txt files exported from the Homebrewery are allowed.`);
console.log(file);
};
reader.readAsText(file);
if(file) {
const reader = new FileReader();
reader.onload = (e)=>{
const fileContent = e.target.result;
const newBrew = {
text : fileContent,
style : ''
};
if(fileContent.startsWith('```metadata')) {
splitTextStyleAndMetadata(newBrew); // Modify newBrew directly
localStorage.setItem(BREWKEY, newBrew.text);
localStorage.setItem(STYLEKEY, newBrew.style);
localStorage.setItem(METAKEY, JSON.stringify(_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])));
window.location.href = '/new';
} else {
alert('This file is invalid, please, enter a valid file');
}
};
reader.readAsText(file);
}
};
return (
<Nav.dropdown>
<Nav.item

View File

@@ -0,0 +1,37 @@
require('./editPage.less');
const React = require('react');
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 PrintNavItem = require('../../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../../navbar/error-navitem.jsx');
const AccountNavItem = require('../../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../../navbar/recent.navitem.jsx').both;
const VaultNavItem = require('../../../navbar/vault.navitem.jsx');
const BaseEditPage = (props)=>{
return (
<div className={`sitePage ${props.className || ''}`}>
<Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{props.brew.title}</Nav.item>
</Nav.section>
<Nav.section>
{props.navButtons}
<PrintNavItem />
<NewBrewItem />
<HelpNavItem />
<VaultNavItem />
<RecentNavItem brew={props.brew} storageKey={props.recentStorageKey} />
<AccountNavItem />
</Nav.section>
</Navbar>
{props.children}
</div>
);
};
module.exports = BaseEditPage;

View File

@@ -29,7 +29,6 @@
&::before {
margin-right : 5px;
font-family : 'Font Awesome 6 Free';
font-weight : 900;
content : '\f00c';
}
}

View File

@@ -11,16 +11,10 @@ import request from '../../utils/request-middleware.js';
const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
const BaseEditPage = require('../basePages/editPage/editPage.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
@@ -97,7 +91,7 @@ const EditPage = createClass({
htmlErrors : Markdown.validate(prevState.brew.text)
}));
fetchThemeBundle((err)=>{this.setState({ error: err })}, (theme)=>{this.setState({ themeBundle: theme })}, this.props.brew.renderer, this.props.brew.theme);
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
document.addEventListener('keydown', this.handleControlKeys);
},
@@ -173,7 +167,7 @@ const EditPage = createClass({
handleMetaChange : function(metadata, field=undefined){
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
fetchThemeBundle((err)=>{this.setState({ error: err })}, (theme)=>{this.setState({ themeBundle: theme })}, metadata.renderer, metadata.theme);
fetchThemeBundle(this, metadata.renderer, metadata.theme);
this.setState((prevState)=>({
brew : {
@@ -438,32 +432,19 @@ const EditPage = createClass({
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`;
},
clearError : function(){
setState({
error : null,
isSaving : false
})
},
renderNavbar : function(){
const shareLink = this.processShareId();
return <Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
</Nav.section>
return <>
<Nav.section>
{this.renderGoogleDriveIcon()}
{this.state.error ?
<ErrorNavItem error={this.state.error} clearError={this.clearError}></ErrorNavItem> :
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
<Nav.dropdown className='save-menu'>
{this.renderSaveButton()}
{this.renderAutoSaveButton()}
</Nav.dropdown>
}
<NewBrew />
<HelpNavItem/>
<Nav.dropdown>
<Nav.item color='teal' icon='fas fa-share-alt'>
share
@@ -478,20 +459,19 @@ const EditPage = createClass({
post to reddit
</Nav.item>
</Nav.dropdown>
<PrintNavItem />
<VaultNavItem />
<RecentNavItem brew={this.state.brew} storageKey='edit' />
<Account />
</Nav.section>
</Navbar>;
</>;
},
render : function(){
return <div className='editPage sitePage'>
return <BaseEditPage
className="editPage"
errorState={this.state.error}
parent={this}
brew={this.state.brew}
navButtons={this.renderNavbar()}
recentStorageKey='edit'>
<Meta name='robots' content='noindex, nofollow' />
{this.renderNavbar()}
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} reviewRequested={this.props.brew.lock.reviewRequested} />}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
@@ -529,7 +509,7 @@ const EditPage = createClass({
/>
</SplitPane>
</div>
</div>;
</BaseEditPage>;
}
});

View File

@@ -1,142 +1,155 @@
import './homePage.less';
require('./homePage.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
import request from '../../utils/request-middleware.js';
const { Meta } = require('vitreum/headtags');
import React from 'react';
import { useEffect, useState, useRef } from 'react';
import request from '../../utils/request-middleware.js';
import { Meta } from 'vitreum/headtags';
const Nav = require('naturalcrit/nav/nav.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const { fetchThemeBundle } = require('../../../../shared/helpers.js');
import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import { fetchThemeBundle } from '../../../../shared/helpers.js';
const BaseEditPage = require('../basePages/editPage/editPage.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
const HomePage = createClass({
displayName : 'HomePage',
getDefaultProps : function() {
return {
brew : DEFAULT_BREW
};
},
getInitialState : function() {
return {
brew : this.props.brew,
isSaving : false,
welcomeText : this.props.brew.text,
error : undefined,
currentEditorViewPageNum : 1,
currentEditorCursorPageNum : 1,
currentBrewRendererPageNum : 1,
themeBundle : {}
};
},
const HomePage =(props)=>{
props = {
brew : DEFAULT_BREW,
ver : '0.0.0',
...props
};
editor : React.createRef(null),
const [brew , setBrew] = useState(props.brew);
const [welcomeText , setWelcomeText] = useState(props.brew.text);
const [error , setError] = useState(undefined);
const [currentEditorViewPageNum , setCurrentEditorViewPageNum] = useState(1);
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [themeBundle , setThemeBundle] = useState({});
const [isSaving , setIsSaving] = useState(false);
componentDidMount : function() {
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
},
const editorRef = useRef(null);
save : function(){
this.setState({
isSaving : true
});
useEffect(()=>{
fetchThemeBundle(setError, setThemeBundle, brew.renderer, brew.theme);
}, []);
const save = ()=>{
request.post('/api')
.send(brew)
.send(this.state.brew)
.end((err, res)=>{
if(err) {
setError(err);
this.setState({ error: err });
return;
}
const saved = res.body;
window.location = `/edit/${saved.editId}`;
const brew = res.body;
window.location = `/edit/${brew.editId}`;
})
.catch((err)=>{
this.setState({ isSaving: false, error: err });
});
};
},
handleSplitMove : function(){
this.editor.current.update();
},
const handleSplitMove = ()=>{
editorRef.current.update();
};
handleEditorViewPageChange : function(pageNumber){
this.setState({ currentEditorViewPageNum: pageNumber });
},
const handleEditorViewPageChange = (pageNumber)=>{
setCurrentEditorViewPageNum(pageNumber);
};
const handleEditorCursorPageChange = (pageNumber)=>{
setCurrentEditorCursorPageNum(pageNumber);
};
const handleBrewRendererPageChange = (pageNumber)=>{
setCurrentBrewRendererPageNum(pageNumber);
};
handleEditorCursorPageChange : function(pageNumber){
this.setState({ currentEditorCursorPageNum: pageNumber });
},
const handleTextChange = (text)=>{
setBrew((prevBrew) => ({ ...prevBrew, text }));
};
handleBrewRendererPageChange : function(pageNumber){
this.setState({ currentBrewRendererPageNum: pageNumber });
},
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
handleTextChange : function(text){
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text },
}));
},
const renderNavbar = ()=>{
return <Navbar ver={props.ver}>
renderSaveButton : function(){
if(this.state.isSaving){
return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
save...
</Nav.item>;
} else {
return <Nav.item icon='fas fa-save' className='save' onClick={this.save}>
save
</Nav.item>;
}
},
renderNavbar : function(){
return <>
<Nav.section>
{error ?
<ErrorNavItem error={error} clearError={clearError}></ErrorNavItem> :
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
null
}
<NewBrewItem />
<HelpNavItem />
<VaultNavItem />
<RecentNavItem />
<AccountNavItem />
</Nav.section>
</Navbar>;
};
</>;
},
return (
<div className='homePage sitePage'>
render : function(){
return <BaseEditPage
className="homePage"
errorState={this.state.error}
parent={this}
brew={this.state.brew}
navButtons={this.renderNavbar()}>
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
{renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={handleSplitMove}>
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={editorRef}
brew={brew}
onTextChange={handleTextChange}
renderer={brew.renderer}
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer}
showEditButtons={false}
themeBundle={themeBundle}
onCursorPageChange={handleEditorCursorPageChange}
onViewPageChange={handleEditorViewPageChange}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
themeBundle={this.state.themeBundle}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={brew.text}
style={brew.style}
renderer={brew.renderer}
onPageChange={handleBrewRendererPageChange}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
themeBundle={themeBundle}
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={this.state.themeBundle}
/>
</SplitPane>
</div>
<div className={`floatingSaveButton${welcomeText !== brew.text ? ' show' : ''}`} onClick={save}>
<div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.save}>
Save current <i className='fas fa-save' />
</div>
<a href='/new' className='floatingNewButton'>
Create your own <i className='fas fa-magic' />
</a>
</div>
)
};
</BaseEditPage>
}
});
module.exports = HomePage;

View File

@@ -1,251 +1,266 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
import './newPage.less';
require('./newPage.less');
const React = require('react');
const createClass = require('create-react-class');
import request from '../../utils/request-middleware.js';
import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import Markdown from 'naturalcrit/markdown.js';
import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
const Nav = require('naturalcrit/nav/nav.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
const BaseEditPage = require('../basePages/editPage/editPage.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta';
const SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`;
let SAVEKEY;
const NewPage = (props)=>{
props = {
brew : DEFAULT_BREW,
...props
};
const [currentBrew , setCurrentBrew ] = useState(props.brew);
const [isSaving , setIsSaving ] = useState(false);
const [saveGoogle , setSaveGoogle ] = useState(global.account?.googleId ? true : false);
const [error , setError ] = useState(null);
const [HTMLErrors , setHTMLErrors ] = useState(Markdown.validate(props.brew.text));
const [currentEditorViewPageNum , setCurrentEditorViewPageNum ] = useState(1);
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [themeBundle , setThemeBundle ] = useState({});
const editorRef = useRef(null);
useEffect(()=>{
document.addEventListener('keydown', handleControlKeys);
loadBrew();
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
const NewPage = createClass({
displayName : 'NewPage',
getDefaultProps : function() {
return {
brew : DEFAULT_BREW
};
}, []);
},
const loadBrew = ()=>{
const brew = { ...currentBrew };
if(!brew.shareId && typeof window !== 'undefined') { //Load from localStorage if in client browser
getInitialState : function() {
const brew = this.props.brew;
return {
brew : brew,
isSaving : false,
saveGoogle : (global.account && global.account.googleId ? true : false),
error : null,
htmlErrors : Markdown.validate(brew.text),
currentEditorViewPageNum : 1,
currentEditorCursorPageNum : 1,
currentBrewRendererPageNum : 1,
themeBundle : {}
};
},
editor : React.createRef(null),
componentDidMount : function() {
document.addEventListener('keydown', this.handleControlKeys);
const brew = this.state.brew;
if(!this.props.brew.shareId && typeof window !== 'undefined') { //Load from localStorage if in client browser
const brewStorage = localStorage.getItem(BREWKEY);
const styleStorage = localStorage.getItem(STYLEKEY);
const metaStorage = JSON.parse(localStorage.getItem(METAKEY));
const metaStorage = JSON.parse(localStorage.getItem(METAKEY));
brew.text = brewStorage ?? brew.text;
brew.style = styleStorage ?? brew.style;
brew.text = brewStorage ?? brew.text;
brew.style = styleStorage ?? brew.style;
// brew.title = metaStorage?.title || this.state.brew.title;
// brew.description = metaStorage?.description || this.state.brew.description;
brew.renderer = metaStorage?.renderer ?? brew.renderer;
brew.theme = metaStorage?.theme ?? brew.theme;
brew.lang = metaStorage?.lang ?? brew.lang;
}
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`;
const saveStorage = localStorage.getItem(SAVEKEY) || 'HOMEBREWERY';
setCurrentBrew(brew);
setSaveGoogle(saveStorage == 'GOOGLE-DRIVE' && saveGoogle);
this.setState({
brew : brew,
saveGoogle : (saveStorage == 'GOOGLE-DRIVE' && this.state.saveGoogle)
});
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
localStorage.setItem(BREWKEY, brew.text);
if(brew.style)
localStorage.setItem(STYLEKEY, brew.style);
localStorage.setItem(METAKEY, JSON.stringify({ renderer: brew.renderer, theme: brew.theme, lang: brew.lang }));
if(window.location.pathname !== '/new')
localStorage.setItem(METAKEY, JSON.stringify({ 'renderer': brew.renderer, 'theme': brew.theme, 'lang': brew.lang }));
if(window.location.pathname != '/new') {
window.history.replaceState({}, window.location.title, '/new/');
};
}
},
componentWillUnmount : function() {
document.removeEventListener('keydown', this.handleControlKeys);
},
const handleControlKeys = (e)=>{
handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return;
const S_KEY = 83;
const P_KEY = 80;
if(e.keyCode === S_KEY) save();
if(e.keyCode === P_KEY) printCurrentBrew();
if(e.keyCode === S_KEY || e.keyCode === P_KEY) {
e.preventDefault();
if(e.keyCode == S_KEY) this.save();
if(e.keyCode == P_KEY) printCurrentBrew();
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
e.stopPropagation();
e.preventDefault();
}
};
},
const handleSplitMove = ()=>{
editorRef.current.update();
};
handleSplitMove : function(){
this.editor.current.update();
},
const handleEditorViewPageChange = (pageNumber)=>{
setCurrentEditorViewPageNum(pageNumber);
};
handleEditorViewPageChange : function(pageNumber){
this.setState({ currentEditorViewPageNum: pageNumber });
},
const handleEditorCursorPageChange = (pageNumber)=>{
setCurrentEditorCursorPageNum(pageNumber);
};
handleEditorCursorPageChange : function(pageNumber){
this.setState({ currentEditorCursorPageNum: pageNumber });
},
const handleBrewRendererPageChange = (pageNumber)=>{
setCurrentBrewRendererPageNum(pageNumber);
};
handleBrewRendererPageChange : function(pageNumber){
this.setState({ currentBrewRendererPageNum: pageNumber });
},
const handleTextChange = (text)=>{
//If there are HTML errors, run the validator on every change to give quick feedback
if(HTMLErrors.length)
HTMLErrors = Markdown.validate(text);
handleTextChange : function(text){
//If there are errors, run the validator on every change to give quick feedback
let htmlErrors = this.state.htmlErrors;
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
setHTMLErrors(HTMLErrors);
setCurrentBrew((prevBrew)=>({ ...prevBrew, text }));
localStorage.setItem(BREWKEY, text);
};
const handleStyleChange = (style)=>{
setCurrentBrew((prevBrew)=>({ ...prevBrew, style }));
localStorage.setItem(STYLEKEY, style);
};
const handleSnipChange = (snippet)=>{
//If there are HTML errors, run the validator on every change to give quick feedback
if(HTMLErrors.length)
HTMLErrors = Markdown.validate(snippet);
setHTMLErrors(HTMLErrors);
setCurrentBrew((prevBrew)=>({ ...prevBrew, snippets: snippet }));
};
const handleMetaChange = (metadata, field = undefined)=>{
if(field === 'theme' || field === 'renderer')
fetchThemeBundle(setError, setThemeBundle, metadata.renderer, metadata.theme);
setCurrentBrew((prev)=>({ ...prev, ...metadata }));
localStorage.setItem(METAKEY, JSON.stringify({
renderer : metadata.renderer,
theme : metadata.theme,
lang : metadata.lang
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text },
htmlErrors : htmlErrors,
}));
};
localStorage.setItem(BREWKEY, text);
},
const save = async ()=>{
setIsSaving(true);
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : { ...prevState.brew, style: style },
}));
localStorage.setItem(STYLEKEY, style);
},
const updatedBrew = { ...currentBrew };
splitTextStyleAndMetadata(updatedBrew);
handleSnipChange : function(snippet){
//If there are errors, run the validator on every change to give quick feedback
let htmlErrors = this.state.htmlErrors;
if(htmlErrors.length) htmlErrors = Markdown.validate(snippet);
const pageRegex = updatedBrew.renderer === 'legacy' ? /\\page/g : /^\\page$/gm;
updatedBrew.pageCount = (updatedBrew.text.match(pageRegex) || []).length + 1;
this.setState((prevState)=>({
brew : { ...prevState.brew, snippets: snippet },
htmlErrors : htmlErrors,
}), ()=>{if(this.state.autoSave) this.trySave();});
},
handleMetaChange : function(metadata, field=undefined){
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
fetchThemeBundle(this, metadata.renderer, metadata.theme);
this.setState((prevState)=>({
brew : { ...prevState.brew, ...metadata },
}), ()=>{
localStorage.setItem(METAKEY, JSON.stringify({
// 'title' : this.state.brew.title,
// 'description' : this.state.brew.description,
'renderer' : this.state.brew.renderer,
'theme' : this.state.brew.theme,
'lang' : this.state.brew.lang
}));
});
;
},
save : async function(){
this.setState({
isSaving : true
});
let brew = this.state.brew;
// Split out CSS to Style if CSS codefence exists
if(brew.text.startsWith('```css') && brew.text.indexOf('```\n\n') > 0) {
const index = brew.text.indexOf('```\n\n');
brew.style = `${brew.style ? `${brew.style}\n` : ''}${brew.text.slice(7, index - 1)}`;
brew.text = brew.text.slice(index + 5);
}
brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
const res = await request
.post(`/api${saveGoogle ? '?saveToGoogle=true' : ''}`)
.send(updatedBrew)
.post(`/api${this.state.saveGoogle ? '?saveToGoogle=true' : ''}`)
.send(brew)
.catch((err)=>{
setIsSaving(false);
setError(err);
this.setState({ isSaving: false, error: err });
});
setIsSaving(false);
if(!res) return;
const savedBrew = res.body;
brew = res.body;
localStorage.removeItem(BREWKEY);
localStorage.removeItem(STYLEKEY);
localStorage.removeItem(METAKEY);
window.location = `/edit/${savedBrew.editId}`;
};
window.location = `/edit/${brew.editId}`;
},
const renderSaveButton = ()=>{
if(isSaving){
renderSaveButton : function(){
if(this.state.isSaving){
return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
save...
</Nav.item>;
} else {
return <Nav.item icon='fas fa-save' className='save' onClick={save}>
return <Nav.item icon='fas fa-save' className='save' onClick={this.save}>
save
</Nav.item>;
}
};
},
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
const renderNavbar = ()=>(
<Navbar>
renderNavbar : function(){
return <>
<Nav.section>
<Nav.item className='brewTitle'>{currentBrew.title}</Nav.item>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
this.renderSaveButton()
}
</Nav.section>
</>;
},
<Nav.section>
{error
? <ErrorNavItem error={error} clearError={clearError} />
: renderSaveButton()}
<PrintNavItem />
<HelpNavItem />
<RecentNavItem />
<AccountNavItem />
</Nav.section>
</Navbar>
);
return (
<div className='newPage sitePage'>
{renderNavbar()}
render : function(){
return <BaseEditPage
className="newPage"
errorState={this.state.error}
parent={this}
brew={this.state.brew}
navButtons={this.renderNavbar()}>
<div className='content'>
<SplitPane onDragFinish={handleSplitMove}>
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={editorRef}
brew={currentBrew}
onTextChange={handleTextChange}
onStyleChange={handleStyleChange}
onMetaChange={handleMetaChange}
onSnipChange={handleSnipChange}
renderer={currentBrew.renderer}
userThemes={props.userThemes}
themeBundle={themeBundle}
onCursorPageChange={handleEditorCursorPageChange}
onViewPageChange={handleEditorViewPageChange}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
onSnipChange={this.handleSnipChange}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
themeBundle={this.state.themeBundle}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={currentBrew.text}
style={currentBrew.style}
renderer={currentBrew.renderer}
theme={currentBrew.theme}
themeBundle={themeBundle}
errors={HTMLErrors}
lang={currentBrew.lang}
onPageChange={handleBrewRendererPageChange}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
</div>
</div>
);
};
</BaseEditPage>;
}
});
module.exports = NewPage;

View File

@@ -17,11 +17,15 @@ const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpe
const SharePage = (props)=>{
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
const [themeBundle, setThemeBundle] = useState({});
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [state, setState] = useState({
themeBundle : {},
currentBrewRendererPageNum : 1,
});
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
setCurrentBrewRendererPageNum(pageNumber);
setState((prevState)=>({
currentBrewRendererPageNum : pageNumber,
...prevState }));
}, []);
const handleControlKeys = (e)=>{
@@ -36,7 +40,11 @@ const SharePage = (props)=>{
useEffect(()=>{
document.addEventListener('keydown', handleControlKeys);
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
fetchThemeBundle(
{ setState },
brew.renderer,
brew.theme
);
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
@@ -106,9 +114,9 @@ const SharePage = (props)=>{
lang={brew.lang}
renderer={brew.renderer}
theme={brew.theme}
themeBundle={themeBundle}
themeBundle={state.themeBundle}
onPageChange={handleBrewRendererPageChange}
currentBrewRendererPageNum={currentBrewRendererPageNum}
currentBrewRendererPageNum={state.currentBrewRendererPageNum}
allowPrint={true}
/>
</div>

View File

@@ -39,14 +39,10 @@ const UserPage = (props)=>{
}] : [])
];
const clearError = ()=>{
setError(null);
};
const navItems = (
<Navbar>
<Nav.section>
{error && (<ErrorNavItem error={error} clearError={clearError}></ErrorNavItem>)}
{error && (<ErrorNavItem error={error} parent={null}></ErrorNavItem>)}
<NewBrew />
<HelpNavItem />
<VaultNavitem />

View File

@@ -1,74 +0,0 @@
import requestMiddleware from './request-middleware';
jest.mock('superagent');
import request from 'superagent';
describe('request-middleware', ()=>{
let version;
let setFn;
let testFn;
beforeEach(()=>{
jest.resetAllMocks();
version = global.version;
global.version = '999';
setFn = jest.fn();
testFn = jest.fn(()=>{ return { set: setFn }; });
});
afterEach(()=>{
global.version = version;
});
it('should add header to get', ()=>{
// Ensure tests functions have been reset
expect(testFn).not.toHaveBeenCalled();
expect(setFn).not.toHaveBeenCalled();
request.get = testFn;
requestMiddleware.get('path');
expect(testFn).toHaveBeenCalledWith('path');
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
});
it('should add header to put', ()=>{
expect(testFn).not.toHaveBeenCalled();
expect(setFn).not.toHaveBeenCalled();
request.put = testFn;
requestMiddleware.put('path');
expect(testFn).toHaveBeenCalledWith('path');
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
});
it('should add header to post', ()=>{
expect(testFn).not.toHaveBeenCalled();
expect(setFn).not.toHaveBeenCalled();
request.post = testFn;
requestMiddleware.post('path');
expect(testFn).toHaveBeenCalledWith('path');
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
});
it('should add header to delete', ()=>{
expect(testFn).not.toHaveBeenCalled();
expect(setFn).not.toHaveBeenCalled();
request.delete = testFn;
requestMiddleware.delete('path');
expect(testFn).toHaveBeenCalledWith('path');
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
});
});

271
package-lock.json generated
View File

@@ -63,9 +63,9 @@
"written-number": "^0.11.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^4.0.0",
"@stylistic/stylelint-plugin": "^3.1.3",
"babel-plugin-transform-import-meta": "^2.3.3",
"eslint": "^9.34.0",
"eslint": "^9.31.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0",
@@ -73,9 +73,9 @@
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^16.23.1",
"stylelint-config-recess-order": "^7.2.0",
"stylelint-config-recommended": "^17.0.0",
"stylelint": "^16.22.0",
"stylelint-config-recess-order": "^7.1.0",
"stylelint-config-recommended": "^16.0.0",
"supertest": "^7.1.4"
},
"engines": {
@@ -1884,9 +1884,9 @@
}
},
"node_modules/@csstools/media-query-list-parser": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz",
"integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz",
"integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==",
"dev": true,
"funding": [
{
@@ -1898,13 +1898,12 @@
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-parser-algorithms": "^3.0.1",
"@csstools/css-tokenizer": "^3.0.1"
}
},
"node_modules/@dmsnell/diff-match-patch": {
@@ -2022,19 +2021,18 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
"integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
"integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -2082,9 +2080,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.34.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
"integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2104,13 +2102,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
"integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.15.2",
"@eslint/core": "^0.14.0",
"levn": "^0.4.1"
},
"engines": {
@@ -2834,11 +2832,37 @@
}
},
"node_modules/@keyv/serialize": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.0.tgz",
"integrity": "sha512-RlDgexML7Z63Q8BSaqhXdCYNBy/JQnqYIwxofUrNLGCblOMHp+xux2Q8nLMLlPpgHQPoU0Do8Z6btCpRBEqZ8g==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz",
"integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==",
"dev": true,
"license": "MIT"
"dependencies": {
"buffer": "^6.0.3"
}
},
"node_modules/@keyv/serialize/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.3.0",
@@ -2981,17 +3005,17 @@
}
},
"node_modules/@stylistic/stylelint-plugin": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-4.0.0.tgz",
"integrity": "sha512-CFwt3K4Y/7bygNCLCQ8Sy4Hzgbhxq3BsNW0FIuYxl17HD3ywptm54ocyeiLVRrk5jtz1Zwks7Xr9eiZt8SWHAw==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.3.tgz",
"integrity": "sha512-85fsmzgsIVmyG3/GFrjuYj6Cz8rAM7IZiPiXCMiSMfoDOC1lOrzrXPDk24WqviAghnPqGpx8b0caK2PuewWGFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4",
"@csstools/media-query-list-parser": "^4.0.3",
"postcss": "^8.5.6",
"postcss-selector-parser": "^7.1.0",
"@csstools/css-parser-algorithms": "^3.0.1",
"@csstools/css-tokenizer": "^3.0.1",
"@csstools/media-query-list-parser": "^3.0.1",
"is-plain-object": "^5.0.0",
"postcss": "^8.4.41",
"postcss-selector-parser": "^6.1.2",
"postcss-value-parser": "^4.2.0",
"style-search": "^0.1.0"
},
@@ -2999,7 +3023,7 @@
"node": "^18.12 || >=20.9"
},
"peerDependencies": {
"stylelint": "^16.22.0"
"stylelint": "^16.8.0"
}
},
"node_modules/@tybys/wasm-util": {
@@ -4651,24 +4675,22 @@
}
},
"node_modules/cacheable": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.3.tgz",
"integrity": "sha512-M6p10iJ/VT0wT7TLIGUnm958oVrU2cUK8pQAVU21Zu7h8rbk/PeRtRWrvHJBql97Bhzk3g1N6+2VKC+Rjxna9Q==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.1.tgz",
"integrity": "sha512-Fa2BZY0CS9F0PFc/6aVA6tgpOdw+hmv9dkZOlHXII5v5Hw+meJBIWDcPrG9q/dXxGcNbym5t77fzmawrBQfTmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"hookified": "^1.10.0",
"keyv": "^5.4.0"
"keyv": "^5.3.4"
}
},
"node_modules/cacheable/node_modules/keyv": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.0.tgz",
"integrity": "sha512-QG7qR2tijh1ftOvClut4YKKg1iW6cx3GZsKoGyJPxHkGWK9oJhG9P3j5deP0QQOGDowBMVQFaP+Vm4NpGYvmIQ==",
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.4.tgz",
"integrity": "sha512-ypEvQvInNpUe+u+w8BIcPkQvEqXquyyibWE/1NB5T2BTzIpS5cGEV1LZskDzPSTvNAaT4+5FutvzlvnkxOSKlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@keyv/serialize": "^1.1.0"
"@keyv/serialize": "^1.0.3"
}
},
"node_modules/cached-path-relative": {
@@ -4863,16 +4885,13 @@
}
},
"node_modules/cipher-base": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz",
"integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.4",
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">= 0.10"
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/cjs-module-lexer": {
@@ -6061,20 +6080,20 @@
}
},
"node_modules/eslint": {
"version": "9.34.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.1",
"@eslint/core": "^0.15.2",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.34.0",
"@eslint/plugin-kit": "^0.3.5",
"@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -6239,6 +6258,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/eslint/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -7616,11 +7648,10 @@
}
},
"node_modules/hookified": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.11.0.tgz",
"integrity": "sha512-aDdIN3GyU5I6wextPplYdfmWCo+aLmjjVbntmX6HLD5RCi/xKsivYEBhnRD+d9224zFf008ZpLMPlWF0ZodYZw==",
"dev": true,
"license": "MIT"
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.10.0.tgz",
"integrity": "sha512-dJw0492Iddsj56U1JsSTm9E/0B/29a1AuoSLRAte8vQg/kaTGF3IgjEWT8c8yG4cC10+HisE1x5QAwR0Xwc+DA==",
"dev": true
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
@@ -11542,11 +11573,10 @@
}
},
"node_modules/postcss-selector-parser": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -12530,23 +12560,16 @@
"license": "ISC"
},
"node_modules/sha.js": {
"version": "2.4.12",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz",
"integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==",
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"license": "(MIT AND BSD-3-Clause)",
"dependencies": {
"inherits": "^2.0.4",
"safe-buffer": "^5.2.1",
"to-buffer": "^1.2.0"
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
},
"bin": {
"sha.js": "bin.js"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/shasum": {
@@ -13283,9 +13306,9 @@
"license": "ISC"
},
"node_modules/stylelint": {
"version": "16.23.1",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.23.1.tgz",
"integrity": "sha512-dNvDTsKV1U2YtiUDfe9d2gp902veFeo3ecCWdGlmLm2WFrAV0+L5LoOj/qHSBABQwMsZPJwfC4bf39mQm1S5zw==",
"version": "16.22.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.22.0.tgz",
"integrity": "sha512-SVEMTdjKNV4ollUrIY9ordZ36zHv2/PHzPjfPMau370MlL2VYXeLgSNMMiEbLGRO8RmD2R8/BVUeF2DfnfkC0w==",
"dev": true,
"funding": [
{
@@ -13312,7 +13335,7 @@
"debug": "^4.4.1",
"fast-glob": "^3.3.3",
"fastest-levenshtein": "^1.0.16",
"file-entry-cache": "^10.1.3",
"file-entry-cache": "^10.1.1",
"global-modules": "^2.0.0",
"globby": "^11.1.0",
"globjoin": "^0.1.4",
@@ -13346,20 +13369,19 @@
}
},
"node_modules/stylelint-config-recess-order": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-7.2.0.tgz",
"integrity": "sha512-3Y97dhsWkUHFKRLGNLF6LE5JuNB2EVAZKYJ41wBRK4gplYdk7eHhSIwE24hanu0AoNmv5534Djip70pE+y5qkA==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-7.1.0.tgz",
"integrity": "sha512-rFc4Z6SCGgEohr1khsmAZ83X56Tdi2dHY/GB7VT3qJkpKU1V2w+mYlK+b7Za5gpsxEng3jnb4FzWyIl/KTH0AQ==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"stylelint": ">=16.18",
"stylelint-order": ">=7"
}
},
"node_modules/stylelint-config-recommended": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-17.0.0.tgz",
"integrity": "sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz",
"integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==",
"dev": true,
"funding": [
{
@@ -13376,7 +13398,7 @@
"node": ">=18.12.0"
},
"peerDependencies": {
"stylelint": "^16.23.0"
"stylelint": "^16.16.0"
}
},
"node_modules/stylelint-order": {
@@ -13396,6 +13418,29 @@
"stylelint": "^16.18.0"
}
},
"node_modules/stylelint/node_modules/@csstools/media-query-list-parser": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz",
"integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/stylelint/node_modules/@csstools/selector-specificity": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
@@ -13427,23 +13472,21 @@
"license": "MIT"
},
"node_modules/stylelint/node_modules/file-entry-cache": {
"version": "10.1.3",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.3.tgz",
"integrity": "sha512-D+w75Ub8T55yor7fPgN06rkCAUbAYw2vpxJmmjv/GDAcvCnv9g7IvHhIZoxzRZThrXPFI2maeY24pPbtyYU7Lg==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.1.tgz",
"integrity": "sha512-zcmsHjg2B2zjuBgjdnB+9q0+cWcgWfykIcsDkWDB4GTPtl1eXUA+gTI6sO0u01AqK3cliHryTU55/b2Ow1hfZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"flat-cache": "^6.1.12"
"flat-cache": "^6.1.10"
}
},
"node_modules/stylelint/node_modules/flat-cache": {
"version": "6.1.12",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.12.tgz",
"integrity": "sha512-U+HqqpZPPXP5d24bWuRzjGqVqUcw64k4nZAbruniDwdRg0H10tvN7H6ku1tjhA4rg5B9GS3siEvwO2qjJJ6f8Q==",
"version": "6.1.11",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.11.tgz",
"integrity": "sha512-zfOAns94mp7bHG/vCn9Ru2eDCmIxVQ5dELUHKjHfDEOJmHNzE+uGa6208kfkgmtym4a0FFjEuFksCXFacbVhSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cacheable": "^1.10.3",
"cacheable": "^1.10.1",
"flatted": "^3.3.3",
"hookified": "^1.10.0"
}
@@ -13457,6 +13500,20 @@
"node": ">= 4"
}
},
"node_modules/stylelint/node_modules/postcss-selector-parser": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/stylelint/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",

View File

@@ -136,9 +136,9 @@
"written-number": "^0.11.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^4.0.0",
"@stylistic/stylelint-plugin": "^3.1.3",
"babel-plugin-transform-import-meta": "^2.3.3",
"eslint": "^9.34.0",
"eslint": "^9.31.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0",
@@ -146,9 +146,9 @@
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^16.23.1",
"stylelint-config-recess-order": "^7.2.0",
"stylelint-config-recommended": "^17.0.0",
"stylelint": "^16.22.0",
"stylelint-config-recess-order": "^7.1.0",
"stylelint-config-recommended": "^16.0.0",
"supertest": "^7.1.4"
}
}

View File

@@ -487,8 +487,8 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
const query = { authors: req.account.username, googleId: { $exists: false } };
const mongoCount = await HomebrewModel.countDocuments(query)
.catch((err)=>{
mongoCount = 0;
console.log(err);
return 0;
});
data.accountDetails = {

View File

@@ -116,21 +116,27 @@ const printCurrentBrew = ()=>{
}
};
const fetchThemeBundle = async (setError, setThemeBundle, renderer, theme)=>{
const fetchThemeBundle = async (obj, renderer, theme)=>{
if(!renderer || !theme) return;
const res = await request
.get(`/api/theme/${renderer}/${theme}`)
.catch((err)=>{
setError(err)
obj.setState({ error: err });
});
if(!res) {
setThemeBundle({});
obj.setState((prevState)=>({
...prevState,
themeBundle : {}
}));
return;
}
const themeBundle = res.body;
themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n');
setThemeBundle(themeBundle);
setError(null);
obj.setState((prevState)=>({
...prevState,
themeBundle : themeBundle,
error : null
}));
};
const debugTextMismatch = (clientTextRaw, serverTextRaw, label) => {