0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-28 00:43:08 +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

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

View File

@@ -1,14 +1,32 @@
require('./error-navitem.less'); require('./error-navitem.less');
const React = require('react'); const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const createClass = require('create-react-class');
const ErrorNavItem = ({error = '', clearError})=>{ const ErrorNavItem = createClass({
getDefaultProps : function() {
return {
error : '',
parent : null
};
},
render : function() {
const clearError = ()=>{
const state = {
error : null
};
if(this.props.parent.state.isSaving) {
state.isSaving = false;
}
this.props.parent.setState(state);
};
const error = this.props.error;
const response = error.response; const response = error.response;
const errorCode = error.code
const status = response?.status; const status = response?.status;
const errorCode = error.code
const HBErrorCode = response?.body?.HBErrorCode; const HBErrorCode = response?.body?.HBErrorCode;
const message = response?.body?.message; const message = response?.body?.message;
let errMsg = ''; let errMsg = '';
try { try {
errMsg += `${error.toString()}\n\n`; errMsg += `${error.toString()}\n\n`;
@@ -133,6 +151,7 @@ const ErrorNavItem = ({error = '', clearError})=>{
</a>. </a>.
</div> </div>
</Nav.item>; </Nav.item>;
}; }
});
module.exports = ErrorNavItem; module.exports = ErrorNavItem;

View File

@@ -10,40 +10,28 @@ const METAKEY = 'homebrewery-new-meta';
const NewBrew = ()=>{ const NewBrew = ()=>{
const handleFileChange = (e)=>{ const handleFileChange = (e)=>{
const file = e.target.files[0]; const file = e.target.files[0];
if(!file) return; if(file) {
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(); const reader = new FileReader();
reader.onload = (e)=>{ reader.onload = (e)=>{
const fileContent = e.target.result; const fileContent = e.target.result;
const newBrew = { text: fileContent, style: '' }; const newBrew = {
text : fileContent,
style : ''
};
if(fileContent.startsWith('```metadata')) { if(fileContent.startsWith('```metadata')) {
splitTextStyleAndMetadata(newBrew); splitTextStyleAndMetadata(newBrew); // Modify newBrew directly
localStorage.setItem(BREWKEY, newBrew.text); localStorage.setItem(BREWKEY, newBrew.text);
localStorage.setItem(STYLEKEY, newBrew.style); localStorage.setItem(STYLEKEY, newBrew.style);
localStorage.setItem(METAKEY, JSON.stringify( localStorage.setItem(METAKEY, JSON.stringify(_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])));
_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])
));
window.location.href = '/new'; window.location.href = '/new';
return; } else {
alert('This file is invalid, please, enter a valid file');
} }
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); reader.readAsText(file);
}
}; };
return ( return (
<Nav.dropdown> <Nav.dropdown>
<Nav.item <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 { &::before {
margin-right : 5px; margin-right : 5px;
font-family : 'Font Awesome 6 Free'; font-family : 'Font Awesome 6 Free';
font-weight : 900;
content : '\f00c'; content : '\f00c';
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -136,9 +136,9 @@
"written-number": "^0.11.1" "written-number": "^0.11.1"
}, },
"devDependencies": { "devDependencies": {
"@stylistic/stylelint-plugin": "^4.0.0", "@stylistic/stylelint-plugin": "^3.1.3",
"babel-plugin-transform-import-meta": "^2.3.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-jest": "^29.0.1",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0", "globals": "^16.3.0",
@@ -146,9 +146,9 @@
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2", "jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"stylelint": "^16.23.1", "stylelint": "^16.22.0",
"stylelint-config-recess-order": "^7.2.0", "stylelint-config-recess-order": "^7.1.0",
"stylelint-config-recommended": "^17.0.0", "stylelint-config-recommended": "^16.0.0",
"supertest": "^7.1.4" "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 query = { authors: req.account.username, googleId: { $exists: false } };
const mongoCount = await HomebrewModel.countDocuments(query) const mongoCount = await HomebrewModel.countDocuments(query)
.catch((err)=>{ .catch((err)=>{
mongoCount = 0;
console.log(err); console.log(err);
return 0;
}); });
data.accountDetails = { 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; if(!renderer || !theme) return;
const res = await request const res = await request
.get(`/api/theme/${renderer}/${theme}`) .get(`/api/theme/${renderer}/${theme}`)
.catch((err)=>{ .catch((err)=>{
setError(err) obj.setState({ error: err });
}); });
if(!res) { if(!res) {
setThemeBundle({}); obj.setState((prevState)=>({
...prevState,
themeBundle : {}
}));
return; return;
} }
const themeBundle = res.body; const themeBundle = res.body;
themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n'); themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n');
setThemeBundle(themeBundle); obj.setState((prevState)=>({
setError(null); ...prevState,
themeBundle : themeBundle,
error : null
}));
}; };
const debugTextMismatch = (clientTextRaw, serverTextRaw, label) => { const debugTextMismatch = (clientTextRaw, serverTextRaw, label) => {