0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-09 09:22:38 +00:00

Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-red-background

This commit is contained in:
Víctor Losada Hernández
2025-10-05 19:43:29 +02:00
16 changed files with 260 additions and 148 deletions

View File

@@ -49,7 +49,7 @@ Make an changes you need to `config/docker.json` then build the image. If it doe
"web_port" : 8000, "web_port" : 8000,
"enable_v3" : true, "enable_v3" : true,
"mongodb_uri": "mongodb://172.17.0.2/homebrewery", "mongodb_uri": "mongodb://172.17.0.2/homebrewery",
"enable_themes" : true, "enable_themes" : true
} }
``` ```
@@ -90,6 +90,13 @@ docker run --name homebrewery-mongodb -d --restart unless-stopped -v mongodata:/
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
``` ```
**NOTE:** If you are running from the Windows command line, this will not work as `$(pwd)` is not valid syntax. Use this command instead:
```shell
# Make sure you run this in the homebrewery directory
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v %cd%/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
```
## Updating the Image ## Updating the Image
When Homebrewery code updates, your docker container will not automatically follow the changes. To do so you will need to rebuild your homebrewery image. When Homebrewery code updates, your docker container will not automatically follow the changes. To do so you will need to rebuild your homebrewery image.
@@ -117,3 +124,9 @@ docker-compose build homebrewery
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
``` ```
**NOTE:** If you are running from the Windows command line, this will not work as `$(pwd)` is not valid syntax. Use this command instead:
```shell
# Make sure you run this in the homebrewery directory
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v %cd%/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
```

View File

@@ -7,15 +7,17 @@ import LockTools from './lockTools/lockTools.jsx';
const tabGroups = ['brew', 'notifications', 'authors', 'locks']; const tabGroups = ['brew', 'notifications', 'authors', 'locks'];
const ADMIN_TAB = 'HB_adminPage_currentTab';
const Admin = ()=>{ const Admin = ()=>{
const [currentTab, setCurrentTab] = useState(''); const [currentTab, setCurrentTab] = useState('');
useEffect(()=>{ useEffect(()=>{
setCurrentTab(localStorage.getItem('hbAdminTab') || 'brew'); setCurrentTab(localStorage.getItem(ADMIN_TAB) || 'brew');
}, []); }, []);
useEffect(()=>{ useEffect(()=>{
localStorage.setItem('hbAdminTab', currentTab); localStorage.setItem(ADMIN_TAB, currentTab);
}, [currentTab]); }, [currentTab]);
return ( return (

View File

@@ -2,7 +2,8 @@ require('./splitPane.less');
const React = require('react'); const React = require('react');
const { useState, useEffect } = React; const { useState, useEffect } = React;
const storageKey = 'naturalcrit-pane-split'; const PANE_WIDTH_KEY = 'HB_editor_splitWidth';
const LIVE_SCROLL_KEY = 'HB_editor_liveScroll';
const SplitPane = (props)=>{ const SplitPane = (props)=>{
const { const {
@@ -18,9 +19,9 @@ const SplitPane = (props)=>{
const [liveScroll, setLiveScroll] = useState(false); const [liveScroll, setLiveScroll] = useState(false);
useEffect(()=>{ useEffect(()=>{
const savedPos = window.localStorage.getItem(storageKey); const savedPos = window.localStorage.getItem(PANE_WIDTH_KEY);
setDividerPos(savedPos ? limitPosition(savedPos, 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)) : window.innerWidth / 2); setDividerPos(savedPos ? limitPosition(savedPos, 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)) : window.innerWidth / 2);
setLiveScroll(window.localStorage.getItem('liveScroll') === 'true'); setLiveScroll(window.localStorage.getItem(LIVE_SCROLL_KEY) === 'true');
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
return ()=>window.removeEventListener('resize', handleResize); return ()=>window.removeEventListener('resize', handleResize);
@@ -29,13 +30,13 @@ const SplitPane = (props)=>{
const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x))); const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x)));
//when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position //when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position
const handleResize = ()=>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13))); const handleResize = ()=>setDividerPos(limitPosition(window.localStorage.getItem(PANE_WIDTH_KEY), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)));
const handleUp =(e)=>{ const handleUp =(e)=>{
e.preventDefault(); e.preventDefault();
if(isDragging) { if(isDragging) {
onDragFinish(dividerPos); onDragFinish(dividerPos);
window.localStorage.setItem(storageKey, dividerPos); window.localStorage.setItem(PANE_WIDTH_KEY, dividerPos);
} }
setIsDragging(false); setIsDragging(false);
}; };
@@ -52,7 +53,7 @@ const SplitPane = (props)=>{
}; };
const liveScrollToggle = ()=>{ const liveScrollToggle = ()=>{
window.localStorage.setItem('liveScroll', String(!liveScroll)); window.localStorage.setItem(LIVE_SCROLL_KEY, String(!liveScroll));
setLiveScroll(!liveScroll); setLiveScroll(!liveScroll);
}; };

View File

@@ -24,6 +24,8 @@ const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m; const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
const PAGE_HEIGHT = 1056; const PAGE_HEIGHT = 1056;
const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState';
const INITIAL_CONTENT = dedent` const INITIAL_CONTENT = dedent`
<!DOCTYPE html><html><head> <!DOCTYPE html><html><head>
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" /> <link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
@@ -122,7 +124,7 @@ const BrewRenderer = (props)=>{
//useEffect to store or gather toolbar state from storage //useEffect to store or gather toolbar state from storage
useEffect(()=>{ useEffect(()=>{
const toolbarState = JSON.parse(window.localStorage.getItem('hb_toolbarState')); const toolbarState = JSON.parse(window.localStorage.getItem(TOOLBAR_STATE_KEY));
toolbarState && setDisplayOptions(toolbarState); toolbarState && setDisplayOptions(toolbarState);
}, []); }, []);
@@ -284,7 +286,7 @@ const BrewRenderer = (props)=>{
const handleDisplayOptionsChange = (newDisplayOptions)=>{ const handleDisplayOptionsChange = (newDisplayOptions)=>{
setDisplayOptions(newDisplayOptions); setDisplayOptions(newDisplayOptions);
localStorage.setItem('hb_toolbarState', JSON.stringify(newDisplayOptions)); localStorage.setItem(TOOLBAR_STATE_KEY, JSON.stringify(newDisplayOptions));
}; };
const pagesStyle = { const pagesStyle = {

View File

@@ -9,6 +9,8 @@ import { Anchored, AnchoredBox, AnchoredTrigger } from '../../../components/Anch
const MAX_ZOOM = 300; const MAX_ZOOM = 300;
const MIN_ZOOM = 10; const MIN_ZOOM = 10;
const TOOLBAR_VISIBILITY = 'HB_renderer_toolbarVisibility';
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages, headerState, setHeaderState })=>{ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages, headerState, setHeaderState })=>{
const [pageNum, setPageNum] = useState(1); const [pageNum, setPageNum] = useState(1);
@@ -21,8 +23,8 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
}, [visiblePages]); }, [visiblePages]);
useEffect(()=>{ useEffect(()=>{
const Visibility = localStorage.getItem('hb_toolbarVisibility'); const Visibility = localStorage.getItem(TOOLBAR_VISIBILITY);
if (Visibility) setToolsVisible(Visibility === 'true'); if(Visibility) setToolsVisible(Visibility === 'true');
}, []); }, []);
@@ -100,7 +102,7 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
<div className='toggleButton'> <div className='toggleButton'>
<button title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{ <button title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{
setToolsVisible(!toolsVisible); setToolsVisible(!toolsVisible);
localStorage.setItem('hb_toolbarVisibility', !toolsVisible); localStorage.setItem(TOOLBAR_VISIBILITY, !toolsVisible);
}}><i className='fas fa-glasses' /></button> }}><i className='fas fa-glasses' /></button>
<button title={`${headerState ? 'Hide' : 'Show'} Header Navigation`} onClick={()=>{setHeaderState(!headerState);}}><i className='fas fa-rectangle-list' /></button> <button title={`${headerState ? 'Hide' : 'Show'} Header Navigation`} onClick={()=>{setHeaderState(!headerState);}}><i className='fas fa-rectangle-list' /></button>
</div> </div>

View File

@@ -10,7 +10,7 @@ const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
const SnippetBar = require('./snippetbar/snippetbar.jsx'); const SnippetBar = require('./snippetbar/snippetbar.jsx');
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx'); const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME'; const EDITOR_THEME_KEY = 'HB_editor_theme';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/; const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;

View File

@@ -4,6 +4,8 @@ import './homebrew.less';
import React from 'react'; import React from 'react';
import { StaticRouter as Router, Route, Routes, useParams, useSearchParams } from 'react-router'; import { StaticRouter as Router, Route, Routes, useParams, useSearchParams } from 'react-router';
import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js';
import HomePage from './pages/homePage/homePage.jsx'; import HomePage from './pages/homePage/homePage.jsx';
import EditPage from './pages/editPage/editPage.jsx'; import EditPage from './pages/editPage/editPage.jsx';
import UserPage from './pages/userPage/userPage.jsx'; import UserPage from './pages/userPage/userPage.jsx';
@@ -57,6 +59,7 @@ const Homebrew = (props)=>{
} }
return null; return null;
}; };
updateLocalStorage();
return ( return (
<Router location={url}> <Router location={url}>

View File

@@ -5,8 +5,8 @@ const Moment = require('moment');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const EDIT_KEY = 'homebrewery-recently-edited'; const EDIT_KEY = 'HB_nav_recentlyEdited';
const VIEW_KEY = 'homebrewery-recently-viewed'; const VIEW_KEY = 'HB_nav_recentlyViewed';
const RecentItems = createClass({ const RecentItems = createClass({

View File

@@ -13,7 +13,7 @@ const AccountPage = (props)=>{
// initialize save location from local storage based on user id // initialize save location from local storage based on user id
React.useEffect(()=>{ React.useEffect(()=>{
if(!saveLocation && accountDetails.username) { if(!saveLocation && accountDetails.username) {
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${accountDetails.username}`; SAVEKEY = `HB_editor_defaultSave_${accountDetails.username}`;
// if no SAVEKEY in local storage, default save location to Google Drive if user has Google account. // if no SAVEKEY in local storage, default save location to Google Drive if user has Google account.
let saveLocation = window.localStorage.getItem(SAVEKEY); let saveLocation = window.localStorage.getItem(SAVEKEY);
saveLocation = saveLocation ?? (accountDetails.googleId ? 'GOOGLE-DRIVE' : 'HOMEBREWERY'); saveLocation = saveLocation ?? (accountDetails.googleId ? 'GOOGLE-DRIVE' : 'HOMEBREWERY');

View File

@@ -7,7 +7,9 @@ const moment = require('moment');
const BrewItem = require('./brewItem/brewItem.jsx'); const BrewItem = require('./brewItem/brewItem.jsx');
const USERPAGE_KEY_PREFIX = 'HOMEBREWERY-LISTPAGE'; const USERPAGE_SORT_DIR = 'HB_listPage_sortDir';
const USERPAGE_SORT_TYPE = 'HB_listPage_sortType';
const USERPAGE_GROUP_VISIBILITY_PREFIX = 'HB_listPage_visibility_group';
const DEFAULT_SORT_TYPE = 'alpha'; const DEFAULT_SORT_TYPE = 'alpha';
const DEFAULT_SORT_DIR = 'asc'; const DEFAULT_SORT_DIR = 'asc';
@@ -50,12 +52,12 @@ const ListPage = createClass({
// LOAD FROM LOCAL STORAGE // LOAD FROM LOCAL STORAGE
if(typeof window !== 'undefined') { if(typeof window !== 'undefined') {
const newSortType = (this.state.sortType ?? (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-SORTTYPE`) || DEFAULT_SORT_TYPE)); const newSortType = (this.state.sortType ?? (localStorage.getItem(USERPAGE_SORT_TYPE) || DEFAULT_SORT_TYPE));
const newSortDir = (this.state.sortDir ?? (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-SORTDIR`) || DEFAULT_SORT_DIR)); const newSortDir = (this.state.sortDir ?? (localStorage.getItem(USERPAGE_SORT_DIR) || DEFAULT_SORT_DIR));
this.updateUrl(this.state.filterString, newSortType, newSortDir); this.updateUrl(this.state.filterString, newSortType, newSortDir);
const brewCollection = this.props.brewCollection.map((brewGroup)=>{ const brewCollection = this.props.brewCollection.map((brewGroup)=>{
brewGroup.visible = (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-VISIBILITY-${brewGroup.class}`) ?? 'true')=='true'; brewGroup.visible = (localStorage.getItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`) ?? 'true')=='true';
return brewGroup; return brewGroup;
}); });
@@ -73,10 +75,10 @@ const ListPage = createClass({
saveToLocalStorage : function() { saveToLocalStorage : function() {
this.state.brewCollection.map((brewGroup)=>{ this.state.brewCollection.map((brewGroup)=>{
localStorage.setItem(`${USERPAGE_KEY_PREFIX}-VISIBILITY-${brewGroup.class}`, `${brewGroup.visible}`); localStorage.setItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`, `${brewGroup.visible}`);
}); });
localStorage.setItem(`${USERPAGE_KEY_PREFIX}-SORTTYPE`, this.state.sortType); localStorage.setItem(USERPAGE_SORT_TYPE, this.state.sortType);
localStorage.setItem(`${USERPAGE_KEY_PREFIX}-SORTDIR`, this.state.sortDir); localStorage.setItem(USERPAGE_SORT_DIR, this.state.sortDir);
}, },
renderBrews : function(brews){ renderBrews : function(brews){

View File

@@ -1,48 +1,53 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import './editPage.less'; import './editPage.less';
import React, { useState, useEffect, useRef, useCallback } from 'react'; // Common imports
import React, { useState, useEffect, useRef } from 'react';
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 _ from 'lodash';; import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
import { makePatches, stringifyPatches } from '@sanity/diff-match-patch'; import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
import { md5 } from 'hash-wasm';
import { gzipSync, strToU8 } from 'fflate'; import SplitPane from 'client/components/splitPane/splitPane.jsx';
import { Meta } from 'vitreum/headtags'; import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
import Nav from 'naturalcrit/nav/nav.jsx'; import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx'; import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx'; import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx'; import AccountNavItem from '../../navbar/account.navitem.jsx';
import ShareNavItem from '../../navbar/share.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx'; import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx'; import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx'; import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx'; import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx'; import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
import SplitPane from 'client/components/splitPane/splitPane.jsx'; // Page specific imports
import Editor from '../../editor/editor.jsx'; import { Meta } from 'vitreum/headtags';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; import _ from 'lodash';
import { md5 } from 'hash-wasm';
import { gzipSync, strToU8 } from 'fflate';
import { makePatches, stringifyPatches } from '@sanity/diff-match-patch';
import ShareNavItem from '../../navbar/share.navitem.jsx';
import LockNotification from './lockNotification/lockNotification.jsx'; import LockNotification from './lockNotification/lockNotification.jsx';
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle } from '../../../../shared/helpers.js';
import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js'; import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js';
import googleDriveIcon from '../../googleDrive.svg'; import googleDriveIcon from '../../googleDrive.svg';
const SAVE_TIMEOUT = 10000; const SAVE_TIMEOUT = 10000;
const UNSAVED_WARNING_TIMEOUT = 900000; //Warn user afer 15 minutes of unsaved changes const UNSAVED_WARNING_TIMEOUT = 900000; //Warn user afer 15 minutes of unsaved changes
const UNSAVED_WARNING_POPUP_TIMEOUT = 4000; //Show the warning for 4 seconds const UNSAVED_WARNING_POPUP_TIMEOUT = 4000; //Show the warning for 4 seconds
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style'; const AUTOSAVE_KEY = 'HB_editor_autoSaveOn';
const SNIPKEY = 'homebrewery-new-snippets'; const BREWKEY = 'HB_newPage_content';
const METAKEY = 'homebrewery-new-meta'; const STYLEKEY = 'HB_newPage_style';
const SNIPKEY = 'HB_newPage_snippets';
const METAKEY = 'HB_newPage_meta';
const useLocalStorage = false;
const EditPage = (props)=>{ const EditPage = (props)=>{
props = { props = {
@@ -74,10 +79,8 @@ const EditPage = (props)=>{
const trySaveRef = useRef(trySave); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew const trySaveRef = useRef(trySave); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew
const unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges const unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges
const useLocalStorage = false;
useEffect(()=>{ useEffect(()=>{
const autoSavePref = JSON.parse(localStorage.getItem('AUTOSAVE_ON') ?? true); const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
setAutoSaveEnabled(autoSavePref); setAutoSaveEnabled(autoSavePref);
setWarnUnsavedChanges(!autoSavePref); setWarnUnsavedChanges(!autoSavePref);
setHTMLErrors(Markdown.validate(currentBrew.text)); setHTMLErrors(Markdown.validate(currentBrew.text));
@@ -120,18 +123,6 @@ const EditPage = (props)=>{
editorRef.current?.update(); editorRef.current?.update();
}; };
const handleEditorViewPageChange = (pageNumber)=>{
setCurrentEditorViewPageNum(pageNumber);
};
const handleEditorCursorPageChange = (pageNumber)=>{
setCurrentEditorCursorPageNum(pageNumber);
};
const handleBrewRendererPageChange = (pageNumber)=>{
setCurrentBrewRendererPageNum(pageNumber);
};
const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata' const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata'
if (subfield == 'renderer' || subfield == 'theme') if (subfield == 'renderer' || subfield == 'theme')
fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme);
@@ -331,7 +322,7 @@ const EditPage = (props)=>{
const toggleAutoSave = ()=>{ const toggleAutoSave = ()=>{
clearTimeout(warnUnsavedTimeout.current); clearTimeout(warnUnsavedTimeout.current);
clearTimeout(saveTimeout.current); clearTimeout(saveTimeout.current);
localStorage.setItem('AUTOSAVE_ON', JSON.stringify(!autoSaveEnabled)); localStorage.setItem(AUTOSAVE_KEY, JSON.stringify(!autoSaveEnabled));
setAutoSaveEnabled(!autoSaveEnabled); setAutoSaveEnabled(!autoSaveEnabled);
setWarnUnsavedChanges(autoSaveEnabled); setWarnUnsavedChanges(autoSaveEnabled);
}; };
@@ -361,11 +352,11 @@ const EditPage = (props)=>{
{renderSaveButton()} {renderSaveButton()}
{renderAutoSaveButton()} {renderAutoSaveButton()}
</Nav.dropdown>} </Nav.dropdown>}
<NewBrewItem/> <NewBrewItem />
<HelpNavItem/>
<ShareNavItem brew={currentBrew} />
<PrintNavItem /> <PrintNavItem />
<HelpNavItem />
<VaultNavItem /> <VaultNavItem />
<ShareNavItem brew={currentBrew} />
<RecentNavItem brew={currentBrew} storageKey='edit' /> <RecentNavItem brew={currentBrew} storageKey='edit' />
<AccountNavItem/> <AccountNavItem/>
</Nav.section> </Nav.section>
@@ -391,8 +382,8 @@ const EditPage = (props)=>{
userThemes={props.userThemes} userThemes={props.userThemes}
themeBundle={themeBundle} themeBundle={themeBundle}
updateBrew={updateBrew} updateBrew={updateBrew}
onCursorPageChange={handleEditorCursorPageChange} onCursorPageChange={setCurrentEditorCursorPageNum}
onViewPageChange={handleEditorViewPageChange} onViewPageChange={setCurrentEditorViewPageNum}
currentEditorViewPageNum={currentEditorViewPageNum} currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}
@@ -405,7 +396,7 @@ const EditPage = (props)=>{
themeBundle={themeBundle} themeBundle={themeBundle}
errors={HTMLErrors} errors={HTMLErrors}
lang={currentBrew.lang} lang={currentBrew.lang}
onPageChange={handleBrewRendererPageChange} onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum} currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}

View File

@@ -1,32 +1,38 @@
/* eslint-disable max-lines */
import './homePage.less'; import './homePage.less';
import React from 'react'; // Common imports
import { useEffect, useState, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
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 { Meta } from 'vitreum/headtags';
import Nav from 'naturalcrit/nav/nav.jsx'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
import Navbar from '../../navbar/navbar.jsx'; import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
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';
import SplitPane from 'client/components/splitPane/splitPane.jsx'; import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx'; import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
// Page specific imports
import { Meta } from 'vitreum/headtags';
const BREWKEY = 'homebrewery-new'; const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style'; const STYLEKEY = 'homebrewery-new-style';
const SNIPKEY = 'homebrewery-new-snippets'; const SNIPKEY = 'homebrewery-new-snippets';
const METAKEY = 'homebrewery-new-meta'; const METAKEY = 'homebrewery-new-meta';
const useLocalStorage = false;
const HomePage =(props)=>{ const HomePage =(props)=>{
props = { props = {
brew : DEFAULT_BREW, brew : DEFAULT_BREW,
@@ -37,7 +43,7 @@ const HomePage =(props)=>{
const [currentBrew , setCurrentBrew] = useState(props.brew); const [currentBrew , setCurrentBrew] = useState(props.brew);
const [welcomeText , setWelcomeText] = useState(props.brew.text); const [welcomeText , setWelcomeText] = useState(props.brew.text);
const [error , setError] = useState(undefined); const [error , setError] = useState(undefined);
const [HTMLErrors , setHTMLErrors ] = useState(Markdown.validate(props.brew.text)); const [HTMLErrors , setHTMLErrors] = useState(Markdown.validate(props.brew.text));
const [currentEditorViewPageNum , setCurrentEditorViewPageNum] = useState(1); const [currentEditorViewPageNum , setCurrentEditorViewPageNum] = useState(1);
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1); const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1); const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
@@ -46,10 +52,24 @@ const HomePage =(props)=>{
const editorRef = useRef(null); const editorRef = useRef(null);
const useLocalStorage = false;
useEffect(()=>{ useEffect(()=>{
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
if(e.keyCode === 83) trySaveRef.current(true);
if(e.keyCode === 80) printCurrentBrew();
if([83, 80].includes(e.keyCode)) {
e.stopPropagation();
e.preventDefault();
}
};
document.addEventListener('keydown', handleControlKeys);
return () => {
document.removeEventListener('keydown', handleControlKeys);
};
}, []); }, []);
const save = ()=>{ const save = ()=>{
@@ -69,18 +89,6 @@ const HomePage =(props)=>{
editorRef.current.update(); editorRef.current.update();
}; };
const handleEditorViewPageChange = (pageNumber)=>{
setCurrentEditorViewPageNum(pageNumber);
};
const handleEditorCursorPageChange = (pageNumber)=>{
setCurrentEditorCursorPageNum(pageNumber);
};
const handleBrewRendererPageChange = (pageNumber)=>{
setCurrentBrewRendererPageNum(pageNumber);
};
const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata' const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata'
if (subfield == 'renderer' || subfield == 'theme') if (subfield == 'renderer' || subfield == 'theme')
fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme);
@@ -117,6 +125,7 @@ const HomePage =(props)=>{
null null
} }
<NewBrewItem /> <NewBrewItem />
<PrintNavItem />
<HelpNavItem /> <HelpNavItem />
<VaultNavItem /> <VaultNavItem />
<RecentNavItem /> <RecentNavItem />
@@ -138,8 +147,8 @@ const HomePage =(props)=>{
renderer={currentBrew.renderer} renderer={currentBrew.renderer}
showEditButtons={false} showEditButtons={false}
themeBundle={themeBundle} themeBundle={themeBundle}
onCursorPageChange={handleEditorCursorPageChange} onCursorPageChange={setCurrentEditorCursorPageNum}
onViewPageChange={handleEditorViewPageChange} onViewPageChange={setCurrentEditorViewPageNum}
currentEditorViewPageNum={currentEditorViewPageNum} currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}
@@ -148,7 +157,7 @@ const HomePage =(props)=>{
text={currentBrew.text} text={currentBrew.text}
style={currentBrew.style} style={currentBrew.style}
renderer={currentBrew.renderer} renderer={currentBrew.renderer}
onPageChange={handleBrewRendererPageChange} onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum} currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}

View File

@@ -1,30 +1,40 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ /* eslint-disable max-lines */
import './newPage.less'; import './newPage.less';
// Common imports
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
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'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
import Navbar from '../../navbar/navbar.jsx'; import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
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'; import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx'; import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import Nav from 'naturalcrit/nav/nav.jsx';
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js'; import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
const BREWKEY = 'homebrewery-new'; // Page specific imports
const STYLEKEY = 'homebrewery-new-style'; import { Meta } from 'vitreum/headtags';
const SNIPKEY = 'homebrewery-new-snippets';
const METAKEY = 'homebrewery-new-meta';
const SAVEKEYPREFIX = 'HOMEBREWERY-DEFAULT-SAVE-LOCATION-'; const BREWKEY = 'HB_newPage_content';
const STYLEKEY = 'HB_newPage_style';
const METAKEY = 'HB_newPage_metadata';
const SNIPKEY = 'HB_newPage_snippets';
const SAVEKEYPREFIX = 'HB_editor_defaultSave_';
const useLocalStorage = true;
const NewPage = (props) => { const NewPage = (props) => {
props = { props = {
@@ -44,13 +54,22 @@ const NewPage = (props) => {
const editorRef = useRef(null); const editorRef = useRef(null);
const useLocalStorage = true;
useEffect(() => { useEffect(() => {
document.addEventListener('keydown', handleControlKeys);
loadBrew(); loadBrew();
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
if(e.keyCode === 83) trySaveRef.current(true);
if(e.keyCode === 80) printCurrentBrew();
if([83, 80].includes(e.keyCode)) {
e.stopPropagation();
e.preventDefault();
}
};
document.addEventListener('keydown', handleControlKeys);
return () => { return () => {
document.removeEventListener('keydown', handleControlKeys); document.removeEventListener('keydown', handleControlKeys);
}; };
@@ -84,34 +103,10 @@ const NewPage = (props) => {
window.history.replaceState({}, window.location.title, '/new/'); window.history.replaceState({}, window.location.title, '/new/');
}; };
const handleControlKeys = (e) => {
if (!(e.ctrlKey || e.metaKey)) return;
const S_KEY = 83;
const P_KEY = 80;
if (e.keyCode === S_KEY) save();
if (e.keyCode === P_KEY) printCurrentBrew();
if (e.keyCode === S_KEY || e.keyCode === P_KEY) {
e.preventDefault();
e.stopPropagation();
}
};
const handleSplitMove = ()=>{ const handleSplitMove = ()=>{
editorRef.current.update(); editorRef.current.update();
}; };
const handleEditorViewPageChange = (pageNumber)=>{
setCurrentEditorViewPageNum(pageNumber);
};
const handleEditorCursorPageChange = (pageNumber)=>{
setCurrentEditorCursorPageNum(pageNumber);
};
const handleBrewRendererPageChange = (pageNumber)=>{
setCurrentBrewRendererPageNum(pageNumber);
};
const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata' const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata'
if (subfield == 'renderer' || subfield == 'theme') if (subfield == 'renderer' || subfield == 'theme')
fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme);
@@ -190,8 +185,10 @@ const NewPage = (props) => {
{error {error
? <ErrorNavItem error={error} clearError={clearError} /> ? <ErrorNavItem error={error} clearError={clearError} />
: renderSaveButton()} : renderSaveButton()}
<NewBrewItem />
<PrintNavItem /> <PrintNavItem />
<HelpNavItem /> <HelpNavItem />
<VaultNavItem />
<RecentNavItem /> <RecentNavItem />
<AccountNavItem /> <AccountNavItem />
</Nav.section> </Nav.section>
@@ -210,8 +207,8 @@ const NewPage = (props) => {
renderer={currentBrew.renderer} renderer={currentBrew.renderer}
userThemes={props.userThemes} userThemes={props.userThemes}
themeBundle={themeBundle} themeBundle={themeBundle}
onCursorPageChange={handleEditorCursorPageChange} onCursorPageChange={setCurrentEditorCursorPageNum}
onViewPageChange={handleEditorViewPageChange} onViewPageChange={setCurrentEditorViewPageNum}
currentEditorViewPageNum={currentEditorViewPageNum} currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}
@@ -224,7 +221,7 @@ const NewPage = (props) => {
themeBundle={themeBundle} themeBundle={themeBundle}
errors={HTMLErrors} errors={HTMLErrors}
lang={currentBrew.lang} lang={currentBrew.lang}
onPageChange={handleBrewRendererPageChange} onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum} currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}

View File

@@ -0,0 +1,35 @@
const getLocalStorageMap = function(){
const localStorageMap = {
'AUTOSAVE_ON' : 'HB_editor_autoSaveOn',
'HOMEBREWERY-EDITOR-THEME' : 'HB_editor_theme',
'liveScroll' : 'HB_editor_liveScroll',
'naturalcrit-pane-split' : 'HB_editor_splitWidth',
'HOMEBREWERY-LISTPAGE-SORTDIR' : 'HB_listPage_sortDir',
'HOMEBREWERY-LISTPAGE-SORTTYPE' : 'HB_listPage_sortType',
'HOMEBREWERY-LISTPAGE-VISIBILITY-published' : 'HB_listPage_visibility_group_published',
'HOMEBREWERY-LISTPAGE-VISIBILITY-unpublished' : 'HB_listPage_visibility_group_unpublished',
'hbAdminTab' : 'HB_adminPage_currentTab',
'homebrewery-new' : 'HB_newPage_content',
'homebrewery-new-meta' : 'HB_newPage_metadata',
'homebrewery-new-style' : 'HB_newPage_style',
'homebrewery-recently-edited' : 'HB_nav_recentlyEdited',
'homebrewery-recently-viewed' : 'HB_nav_recentlyViewed',
'hb_toolbarState' : 'HB_renderer_toolbarState',
'hb_toolbarVisibility' : 'HB_renderer_toolbarVisibility'
};
if(global?.account?.username){
const username = global.account.username;
localStorageMap[`HOMEBREWERY-DEFAULT-SAVE-LOCATION-${username}`] = `HB_editor_defaultSave_${username}`;
}
return localStorageMap;
};
export default getLocalStorageMap;

View File

@@ -0,0 +1,30 @@
import getLocalStorageMap from './localStorageKeyMap.js';
describe('getLocalStorageMap', ()=>{
it('no username', ()=>{
const account = global.account;
delete global.account;
const map = getLocalStorageMap();
global.account = account;
expect(map).toBeInstanceOf(Object);
expect(Object.entries(map)).toHaveLength(16);
});
it('no username', ()=>{
const account = global.account;
global.account = { username: 'test' };
const map = getLocalStorageMap();
global.account = account;
expect(map).toBeInstanceOf(Object);
expect(Object.entries(map)).toHaveLength(17);
expect(map).toHaveProperty('HOMEBREWERY-DEFAULT-SAVE-LOCATION-test', 'HB_editor_defaultSave_test');
});
});

View File

@@ -0,0 +1,25 @@
import getLocalStorageMap from './localStorageKeyMap.js';
const updateLocalStorage = function(){
// Return if no window and thus no local storage
if(typeof window === 'undefined') return;
// Return if the local storage key map has no content
const localStorageKeyMap = getLocalStorageMap();
if(Object.keys(localStorageKeyMap).length == 0) return;
const storage = window.localStorage;
Object.keys(localStorageKeyMap).forEach((key)=>{
if(storage[key]){
if(!storage[localStorageKeyMap[key]]){
const data = storage.getItem(key);
storage.setItem(localStorageKeyMap[key], data);
};
storage.removeItem(key);
}
});
};
export { updateLocalStorage };