0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-29 13:22:40 +00:00

Merge branch 'master' into addDBCheckMiddleware

This commit is contained in:
G.Ambatte
2025-09-03 19:13:45 +12:00
committed by GitHub

View File

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