Give "unsaved change" warning to NewPage and HomePage. Autosave is always disabled on those.

This commit is contained in:
Trevor Buckner
2026-09-09 21:08:58 +00:00
parent 06b5dbca23
commit 67728afd4f
3 changed files with 90 additions and 45 deletions
+10 -8
View File
@@ -40,7 +40,7 @@ import { updateHistory, versionHistoryGarbageCollection } from '../../utils/vers
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 = 90000; //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 AUTOSAVE_KEY = 'HB_editor_autoSaveOn'; const AUTOSAVE_KEY = 'HB_editor_autoSaveOn';
@@ -50,7 +50,7 @@ const SNIPKEY = 'HB_newPage_snippets';
const METAKEY = 'HB_newPage_meta'; const METAKEY = 'HB_newPage_meta';
const useLocalStorage = false; const useLocalStorage = false;
const neverSaved = false; const sandbox = false;
const EditPage = (props)=>{ const EditPage = (props)=>{
props = { props = {
@@ -101,7 +101,8 @@ const EditPage = (props)=>{
}); });
useEffect(()=>{ useEffect(()=>{
const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true); const autoSavePref = !sandbox && JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
setAutoSaveEnabled(autoSavePref); setAutoSaveEnabled(autoSavePref);
setWarnUnsavedChanges(!autoSavePref); setWarnUnsavedChanges(!autoSavePref);
setHTMLErrors(hbfm.validate(currentBrew.text)); setHTMLErrors(hbfm.validate(currentBrew.text));
@@ -122,6 +123,7 @@ const EditPage = (props)=>{
if(unsavedChangesRef.current) if(unsavedChangesRef.current)
return 'You have unsaved changes!'; return 'You have unsaved changes!';
}; };
return ()=>{ return ()=>{
document.removeEventListener('keydown', handleControlKeys); document.removeEventListener('keydown', handleControlKeys);
window.onbeforeunload = null; window.onbeforeunload = null;
@@ -313,8 +315,8 @@ const EditPage = (props)=>{
resetWarnUnsavedTimer(); resetWarnUnsavedTimer();
const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60); const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
const text = elapsedTime === 0 const text = elapsedTime === 0
? 'Autosave is OFF.' ? `Autosave is OFF${sandbox ?? 'for this sandbox page'}.`
: `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; : `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'> return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
Reminder... Reminder...
@@ -330,9 +332,9 @@ const EditPage = (props)=>{
if(autoSaveEnabled) if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>; return <Nav.item className='save saved'>auto-saved</Nav.item>;
// #5 - No unsaved changes, and has never been saved, hide the button // #5 - Sandbox with no unsaved changes, and has never been saved, hide the button
if(neverSaved) if(sandbox)
return <Nav.item className='save neverSaved' disabled={true}>save now</Nav.item>; return <Nav.item className='save sandbox' disabled={true}>save now</Nav.item>;
// DEFAULT - No unsaved changes, show SAVED // DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved</Nav.item>; return <Nav.item className='save saved'>saved</Nav.item>;
+37 -19
View File
@@ -32,13 +32,18 @@ const { both: RecentNavItem } = RecentNavItems;
import Headtags from '@vitreum/headtags.js'; import Headtags from '@vitreum/headtags.js';
const Meta = Headtags.Meta; const Meta = Headtags.Meta;
const BREWKEY = 'homebrewery-new'; const SAVE_TIMEOUT = 10000;
const STYLEKEY = 'homebrewery-new-style'; const UNSAVED_WARNING_TIMEOUT = 90000; //Warn user afer 15 minutes of unsaved changes
const SNIPKEY = 'homebrewery-new-snippets'; const UNSAVED_WARNING_POPUP_TIMEOUT = 4000; //Show the warning for 4 seconds
const METAKEY = 'homebrewery-new-meta';
const AUTOSAVE_KEY = 'HB_editor_autoSaveOn';
const BREWKEY = 'HB_newPage_content';
const STYLEKEY = 'HB_newPage_style';
const SNIPKEY = 'HB_newPage_snippets';
const METAKEY = 'HB_newPage_meta';
const useLocalStorage = false; const useLocalStorage = false;
const neverSaved = true; const sandbox = true;
const HomePage =(props)=>{ const HomePage =(props)=>{
props = { props = {
@@ -55,7 +60,8 @@ const HomePage =(props)=>{
const [themeBundle, setThemeBundle] = useState({}); const [themeBundle, setThemeBundle] = useState({});
const [unsavedChanges, setUnsavedChanges] = useState(false); const [unsavedChanges, setUnsavedChanges] = useState(false);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [autoSaveEnabled, setAutoSaveEnable] = 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));
@@ -79,6 +85,11 @@ const HomePage =(props)=>{
}); });
useEffect(()=>{ useEffect(()=>{
const autoSavePref = !sandbox && JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
setAutoSaveEnabled(autoSavePref);
setWarnUnsavedChanges(!autoSavePref);
setHTMLErrors(hbfm.validate(currentBrew.text));
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
const handleControlKeys = (e)=>{ const handleControlKeys = (e)=>{
@@ -96,6 +107,7 @@ const HomePage =(props)=>{
if(unsavedChangesRef.current) if(unsavedChangesRef.current)
return 'You have unsaved changes!'; return 'You have unsaved changes!';
}; };
return ()=>{ return ()=>{
document.removeEventListener('keydown', handleControlKeys); document.removeEventListener('keydown', handleControlKeys);
window.onbeforeunload = null; window.onbeforeunload = null;
@@ -130,24 +142,30 @@ const HomePage =(props)=>{
editorRef.current.update(); editorRef.current.update();
}; };
const resetWarnUnsavedTimer = ()=>{
setTimeout(()=>setWarnUnsavedChanges(false), UNSAVED_WARNING_POPUP_TIMEOUT); // Hide the warning after 4 seconds
clearTimeout(warnUnsavedTimeout.current);
warnUnsavedTimeout.current = setTimeout(()=>setWarnUnsavedChanges(true), UNSAVED_WARNING_TIMEOUT); // 15 minutes between unsaved work warnings
};
const renderSaveButton = ()=>{ const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING // #1 - Currently saving, show SAVING
if(isSaving) if(isSaving)
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>; 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 // #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
// if(unsavedChanges && warnUnsavedChanges) { if(unsavedChanges && warnUnsavedChanges) {
// resetWarnUnsavedTimer(); resetWarnUnsavedTimer();
// const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60); const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
// const text = elapsedTime === 0 const text = elapsedTime === 0
// ? 'Autosave is OFF.' ? `Autosave is OFF${sandbox ?? 'for this sandbox page'}.`
// : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; : `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'> return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
// Reminder... Reminder...
// <div className='errorContainer'>{text}</div> <div className='errorContainer'>{text}</div>
// </Nav.item>; </Nav.item>;
// } }
// #3 - Unsaved changes exist, click to save, show SAVE NOW // #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges) if(unsavedChanges)
@@ -157,8 +175,8 @@ const HomePage =(props)=>{
if(autoSaveEnabled) if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>; return <Nav.item className='save saved'>auto-saved</Nav.item>;
// #5 - No unsaved changes, and has never been saved, hide the button // #5 - Sandbox with no unsaved changes, and has never been saved, hide the button
if(neverSaved) if(sandbox)
return <Nav.item className='save neverSaved' disabled={true}>save now</Nav.item>; return <Nav.item className='save neverSaved' disabled={true}>save now</Nav.item>;
// DEFAULT - No unsaved changes, show SAVED // DEFAULT - No unsaved changes, show SAVED
+41 -16
View File
@@ -28,15 +28,20 @@ import RecentNavItems from '@navbar/recent.navitem.jsx';
const { both: RecentNavItem } = RecentNavItems; const { both: RecentNavItem } = RecentNavItems;
// Page specific imports // Page specific imports
const SAVE_TIMEOUT = 10000;
const UNSAVED_WARNING_TIMEOUT = 90000; //Warn user afer 15 minutes of unsaved changes
const UNSAVED_WARNING_POPUP_TIMEOUT = 4000; //Show the warning for 4 seconds
const AUTOSAVE_KEY = 'HB_editor_autoSaveOn';
const BREWKEY = 'HB_newPage_content'; const BREWKEY = 'HB_newPage_content';
const STYLEKEY = 'HB_newPage_style'; const STYLEKEY = 'HB_newPage_style';
const METAKEY = 'HB_newPage_metadata';
const SNIPKEY = 'HB_newPage_snippets'; const SNIPKEY = 'HB_newPage_snippets';
const METAKEY = 'HB_newPage_meta';
const SAVEKEYPREFIX = 'HB_editor_defaultSave_'; const SAVEKEYPREFIX = 'HB_editor_defaultSave_';
const useLocalStorage = true; const useLocalStorage = true;
const neverSaved = true; const sandbox = true;
const NewPage = (props)=>{ const NewPage = (props)=>{
props = { props = {
@@ -55,6 +60,7 @@ const NewPage = (props)=>{
const [themeBundle, setThemeBundle] = useState({}); const [themeBundle, setThemeBundle] = useState({});
const [unsavedChanges, setUnsavedChanges] = useState(false); const [unsavedChanges, setUnsavedChanges] = useState(false);
const [autoSaveEnabled, setAutoSaveEnabled] = 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));
@@ -63,6 +69,10 @@ const NewPage = (props)=>{
const trySaveRef = useRef(null); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew const trySaveRef = useRef(null); // 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
useEffect(()=>{
loadBrew();
}, []);
const { const {
handleBrewChange handleBrewChange
} = useCommonEditPageFunctions({ } = useCommonEditPageFunctions({
@@ -81,7 +91,11 @@ const NewPage = (props)=>{
}); });
useEffect(()=>{ useEffect(()=>{
loadBrew(); const autoSavePref = !sandbox && JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
setAutoSaveEnabled(autoSavePref);
setWarnUnsavedChanges(!autoSavePref);
setHTMLErrors(hbfm.validate(currentBrew.text));
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
const handleControlKeys = (e)=>{ const handleControlKeys = (e)=>{
@@ -95,9 +109,14 @@ const NewPage = (props)=>{
}; };
document.addEventListener('keydown', handleControlKeys); document.addEventListener('keydown', handleControlKeys);
window.onbeforeunload = ()=>{
if(unsavedChangesRef.current)
return 'You have unsaved changes!';
};
return ()=>{ return ()=>{
document.removeEventListener('keydown', handleControlKeys); document.removeEventListener('keydown', handleControlKeys);
window.onbeforeunload = null;
}; };
}, []); }, []);
@@ -146,6 +165,12 @@ const NewPage = (props)=>{
editorRef.current.update(); editorRef.current.update();
}; };
const resetWarnUnsavedTimer = ()=>{
setTimeout(()=>setWarnUnsavedChanges(false), UNSAVED_WARNING_POPUP_TIMEOUT); // Hide the warning after 4 seconds
clearTimeout(warnUnsavedTimeout.current);
warnUnsavedTimeout.current = setTimeout(()=>setWarnUnsavedChanges(true), UNSAVED_WARNING_TIMEOUT); // 15 minutes between unsaved work warnings
};
const trySave = async ()=>{ const trySave = async ()=>{
setIsSaving(true); setIsSaving(true);
@@ -180,18 +205,18 @@ const NewPage = (props)=>{
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>; 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 // #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
// if(unsavedChanges && warnUnsavedChanges) { if(unsavedChanges && warnUnsavedChanges) {
// resetWarnUnsavedTimer(); resetWarnUnsavedTimer();
// const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60); const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
// const text = elapsedTime === 0 const text = elapsedTime === 0
// ? 'Autosave is OFF.' ? `Autosave is OFF${sandbox ?? 'for this sandbox page'}.`
// : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; : `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'> return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
// Reminder... Reminder...
// <div className='errorContainer'>{text}</div> <div className='errorContainer'>{text}</div>
// </Nav.item>; </Nav.item>;
// } }
// #3 - Unsaved changes exist, click to save, show SAVE NOW // #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges) if(unsavedChanges)
@@ -201,8 +226,8 @@ const NewPage = (props)=>{
if(autoSaveEnabled) if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>; return <Nav.item className='save saved'>auto-saved</Nav.item>;
// #5 - No unsaved changes, and has never been saved, hide the button // #5 - Sandbox with no unsaved changes, and has never been saved, hide the button
if(neverSaved) if(sandbox)
return <Nav.item className='save neverSaved' disabled={true}>save now</Nav.item>; return <Nav.item className='save neverSaved' disabled={true}>save now</Nav.item>;
// DEFAULT - No unsaved changes, show SAVED // DEFAULT - No unsaved changes, show SAVED