Merge branch 'master' into fix-deletion-of-orphan-stubs

This commit is contained in:
Víctor Losada Hernández
2026-09-20 15:11:23 +02:00
committed by GitHub
10 changed files with 128 additions and 273 deletions
+26 -40
View File
@@ -23,7 +23,6 @@ import safeHTML from './safeHTML.js';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m; 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 TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState'; const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState';
@@ -47,31 +46,25 @@ const BrewPage = (props)=>{
}; };
const pageRef = useRef(null); const pageRef = useRef(null);
const cleanText = safeHTML(props.contents); const cleanText = safeHTML(props.contents);
const pageNum = props.index + 1;
useEffect(()=>{ useEffect(()=>{
if(!pageRef.current) return; if(!pageRef.current) return;
// Observer for tracking pages within the `.pages` div // Observer for tracking which pages are at least 30% visible in the iframe
const visibleObserver = new IntersectionObserver( const visibleObserver = new IntersectionObserver(
(entries)=>{ (entries)=>entries.forEach((entry)=>{
entries.forEach((entry)=>{ props.onVisibilityChange(pageNum, entry.isIntersecting, false); // add page to array of visible pages.
if(entry.isIntersecting) }),
props.onVisibilityChange(props.index + 1, true, false); // add page to array of visible pages.
else
props.onVisibilityChange(props.index + 1, false, false);
});
},
{ threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds. { threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds.
); );
// Observer for tracking the page at the center of the iframe. // Observer for tracking the page at the center of the iframe.
const centerObserver = new IntersectionObserver( const centerObserver = new IntersectionObserver(
(entries)=>{ (entries)=>entries.forEach((entry)=>{
entries.forEach((entry)=>{ if(entry.isIntersecting)
if(entry.isIntersecting) props.onVisibilityChange(pageNum, true, true); // Set this page as the center page
props.onVisibilityChange(props.index + 1, true, true); // Set this page as the center page }),
});
},
{ threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center { threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center
); );
@@ -92,7 +85,7 @@ const BrewPage = (props)=>{
//v=====--------------------< Brew Renderer Component >-------------------=====v// //v=====--------------------< Brew Renderer Component >-------------------=====v//
let renderedPages = []; let renderedPages = [];
let pageTemplates = []; const pageTemplates = [];
let rawPages = []; let rawPages = [];
const BrewRenderer = (props)=>{ const BrewRenderer = (props)=>{
@@ -100,22 +93,21 @@ const BrewRenderer = (props)=>{
text : '', text : '',
style : '', style : '',
renderer : 'legacy', renderer : 'legacy',
theme : '5ePHB',
lang : '', lang : '',
errors : [], errors : [],
currentEditorCursorPageNum : 1, currentEditorCursorPageNum : 1,
currentEditorViewPageNum : 1,
currentBrewRendererPageNum : 1, currentBrewRendererPageNum : 1,
themeBundle : {}, themeBundle : {},
onPageChange : ()=>{}, onPageChange : ()=>{},
...props ...props
}; };
const [visiblePages, setVisiblePages] = useState([]);
const [centerPage , setCenterPage ] = useState(1);
const [state, setState] = useState({ const [state, setState] = useState({
isMounted : false, isMounted : false,
visibility : 'hidden', visibility : 'hidden'
visiblePages : [],
centerPage : 1
}); });
const [displayOptions, setDisplayOptions] = useState({ const [displayOptions, setDisplayOptions] = useState({
@@ -135,9 +127,7 @@ const BrewRenderer = (props)=>{
const [headerState, setHeaderState] = useState(false); const [headerState, setHeaderState] = useState(false);
const mainRef = useRef(null);
const pagesRef = useRef(null); const pagesRef = useRef(null);
const urlRef = useRef('');
if(props.renderer == 'legacy') { if(props.renderer == 'legacy') {
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY); rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
@@ -146,20 +136,16 @@ const BrewRenderer = (props)=>{
} }
const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{ const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{
setState((prevState)=>{ setVisiblePages((prev)=>{
const updatedVisiblePages = new Set(prevState.visiblePages); const updatedVisiblePages = new Set(prev);
if(!isCenter) isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum);
isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum); return [...updatedVisiblePages].sort((a, b)=>a - b);
return {
...prevState,
visiblePages : [...updatedVisiblePages].sort((a, b)=>a - b),
centerPage : isCenter ? pageNum : prevState.centerPage
};
}); });
if(isCenter) if(isCenter) {
setCenterPage(pageNum);
props.onPageChange(pageNum); props.onPageChange(pageNum);
}
}; };
const isInView = (index)=>{ const isInView = (index)=>{
@@ -341,19 +327,19 @@ const BrewRenderer = (props)=>{
: null} : null}
<ErrorBar errors={props.errors} /> <ErrorBar errors={props.errors} />
<div className='popups' ref={mainRef}> <div className='popups'>
<RenderWarnings /> <RenderWarnings />
<NotificationPopup /> <NotificationPopup />
</div> </div>
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/> <ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={visiblePages.length > 0 ? visiblePages : [centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
{/*render in iFrame so broken code doesn't crash the site.*/} {/*render in iFrame so broken code doesn't crash the site.*/}
<Frame id='BrewRenderer' title="Rendered Brew Content" initialContent={INITIAL_CONTENT} <Frame id='BrewRenderer' title='Rendered Brew Content' initialContent={INITIAL_CONTENT}
style={{ width: '100%', height: '100%', visibility: state.visibility }} style={{ width: '100%', height: '100%', visibility: state.visibility }}
contentDidMount={frameDidMount} contentDidMount={frameDidMount}
onClick={()=>{emitClick();}} onClick={()=>{emitClick();}}
sandbox="allow-same-origin allow-modals allow-top-navigation" sandbox='allow-same-origin allow-modals allow-top-navigation'
> >
<div className='brewRenderer' <div className='brewRenderer'
onKeyDown={handleControlKeys} onKeyDown={handleControlKeys}
+10 -16
View File
@@ -1,34 +1,28 @@
import './navbar.less'; import './navbar.less';
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import Nav from './nav.jsx'; import Nav from './nav.jsx';
import PatreonNavItem from './patreon.navitem.jsx'; import PatreonNavItem from './patreon.navitem.jsx';
const Navbar = createReactClass({ const Navbar = ({ children })=>{
displayName : 'Navbar', const version = global.version || '0.0.0';
getInitialState : function() {
return {
ver : global.version || '0.0.0'
};
},
render : function(){ return (
return <Nav.base> <Nav.base>
<Nav.section> <Nav.section>
<Nav.logo /> <Nav.logo />
<Nav.item href='/' className='homebrewLogo'> <Nav.item href='/' className='homebrewLogo'>
<div>The Homebrewery</div> <div>The Homebrewery</div>
</Nav.item> </Nav.item>
<Nav.item newTab={true} href='/changelog' color='purple' icon='far fa-file-alt'> <Nav.item newTab={true} href='/changelog' color='purple' icon='far fa-file-alt'>
{`v${this.state.ver}`} {`v${version}`}
</Nav.item> </Nav.item>
<PatreonNavItem /> <PatreonNavItem />
{/*this.renderChromeWarning()*/} {/* this.renderChromeWarning() */}
</Nav.section> </Nav.section>
{this.props.children} {children}
</Nav.base>; </Nav.base>
} );
}); };
export default Navbar; export default Navbar;
+4 -58
View File
@@ -9,7 +9,7 @@ import _ from 'lodash';
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js'; import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.js' import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx'
import SplitPane from '@components/splitPane/splitPane.jsx'; import SplitPane from '@components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx'; import Editor from '../../editor/editor.jsx';
@@ -53,8 +53,6 @@ const EditPage = (props)=>{
}; };
const [currentBrew, setCurrentBrew] = useState(props.brew); const [currentBrew, setCurrentBrew] = useState(props.brew);
const [isSaving, setIsSaving] = useState(false);
const [lastSavedTime, setLastSavedTime] = useState(new Date());
const [saveGoogle, setSaveGoogle] = useState(!!props.brew.googleId); const [saveGoogle, setSaveGoogle] = useState(!!props.brew.googleId);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text)); const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
@@ -62,13 +60,10 @@ const EditPage = (props)=>{
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1); const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1); const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [themeBundle, setThemeBundle] = useState({}); const [themeBundle, setThemeBundle] = useState({});
const [unsavedChanges, setUnsavedChanges] = useState(false);
const [alertTrashedGoogleBrew, setAlertTrashedGoogleBrew] = useState(props.brew.trashed); const [alertTrashedGoogleBrew, setAlertTrashedGoogleBrew] = useState(props.brew.trashed);
const [alertNoGoogleToTransfer, setAlertNoGoogleToTransfer] = useState(false); const [alertNoGoogleToTransfer, setAlertNoGoogleToTransfer] = useState(false);
const [alertOwnershipToTransfer, setAlertOwnershipToTransfer] = useState(false); const [alertOwnershipToTransfer, setAlertOwnershipToTransfer] = useState(false);
const [confirmGoogleTransfer, setConfirmGoogleTransfer] = useState(false); const [confirmGoogleTransfer, setConfirmGoogleTransfer] = useState(false);
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true);
const editorRef = useRef(null); const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew)); const lastSavedBrew = useRef(_.cloneDeep(props.brew));
@@ -206,52 +201,12 @@ const EditPage = (props)=>{
</Nav.item> </Nav.item>
); );
const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING
if(isSaving)
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
if(unsavedChanges && warnUnsavedChanges) {
resetWarnUnsavedTimer();
const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
const text = elapsedTime === 0
? `Autosave is OFF${sandbox ? ' for this sandbox page' : ''}.`
: `Autosave is OFF${sandbox ? ' for this sandbox page' : ''}, and you haven't saved for ${elapsedTime} minutes.`;
return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
Reminder...
<div className='errorContainer'>{text}</div>
</Nav.item>;
}
// #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges)
return <Nav.item className='save' onClick={()=>trySave(true, true, saveGoogle)} color='blue' icon='fas fa-save'>save now</Nav.item>;
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>;
// #5 - Sandbox with no unsaved changes, and has never been saved, hide the button
if(sandbox)
return <Nav.item className='save sandbox' disabled={true}>save now</Nav.item>;
// DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved</Nav.item>;
};
const renderAutoSaveButton = ()=>( const renderAutoSaveButton = ()=>(
<Nav.item onClick={toggleAutoSave}> <Nav.item onClick={toggleAutoSave}>
Autosave <i className={autoSaveEnabled ? 'fas fa-power-off active' : 'fas fa-power-off'}></i> Autosave <i className={autoSaveEnabled ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
</Nav.item> </Nav.item>
); );
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
const renderNavbar = ()=>{ const renderNavbar = ()=>{
return <Navbar> return <Navbar>
<Nav.section> <Nav.section>
@@ -278,10 +233,12 @@ const EditPage = (props)=>{
}; };
const { const {
resetWarnUnsavedTimer,
handleSplitMove, handleSplitMove,
handleBrewChange, handleBrewChange,
toggleAutoSave, toggleAutoSave,
clearError,
renderSaveButton,
autoSaveEnabled,
trySave trySave
} = useCommonEditPageFunctions({ } = useCommonEditPageFunctions({
saveGoogle, saveGoogle,
@@ -297,19 +254,10 @@ const EditPage = (props)=>{
SNIPKEY, SNIPKEY,
METAKEY, METAKEY,
hbfm, hbfm,
autoSaveEnabled,
setAutoSaveEnabled,
setWarnUnsavedChanges,
unsavedChanges,
setUnsavedChanges,
sandbox, sandbox,
lastSavedBrew, lastSavedBrew,
editorRef, editorRef,
isSaving,
setIsSaving,
save, save,
lastSavedTime,
setLastSavedTime
}); });
return ( return (
@@ -341,12 +289,10 @@ const EditPage = (props)=>{
text={currentBrew.text} text={currentBrew.text}
style={currentBrew.style} style={currentBrew.style}
renderer={currentBrew.renderer} renderer={currentBrew.renderer}
theme={currentBrew.theme}
themeBundle={themeBundle} themeBundle={themeBundle}
errors={HTMLErrors} errors={HTMLErrors}
lang={currentBrew.lang} lang={currentBrew.lang}
onPageChange={setCurrentBrewRendererPageNum} onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true} allowPrint={true}
+5 -58
View File
@@ -9,7 +9,7 @@ import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.js' import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx'
import SplitPane from '@components/splitPane/splitPane.jsx'; import SplitPane from '@components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx'; import Editor from '../../editor/editor.jsx';
@@ -53,11 +53,6 @@ const HomePage =(props)=>{
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1); const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1); const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [themeBundle, setThemeBundle] = useState({}); const [themeBundle, setThemeBundle] = useState({});
const [unsavedChanges, setUnsavedChanges] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [lastSavedTime, setLastSavedTime] = useState(new Date());
const [autoSaveEnabled, setAutoSaveEnabled] = useState(false);
const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true);
const editorRef = useRef(null); const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew)); const lastSavedBrew = useRef(_.cloneDeep(props.brew));
@@ -77,46 +72,6 @@ const HomePage =(props)=>{
window.location = `/edit/${saved.editId}`; window.location = `/edit/${saved.editId}`;
}; };
const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING
if(isSaving)
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
if(unsavedChanges && warnUnsavedChanges) {
resetWarnUnsavedTimer();
const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
const text = elapsedTime === 0
? `Autosave is OFF${sandbox ? ' for this sandbox page' : ''}.`
: `Autosave is OFF${sandbox ? ' for this sandbox page' : ''}, and you haven't saved for ${elapsedTime} minutes.`;
return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
Reminder...
<div className='errorContainer'>{text}</div>
</Nav.item>;
}
// #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges)
return <Nav.item className='save' onClick={()=>trySave(true, true, saveGoogle)} color='blue' icon='fas fa-save'>save now</Nav.item>;
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>;
// #5 - Sandbox with no unsaved changes, and has never been saved, hide the button
if(sandbox)
return <Nav.item className='save neverSaved' disabled={true}>save now</Nav.item>;
// DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved</Nav.item>;
};
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
const renderNavbar = ()=>{ const renderNavbar = ()=>{
return <Navbar ver={props.ver}> return <Navbar ver={props.ver}>
<Nav.section> <Nav.section>
@@ -134,9 +89,11 @@ const HomePage =(props)=>{
}; };
const { const {
resetWarnUnsavedTimer,
handleSplitMove, handleSplitMove,
handleBrewChange, handleBrewChange,
clearError,
renderSaveButton,
unsavedChanges,
trySave trySave
} = useCommonEditPageFunctions({ } = useCommonEditPageFunctions({
saveGoogle, saveGoogle,
@@ -152,19 +109,10 @@ const HomePage =(props)=>{
SNIPKEY, SNIPKEY,
METAKEY, METAKEY,
hbfm, hbfm,
autoSaveEnabled,
setAutoSaveEnabled,
setWarnUnsavedChanges,
unsavedChanges,
setUnsavedChanges,
sandbox, sandbox,
lastSavedBrew, lastSavedBrew,
editorRef, editorRef,
isSaving,
setIsSaving,
save, save,
lastSavedTime,
setLastSavedTime
}); });
return ( return (
@@ -190,11 +138,10 @@ const HomePage =(props)=>{
text={currentBrew.text} text={currentBrew.text}
style={currentBrew.style} style={currentBrew.style}
renderer={currentBrew.renderer} renderer={currentBrew.renderer}
themeBundle={themeBundle}
onPageChange={setCurrentBrewRendererPageNum} onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}
themeBundle={themeBundle}
/> />
</SplitPane> </SplitPane>
</div> </div>
+3 -59
View File
@@ -10,7 +10,7 @@ import _ from 'lodash';
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '@shared/helpers.js'; import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '@shared/helpers.js';
import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.js' import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.jsx'
import SplitPane from '@components/splitPane/splitPane.jsx'; import SplitPane from '@components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx'; import Editor from '../../editor/editor.jsx';
@@ -45,8 +45,6 @@ const NewPage = (props)=>{
}; };
const [currentBrew, setCurrentBrew] = useState(props.brew); const [currentBrew, setCurrentBrew] = useState(props.brew);
const [isSaving, setIsSaving] = useState(false);
const [lastSavedTime, setLastSavedTime] = useState(new Date());
const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false); const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text)); const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text));
@@ -54,9 +52,6 @@ const NewPage = (props)=>{
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1); const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1); const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [themeBundle, setThemeBundle] = useState({}); const [themeBundle, setThemeBundle] = useState({});
const [unsavedChanges, setUnsavedChanges] = useState(false);
const [autoSaveEnabled, setAutoSaveEnabled] = useState(false);
const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true);
const editorRef = useRef(null); const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew)); const lastSavedBrew = useRef(_.cloneDeep(props.brew));
@@ -121,46 +116,6 @@ const NewPage = (props)=>{
window.location = `/edit/${savedBrew.editId}`; window.location = `/edit/${savedBrew.editId}`;
}; };
const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING
if(isSaving)
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
if(unsavedChanges && warnUnsavedChanges) {
resetWarnUnsavedTimer();
const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
const text = elapsedTime === 0
? `Autosave is OFF${sandbox ? ' for this sandbox page' : ''}.`
: `Autosave is OFF${sandbox ? ' for this sandbox page' : ''}, and you haven't saved for ${elapsedTime} minutes.`;
return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
Reminder...
<div className='errorContainer'>{text}</div>
</Nav.item>;
}
// #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges)
return <Nav.item className='save' onClick={()=>trySave(true, true, saveGoogle)} color='blue' icon='fas fa-save'>save now</Nav.item>;
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>;
// #5 - Sandbox with no unsaved changes, and has never been saved, hide the button
if(sandbox)
return <Nav.item className='save neverSaved' disabled={true}>save now</Nav.item>;
// DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved</Nav.item>;
};
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
const renderNavbar = ()=>( const renderNavbar = ()=>(
<Navbar> <Navbar>
<Nav.section> <Nav.section>
@@ -182,10 +137,10 @@ const NewPage = (props)=>{
); );
const { const {
resetWarnUnsavedTimer,
handleSplitMove, handleSplitMove,
handleBrewChange, handleBrewChange,
trySave clearError,
renderSaveButton
} = useCommonEditPageFunctions({ } = useCommonEditPageFunctions({
saveGoogle, saveGoogle,
setError, setError,
@@ -200,19 +155,10 @@ const NewPage = (props)=>{
SNIPKEY, SNIPKEY,
METAKEY, METAKEY,
hbfm, hbfm,
autoSaveEnabled,
setAutoSaveEnabled,
setWarnUnsavedChanges,
unsavedChanges,
setUnsavedChanges,
sandbox, sandbox,
lastSavedBrew, lastSavedBrew,
editorRef, editorRef,
isSaving,
setIsSaving,
save, save,
lastSavedTime,
setLastSavedTime
}); });
return ( return (
@@ -237,12 +183,10 @@ const NewPage = (props)=>{
text={currentBrew.text} text={currentBrew.text}
style={currentBrew.style} style={currentBrew.style}
renderer={currentBrew.renderer} renderer={currentBrew.renderer}
theme={currentBrew.theme}
themeBundle={themeBundle} themeBundle={themeBundle}
errors={HTMLErrors} errors={HTMLErrors}
lang={currentBrew.lang} lang={currentBrew.lang}
onPageChange={setCurrentBrewRendererPageNum} onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true} allowPrint={true}
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useEffectEvent, useRef } from 'react'; import React, { useState, useEffect, useEffectEvent, useRef } from 'react';
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js'; import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
import _ from 'lodash'; import _ from 'lodash';
import Nav from '@navbar/nav.jsx';
const AUTOSAVE_KEY = 'HB_editor_autoSaveOn'; const AUTOSAVE_KEY = 'HB_editor_autoSaveOn';
@@ -23,21 +24,18 @@ export default function useCommonEditPageFunctions(dependencies) {
SNIPKEY, SNIPKEY,
METAKEY, METAKEY,
hbfm, hbfm,
autoSaveEnabled,
setAutoSaveEnabled,
setWarnUnsavedChanges,
sandbox, sandbox,
unsavedChanges,
setUnsavedChanges,
lastSavedBrew, lastSavedBrew,
editorRef, editorRef,
isSaving,
setIsSaving,
save, save,
lastSavedTime,
setLastSavedTime
} = dependencies; } = dependencies;
const [isSaving , setIsSaving] = useState(false);
const [lastSavedTime , setLastSavedTime] = useState(new Date());
const [autoSaveEnabled , setAutoSaveEnabled] = useState(!sandbox);
const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true);
const [unsavedChanges , setUnsavedChanges] = useState(false);
const unsavedChangesRef = useRef(unsavedChanges); // onBeforeUnload lives outside React and needs ref to unsavedChanges const unsavedChangesRef = useRef(unsavedChanges); // onBeforeUnload lives outside React and needs ref to unsavedChanges
const warnUnsavedTimeout = useRef(null); // timers live outside React and need ref to consistently track time const warnUnsavedTimeout = useRef(null); // timers live outside React and need ref to consistently track time
const saveTimeout = useRef(null); const saveTimeout = useRef(null);
@@ -59,7 +57,6 @@ export default function useCommonEditPageFunctions(dependencies) {
e.preventDefault(); e.preventDefault();
} }
}; };
document.addEventListener('keydown', handleControlKeys); document.addEventListener('keydown', handleControlKeys);
window.onbeforeunload = ()=>{ window.onbeforeunload = ()=>{
if(unsavedChangesRef.current) if(unsavedChangesRef.current)
@@ -121,6 +118,11 @@ export default function useCommonEditPageFunctions(dependencies) {
setWarnUnsavedChanges(autoSaveEnabled); setWarnUnsavedChanges(autoSaveEnabled);
}; };
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
const trySave = useEffectEvent((forceSave = false, hasChanges = true, saveToGoogle = false)=>{ const trySave = useEffectEvent((forceSave = false, hasChanges = true, saveToGoogle = false)=>{
clearTimeout(saveTimeout.current); clearTimeout(saveTimeout.current);
if(isSaving) return; if(isSaving) return;
@@ -140,11 +142,43 @@ export default function useCommonEditPageFunctions(dependencies) {
}, newTimeout); }, newTimeout);
}); });
const renderSaveButton = ()=>{
if(isSaving)
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
if(unsavedChanges && warnUnsavedChanges) {
resetWarnUnsavedTimer();
const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
const text = elapsedTime === 0
? `Autosave is OFF${sandbox ? ' for this sandbox page' : ''}.`
: `Autosave is OFF${sandbox ? ' for this sandbox page' : ''}, and you haven't saved for ${elapsedTime} minutes.`;
return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
Reminder...
<div className='errorContainer'>{text}</div>
</Nav.item>;
}
if(unsavedChanges)
return <Nav.item className='save' onClick={()=>trySave(true, true, saveGoogle)} color='blue' icon='fas fa-save'>save now</Nav.item>;
if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>;
if(sandbox)
return <Nav.item className='save neverSaved' disabled={true}>save now</Nav.item>;
return <Nav.item className='save saved'>saved</Nav.item>;
};
return { return {
resetWarnUnsavedTimer,
handleSplitMove, handleSplitMove,
handleBrewChange, handleBrewChange,
toggleAutoSave, toggleAutoSave,
clearError,
trySave, trySave,
renderSaveButton,
autoSaveEnabled,
unsavedChanges,
} }
} }
+1
View File
@@ -14,6 +14,7 @@
.note table tbody tr:nth-child(odd) { background : #FFFFFF; } .note table tbody tr:nth-child(odd) { background : #FFFFFF; }
/* DROP CAP */ /* DROP CAP */
.first-letter, .drop-cap
h1 + p::first-letter { h1 + p::first-letter {
color : black; color : black;
background-image : unset; background-image : unset;
+1
View File
@@ -30,6 +30,7 @@ export default [
name : 'Tweak Drop Cap', name : 'Tweak Drop Cap',
icon : 'fas fa-sliders-h', icon : 'fas fa-sliders-h',
gen : dedent`/* Drop Cap settings */ gen : dedent`/* Drop Cap settings */
.page .first-letter, .page .drop-cap,
.page h1 + p::first-letter { .page h1 + p::first-letter {
font-family: SolberaImitationRemake; font-family: SolberaImitationRemake;
font-size: 3.5cm; font-size: 3.5cm;
+16 -15
View File
@@ -85,23 +85,24 @@
line-height : 1em; line-height : 1em;
-webkit-column-span : all; -webkit-column-span : all;
-moz-column-span : all; -moz-column-span : all;
& + p::first-letter {
float : left;
padding-bottom : 2px;
padding-left : 40px; //Allow background color to extend into margins
margin-top : -0.3cm;
margin-bottom : -20px;
margin-left : -40px;
font-family : 'SolberaImitationRemake';
font-size : 3.5cm;
line-height : 1em;
color : rgba(0, 0, 0, 0);
background-image : linear-gradient(-45deg, #322814, #998250, #322814);
-webkit-background-clip : text;
background-clip : text;
}
& + p::first-line { font-variant : small-caps; } & + p::first-line { font-variant : small-caps; }
} }
.first-letter, .drop-cap,
h1 + p::first-letter {
float : left;
padding-bottom : 2px;
padding-left : 40px; //Allow background color to extend into margins
margin-top : -0.3cm;
margin-bottom : -20px;
margin-left : -40px;
font-family : 'SolberaImitationRemake';
font-size : 3.5cm;
line-height : 1em;
color : rgba(0, 0, 0, 0);
background-image : linear-gradient(-45deg, #322814, #998250, #322814);
-webkit-background-clip : text;
background-clip : text;
}
h2 { h2 {
//margin-top : 0px; //Font is misaligned. Shift up slightly //margin-top : 0px; //Font is misaligned. Shift up slightly
//margin-bottom : 0.05cm; //margin-bottom : 0.05cm;
+14 -13
View File
@@ -80,21 +80,22 @@
font-size : 0.89cm; font-size : 0.89cm;
font-variant : small-caps; font-variant : small-caps;
line-height : 1em; line-height : 1em;
& + p::first-letter {
float : left;
padding-top : 0.3em;
padding-bottom : 2px;
padding-left : 40px; //Allow background color to extend into margins
margin-top : -0.3cm;
margin-right : 0.1em;
margin-bottom : -20px;
margin-left : -40px;
font-family : 'FrederickaTheGreat';
font-size : 1.9em;
line-height : 1em;
}
& + p::first-line { font-variant : small-caps; } & + p::first-line { font-variant : small-caps; }
} }
.first-letter, .drop-cap,
h1 + p::first-letter {
float : left;
padding-top : 0.3em;
padding-bottom : 2px;
padding-left : 40px; //Allow background color to extend into margins
margin-top : -0.3cm;
margin-right : 0.1em;
margin-bottom : -20px;
margin-left : -40px;
font-family : 'FrederickaTheGreat';
font-size : 1.9em;
line-height : 1em;
}
h2 { h2 {
font-size : 0.62cm; font-size : 0.62cm;
line-height : 0.988em; //Font is misaligned. Shift up slightly line-height : 0.988em; //Font is misaligned. Shift up slightly