- This brew is currently in your Trash folder on Google Drive!
If you want to keep it, make sure to move it before it is deleted permanently!
-
- OK
-
+ {alertTrashedGoogleBrew && (
+
+ This brew is currently in your Trash folder on Google Drive!
+ If you want to keep it, make sure to move it before it is deleted permanently!
+
OK
- }
- ;
- },
-
- renderSaveButton : function(){
+ )}
+
+ );
+ const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING
- if(this.state.isSaving){
+ if(isSaving)
return
saving...;
- }
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
- if(this.state.unsavedChanges && this.state.autoSaveWarning){
- this.setAutosaveWarning();
- const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
- const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
+ if(unsavedChanges && warnUnsavedChanges) {
+ resetWarnUnsavedTimer();
+ const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
+ const text = elapsedTime === 0
+ ? 'Autosave is OFF.'
+ : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
return
- Reminder...
-
- {text}
-
+ Reminder...
+ {text}
;
}
// #3 - Unsaved changes exist, click to save, show SAVE NOW
- // Use trySave(true) instead of save() to use debounced save function
- if(this.state.unsavedChanges){
- return
this.trySave(true)} color='blue' icon='fas fa-save'>Save Now;
- }
+ if(unsavedChanges)
+ return
trySave(true)} color='blue' icon='fas fa-save'>Save Now;
+
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
- if(this.state.autoSave){
+ if(autoSaveEnabled)
return
auto-saved.;
- }
+
// DEFAULT - No unsaved changes, show SAVED
return
saved.;
- },
+ };
- handleAutoSave : function(){
- if(this.warningTimer) clearTimeout(this.warningTimer);
- this.setState((prevState)=>({
- autoSave : !prevState.autoSave,
- autoSaveWarning : prevState.autoSave
- }), ()=>{
- localStorage.setItem('AUTOSAVE_ON', JSON.stringify(this.state.autoSave));
- });
- },
+ const toggleAutoSave = ()=>{
+ clearTimeout(warnUnsavedTimeout.current);
+ clearTimeout(saveTimeout.current);
+ localStorage.setItem('AUTOSAVE_ON', JSON.stringify(!autoSaveEnabled));
+ setAutoSaveEnabled(!autoSaveEnabled);
+ setWarnUnsavedChanges(autoSaveEnabled);
+ };
- setAutosaveWarning : function(){
- setTimeout(()=>this.setState({ autoSaveWarning: false }), 4000); // 4 seconds to display
- this.warningTimer = setTimeout(()=>{this.setState({ autoSaveWarning: true });}, 900000); // 15 minutes between warnings
- this.warningTimer;
- },
+ const renderAutoSaveButton = ()=>(
+
+ Autosave
+
+ );
- errorReported : function(error) {
- this.setState({
- error
- });
- },
-
- renderAutoSaveButton : function(){
- return
- Autosave
- ;
- },
-
- processShareId : function() {
- return this.state.brew.googleId && !this.state.brew.stubbed ?
- this.state.brew.googleId + this.state.brew.shareId :
- this.state.brew.shareId;
- },
-
- getRedditLink : function(){
-
- const shareLink = this.processShareId();
- const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : '';
- const title = `${this.props.brew.title} ${systems}`;
- const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
-
-**[Homebrewery Link](${global.config.baseUrl}/share/${shareLink})**`;
-
- return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`;
- },
-
- renderNavbar : function(){
- const shareLink = this.processShareId();
+ const clearError = ()=>{
+ setError(null);
+ setIsSaving(false);
+ };
+ const renderNavbar = ()=>{
return
- {this.state.brew.title}
+ {currentBrew.title}
- {this.renderGoogleDriveIcon()}
- {this.state.error ?
- :
-
- {this.renderSaveButton()}
- {this.renderAutoSaveButton()}
-
- }
-
+ {renderGoogleDriveIcon()}
+ {error
+ ?
+ :
+ {renderSaveButton()}
+ {renderAutoSaveButton()}
+ }
+
-
-
- share
-
-
- view
-
- {navigator.clipboard.writeText(`${global.config.baseUrl}/share/${shareLink}`);}}>
- copy url
-
-
- post to reddit
-
-
+
-
-
+
+
-
;
- },
+ };
- render : function(){
- return
+ return (
+
- {this.renderNavbar()}
- {this.props.brew.lock &&
}
+ {renderNavbar()}
+
+ {currentBrew.lock &&
}
+
-
+
- ;
- }
-});
+
+ );
+};
module.exports = EditPage;
diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx
index d03e30c91..84967b1ff 100644
--- a/client/homebrew/pages/homePage/homePage.jsx
+++ b/client/homebrew/pages/homePage/homePage.jsx
@@ -1,90 +1,91 @@
-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 './homePage.less';
-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 VaultNavItem = require('../../navbar/vault.navitem.jsx');
-const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
-const AccountNavItem = require('../../navbar/account.navitem.jsx');
-const ErrorNavItem = require('../../navbar/error-navitem.jsx');
-const { fetchThemeBundle } = require('../../../../shared/helpers.js');
+import React from 'react';
+import { useEffect, useState, useRef } from 'react';
+import request from '../../utils/request-middleware.js';
+import { Meta } from 'vitreum/headtags';
-const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
-const Editor = require('../../editor/editor.jsx');
-const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
+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 { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
+import SplitPane from 'client/components/splitPane/splitPane.jsx';
+import Editor from '../../editor/editor.jsx';
+import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
-const HomePage = createClass({
- displayName : 'HomePage',
- getDefaultProps : function() {
- return {
- brew : DEFAULT_BREW,
- ver : '0.0.0'
- };
- },
- getInitialState : function() {
- return {
- brew : this.props.brew,
- welcomeText : this.props.brew.text,
- error : undefined,
- currentEditorViewPageNum : 1,
- currentEditorCursorPageNum : 1,
- currentBrewRendererPageNum : 1,
- themeBundle : {}
- };
- },
+import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
- editor : React.createRef(null),
+const HomePage =(props)=>{
+ props = {
+ brew : DEFAULT_BREW,
+ ver : '0.0.0',
+ ...props
+ };
- componentDidMount : function() {
- fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
- },
+ 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);
- handleSave : function(){
+ const editorRef = useRef(null);
+
+ useEffect(()=>{
+ fetchThemeBundle(setError, setThemeBundle, brew.renderer, brew.theme);
+ }, []);
+
+ const save = ()=>{
request.post('/api')
- .send(this.state.brew)
+ .send(brew)
.end((err, res)=>{
if(err) {
- this.setState({ error: err });
+ setError(err);
return;
}
- const brew = res.body;
- window.location = `/edit/${brew.editId}`;
+ const saved = res.body;
+ window.location = `/edit/${saved.editId}`;
});
- },
- handleSplitMove : function(){
- this.editor.current.update();
- },
+ };
- handleEditorViewPageChange : function(pageNumber){
- this.setState({ currentEditorViewPageNum: pageNumber });
- },
+ const handleSplitMove = ()=>{
+ editorRef.current.update();
+ };
- handleEditorCursorPageChange : function(pageNumber){
- this.setState({ currentEditorCursorPageNum: pageNumber });
- },
+ const handleEditorViewPageChange = (pageNumber)=>{
+ setCurrentEditorViewPageNum(pageNumber);
+ };
+
+ const handleEditorCursorPageChange = (pageNumber)=>{
+ setCurrentEditorCursorPageNum(pageNumber);
+ };
+
+ const handleBrewRendererPageChange = (pageNumber)=>{
+ setCurrentBrewRendererPageNum(pageNumber);
+ };
- handleBrewRendererPageChange : function(pageNumber){
- this.setState({ currentBrewRendererPageNum: pageNumber });
- },
+ const handleTextChange = (text)=>{
+ setBrew((prevBrew) => ({ ...prevBrew, text }));
+ };
- handleTextChange : function(text){
- this.setState((prevState)=>({
- brew : { ...prevState.brew, text: text },
- }));
- },
- renderNavbar : function(){
- return
+ const clearError = ()=>{
+ setError(null);
+ setIsSaving(false);
+ };
+
+ const renderNavbar = ()=>{
+ return
- {this.state.error ?
- :
+ {error ?
+ :
null
}
@@ -94,48 +95,48 @@ const HomePage = createClass({
;
- },
+ };
- render : function(){
- return
+ return (
+
- {this.renderNavbar()}
+ {renderNavbar()}
-
+
-
;
- }
-});
+
+ )
+};
module.exports = HomePage;
diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx
index 64fac86c0..bb21441cf 100644
--- a/client/homebrew/pages/newPage/newPage.jsx
+++ b/client/homebrew/pages/newPage/newPage.jsx
@@ -1,275 +1,251 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
-require('./newPage.less');
-const React = require('react');
-const createClass = require('create-react-class');
-import request from '../../utils/request-middleware.js';
+import './newPage.less';
-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');
-const PrintNavItem = require('../../navbar/print.navitem.jsx');
-const Navbar = require('../../navbar/navbar.jsx');
-const AccountNavItem = require('../../navbar/account.navitem.jsx');
-const ErrorNavItem = require('../../navbar/error-navitem.jsx');
-const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
-const HelpNavItem = require('../../navbar/help.navitem.jsx');
+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 SplitPane = require('naturalcrit/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');
-const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
+import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
+import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
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({
- displayName : 'NewPage',
- getDefaultProps : function() {
- return {
- brew : DEFAULT_BREW
+ 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);
};
- },
+ }, []);
- 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 loadBrew = ()=>{
+ const brew = { ...currentBrew };
+ if(!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.title = metaStorage?.title || this.state.brew.title;
- // brew.description = metaStorage?.description || this.state.brew.description;
+ brew.text = brewStorage ?? brew.text;
+ brew.style = styleStorage ?? brew.style;
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';
- this.setState({
- brew : brew,
- saveGoogle : (saveStorage == 'GOOGLE-DRIVE' && this.state.saveGoogle)
- });
-
- fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
+ setCurrentBrew(brew);
+ setSaveGoogle(saveStorage == 'GOOGLE-DRIVE' && saveGoogle);
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);
- },
+ };
- handleControlKeys : function(e){
- if(!(e.ctrlKey || e.metaKey)) return;
+ const handleControlKeys = (e) => {
+ if (!(e.ctrlKey || e.metaKey)) return;
const S_KEY = 83;
const P_KEY = 80;
- if(e.keyCode == S_KEY) this.save();
- if(e.keyCode == P_KEY) printCurrentBrew();
- if(e.keyCode == P_KEY || e.keyCode == S_KEY){
- e.stopPropagation();
+ if (e.keyCode === S_KEY) save();
+ if (e.keyCode === P_KEY) printCurrentBrew();
+ if (e.keyCode === S_KEY || e.keyCode === P_KEY) {
e.preventDefault();
+ e.stopPropagation();
}
- },
+ };
- 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)=>{
+ //If there are HTML errors, run the validator on every change to give quick feedback
+ if(HTMLErrors.length)
+ HTMLErrors = Markdown.validate(text);
- handleBrewRendererPageChange : function(pageNumber){
- this.setState({ currentBrewRendererPageNum: pageNumber });
- },
-
- 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);
-
- this.setState((prevState)=>({
- brew : { ...prevState.brew, text: text },
- htmlErrors : htmlErrors,
- }));
+ setHTMLErrors(HTMLErrors);
+ setCurrentBrew((prevBrew) => ({ ...prevBrew, text }));
localStorage.setItem(BREWKEY, text);
- },
+ };
- handleStyleChange : function(style){
- this.setState((prevState)=>({
- brew : { ...prevState.brew, style: style },
- }));
+ const handleStyleChange = (style) => {
+ setCurrentBrew(prevBrew => ({ ...prevBrew, style }));
localStorage.setItem(STYLEKEY, style);
- },
+ };
- 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 handleSnipChange = (snippet)=>{
+ //If there are HTML errors, run the validator on every change to give quick feedback
+ if(HTMLErrors.length)
+ HTMLErrors = Markdown.validate(snippet);
- this.setState((prevState)=>({
- brew : { ...prevState.brew, snippets: snippet },
- htmlErrors : htmlErrors,
- }), ()=>{if(this.state.autoSave) this.trySave();});
- },
+ setHTMLErrors(HTMLErrors);
+ setCurrentBrew((prevBrew) => ({ ...prevBrew, snippets: snippet }));
+ };
- 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);
+ const handleMetaChange = (metadata, field = undefined) => {
+ if (field === 'theme' || field === 'renderer')
+ fetchThemeBundle(setError, setThemeBundle, 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
- }));
- });
- ;
- },
+ setCurrentBrew(prev => ({ ...prev, ...metadata }));
+ localStorage.setItem(METAKEY, JSON.stringify({
+ renderer : metadata.renderer,
+ theme : metadata.theme,
+ lang : metadata.lang
+ }));
+ };
- save : async function(){
- this.setState({
- isSaving : true
- });
+ const save = async () => {
+ setIsSaving(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);
- }
+ let updatedBrew = { ...currentBrew };
+ splitTextStyleAndMetadata(updatedBrew);
+
+ const pageRegex = updatedBrew.renderer === 'legacy' ? /\\page/g : /^\\page$/gm;
+ updatedBrew.pageCount = (updatedBrew.text.match(pageRegex) || []).length + 1;
- 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 });
+ .post(`/api${saveGoogle ? '?saveToGoogle=true' : ''}`)
+ .send(updatedBrew)
+ .catch((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(STYLEKEY);
localStorage.removeItem(METAKEY);
- window.location = `/edit/${brew.editId}`;
- },
+ window.location = `/edit/${savedBrew.editId}`;
+ };
- renderSaveButton : function(){
- if(this.state.isSaving){
+ const renderSaveButton = ()=>{
+ if(isSaving){
return
save...
;
} else {
- return
+ return
save
;
}
- },
+ };
- renderNavbar : function(){
- return
+ const clearError = ()=>{
+ setError(null);
+ setIsSaving(false);
+ };
+ const renderNavbar = () => (
+
- {this.state.brew.title}
+ {currentBrew.title}
- {this.state.error ?
- :
- this.renderSaveButton()
- }
+ {error
+ ?
+ : renderSaveButton()}
- ;
- },
+
+ );
- render : function(){
- return
- {this.renderNavbar()}
+ return (
+
;
- }
-});
+
+ );
+};
module.exports = NewPage;
diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx
index e9c5540a2..50104a665 100644
--- a/client/homebrew/pages/sharePage/sharePage.jsx
+++ b/client/homebrew/pages/sharePage/sharePage.jsx
@@ -17,15 +17,11 @@ const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpe
const SharePage = (props)=>{
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
- const [state, setState] = useState({
- themeBundle : {},
- currentBrewRendererPageNum : 1,
- });
+ const [themeBundle, setThemeBundle] = useState({});
+ const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
- setState((prevState)=>({
- currentBrewRendererPageNum : pageNumber,
- ...prevState }));
+ setCurrentBrewRendererPageNum(pageNumber);
}, []);
const handleControlKeys = (e)=>{
@@ -40,11 +36,7 @@ const SharePage = (props)=>{
useEffect(()=>{
document.addEventListener('keydown', handleControlKeys);
- fetchThemeBundle(
- { setState },
- brew.renderer,
- brew.theme
- );
+ fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
@@ -114,9 +106,9 @@ const SharePage = (props)=>{
lang={brew.lang}
renderer={brew.renderer}
theme={brew.theme}
- themeBundle={state.themeBundle}
+ themeBundle={themeBundle}
onPageChange={handleBrewRendererPageChange}
- currentBrewRendererPageNum={state.currentBrewRendererPageNum}
+ currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true}
/>
diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx
index f6fae639d..e4a8b0b4e 100644
--- a/client/homebrew/pages/userPage/userPage.jsx
+++ b/client/homebrew/pages/userPage/userPage.jsx
@@ -39,10 +39,14 @@ const UserPage = (props)=>{
}] : [])
];
+ const clearError = ()=>{
+ setError(null);
+ };
+
const navItems = (
- {error && ()}
+ {error && ()}
diff --git a/client/homebrew/pages/vaultPage/vaultPage.jsx b/client/homebrew/pages/vaultPage/vaultPage.jsx
index f979aa4f7..e098bb1a2 100644
--- a/client/homebrew/pages/vaultPage/vaultPage.jsx
+++ b/client/homebrew/pages/vaultPage/vaultPage.jsx
@@ -12,7 +12,7 @@ const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx');
-const SplitPane = require('../../../../shared/naturalcrit/splitPane/splitPane.jsx');
+const SplitPane = require('client/components/splitPane/splitPane.jsx');
const ErrorIndex = require('../errorPage/errors/errorIndex.js');
import request from '../../utils/request-middleware.js';
diff --git a/client/homebrew/utils/request-middleware.spec.js b/client/homebrew/utils/request-middleware.spec.js
new file mode 100644
index 000000000..d7c198394
--- /dev/null
+++ b/client/homebrew/utils/request-middleware.spec.js
@@ -0,0 +1,74 @@
+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');
+ });
+});
diff --git a/package.json b/package.json
index 25800fff6..77f192afc 100644
--- a/package.json
+++ b/package.json
@@ -137,19 +137,19 @@
"written-number": "^0.11.1"
},
"devDependencies": {
- "@stylistic/stylelint-plugin": "^3.1.3",
+ "@stylistic/stylelint-plugin": "^4.0.0",
"babel-plugin-transform-import-meta": "^2.3.3",
- "eslint": "^9.31.0",
+ "eslint": "^9.35.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0",
- "jest": "^30.0.5",
+ "jest": "^30.1.3",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^16.22.0",
- "stylelint-config-recess-order": "^7.1.0",
- "stylelint-config-recommended": "^16.0.0",
+ "stylelint": "^16.24.0",
+ "stylelint-config-recess-order": "^7.3.0",
+ "stylelint-config-recommended": "^17.0.0",
"supertest": "^7.1.4"
}
}
diff --git a/server/app.js b/server/app.js
index 869fe6555..afba0997b 100644
--- a/server/app.js
+++ b/server/app.js
@@ -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 = {
diff --git a/shared/helpers.js b/shared/helpers.js
index e09b0bdc4..3f91583d6 100644
--- a/shared/helpers.js
+++ b/shared/helpers.js
@@ -116,27 +116,21 @@ const printCurrentBrew = ()=>{
}
};
-const fetchThemeBundle = async (obj, renderer, theme)=>{
+const fetchThemeBundle = async (setError, setThemeBundle, renderer, theme)=>{
if(!renderer || !theme) return;
const res = await request
.get(`/api/theme/${renderer}/${theme}`)
.catch((err)=>{
- obj.setState({ error: err });
+ setError(err)
});
if(!res) {
- obj.setState((prevState)=>({
- ...prevState,
- themeBundle : {}
- }));
+ setThemeBundle({});
return;
}
const themeBundle = res.body;
themeBundle.joinedStyles = themeBundle.styles.map((style)=>``).join('\n\n');
- obj.setState((prevState)=>({
- ...prevState,
- themeBundle : themeBundle,
- error : null
- }));
+ setThemeBundle(themeBundle);
+ setError(null);
};
const debugTextMismatch = (clientTextRaw, serverTextRaw, label) => {
diff --git a/themes/V3/UnearthedArcana/dropdownPreview.png b/themes/V3/UnearthedArcana/dropdownPreview.png
new file mode 100644
index 000000000..cfc1e36bc
Binary files /dev/null and b/themes/V3/UnearthedArcana/dropdownPreview.png differ
diff --git a/themes/V3/UnearthedArcana/dropdownTexture.png b/themes/V3/UnearthedArcana/dropdownTexture.png
new file mode 100644
index 000000000..d0c0256c0
Binary files /dev/null and b/themes/V3/UnearthedArcana/dropdownTexture.png differ
diff --git a/themes/V3/UnearthedArcana/settings.json b/themes/V3/UnearthedArcana/settings.json
new file mode 100644
index 000000000..273f0bb2f
--- /dev/null
+++ b/themes/V3/UnearthedArcana/settings.json
@@ -0,0 +1,6 @@
+{
+ "name" : "UnearthedArcana",
+ "renderer" : "V3",
+ "baseTheme" : false,
+ "baseSnippets" : false
+}
diff --git a/themes/V3/UnearthedArcana/style.less b/themes/V3/UnearthedArcana/style.less
new file mode 100644
index 000000000..695924d37
--- /dev/null
+++ b/themes/V3/UnearthedArcana/style.less
@@ -0,0 +1,38 @@
+@import (less) './themes/fonts/5e/fonts.less';
+@import (less) './themes/assets/assets.less';
+
+:root {
+ //Colors
+ --HB_Color_Background : #FFFFFF; // White
+ --HB_Color_WatercolorStain : #000000; // Black
+}
+
+.page {
+ font-family: Cambria,Georgia,serif;
+ font-size: 14px;
+ h1, h2, h3, h4 {
+ font-variant: small-caps;
+ font-weight: normal;
+ }
+ h1 {
+ column-span: all;
+ -webkit-column-span: all;
+ font-size: 40px;
+ }
+ h2 {
+ font-size: 26px;
+ }
+ h3 {
+ font-size: 20px;
+ border-bottom: 2px solid black;
+ }
+ h4 {
+ font-size: 18px;
+ }
+ h5 {
+ font-size: 16px;
+ }
+ h6 {
+ font-size: 14px;
+ }
+}
diff --git a/themes/themes.json b/themes/themes.json
index 16a4b9b13..7e01b180c 100644
--- a/themes/themes.json
+++ b/themes/themes.json
@@ -35,6 +35,13 @@
"baseTheme": "Blank",
"baseSnippets": "5ePHB",
"path": "Journal"
+ },
+ "UnearthedArcana": {
+ "name": "UnearthedArcana",
+ "renderer": "V3",
+ "baseTheme": false,
+ "baseSnippets": false,
+ "path": "UnearthedArcana"
}
}
}
\ No newline at end of file