diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index dea5606f2..a0287236b 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -38,17 +38,13 @@ import LockNotification from './lockNotification/lockNotification.jsx'; import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js'; import googleDriveIcon from '../../googleDrive.svg'; -const SAVE_TIMEOUT = 10000; -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 BREWKEY = 'HB_newPage_content'; const STYLEKEY = 'HB_newPage_style'; const SNIPKEY = 'HB_newPage_snippets'; const METAKEY = 'HB_newPage_meta'; const useLocalStorage = false; -const sandbox = false; +const sandbox = false; const EditPage = (props)=>{ props = { @@ -74,10 +70,8 @@ const EditPage = (props)=>{ const [autoSaveEnabled, setAutoSaveEnabled] = useState(true); const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true); - const editorRef = useRef(null); - const lastSavedBrew = useRef(_.cloneDeep(props.brew)); - const saveTimeout = useRef(null); - const warnUnsavedTimeout = useRef(null); + const editorRef = useRef(null); + const lastSavedBrew = useRef(_.cloneDeep(props.brew)); const updateBrew = (newData)=>setCurrentBrew((prevBrew)=>({ ...prevBrew, @@ -86,12 +80,6 @@ const EditPage = (props)=>{ snippets : newData.snippets })); - 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 handleGoogleClick = ()=>{ if(currentBrew.authors.length > 0 && global.account?.username !== currentBrew.authors[0]) { setAlertOwnershipToTransfer(true); @@ -122,25 +110,6 @@ const EditPage = (props)=>{ trySave(true, true, newSaveGoogle); }; - const trySave = useEffectEvent((immediate = false, hasChanges = true, saveToGoogle = false)=>{ - clearTimeout(saveTimeout.current); - if(isSaving) return; - if(!hasChanges && !immediate) return; - const newTimeout = immediate ? 0 : SAVE_TIMEOUT; - - saveTimeout.current = setTimeout(async ()=>{ - setIsSaving(true); - setError(null); - await save(currentBrew, saveToGoogle) - .catch((err)=>{ - setError(err); - }); - setIsSaving(false); - setLastSavedTime(new Date()); - if(!autoSaveEnabled) resetWarnUnsavedTimer(); - }, newTimeout); - }); - const save = async (brew, saveToGoogle)=>{ setHTMLErrors(hbfm.validate(brew.text)); @@ -272,14 +241,6 @@ const EditPage = (props)=>{ return saved; }; - const toggleAutoSave = ()=>{ - clearTimeout(warnUnsavedTimeout.current); - clearTimeout(saveTimeout.current); - localStorage.setItem(AUTOSAVE_KEY, JSON.stringify(!autoSaveEnabled)); - setAutoSaveEnabled(!autoSaveEnabled); - setWarnUnsavedChanges(autoSaveEnabled); - }; - const renderAutoSaveButton = ()=>( Autosave @@ -317,8 +278,11 @@ const EditPage = (props)=>{ }; const { + resetWarnUnsavedTimer, handleSplitMove, - handleBrewChange + handleBrewChange, + toggleAutoSave, + trySave } = useCommonEditPageFunctions({ saveGoogle, setError, @@ -338,10 +302,14 @@ const EditPage = (props)=>{ setWarnUnsavedChanges, unsavedChanges, setUnsavedChanges, - trySave, sandbox, lastSavedBrew, - editorRef + editorRef, + isSaving, + setIsSaving, + save, + lastSavedTime, + setLastSavedTime }); return ( diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 1e02d67b8..a1a4a257e 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -31,10 +31,6 @@ const { both: RecentNavItem } = RecentNavItems; import Headtags from '@vitreum/headtags.js'; const Meta = Headtags.Meta; -const SAVE_TIMEOUT = 10000; -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 BREWKEY = 'HB_newPage_content'; const STYLEKEY = 'HB_newPage_style'; const SNIPKEY = 'HB_newPage_snippets'; @@ -50,6 +46,7 @@ const HomePage =(props)=>{ }; const [currentBrew, setCurrentBrew] = useState(props.brew); + const [saveGoogle, setSaveGoogle] = useState(global.account?.googleId ? true : false); const [error, setError] = useState(undefined); const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text)); const [currentEditorViewPageNum, setCurrentEditorViewPageNum] = useState(1); @@ -64,25 +61,20 @@ const HomePage =(props)=>{ const editorRef = useRef(null); const lastSavedBrew = useRef(_.cloneDeep(props.brew)); - const warnUnsavedTimeout = useRef(null); - const save = ()=>{ - request.post('/api') - .send(currentBrew) - .end((err, res)=>{ - if(err) { - setError(err); - return; - } - const saved = res.body; - window.location = `/edit/${saved.editId}`; + const save = async (brew, saveToGoogle)=>{ + const res = await request + .post(`/api${saveGoogle ? '?saveToGoogle=true' : ''}`) + .send(brew) + .catch((err)=>{ + console.error('Error Updating Local Brew'); + setError(err); }); - }; + if(!res) return; - 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 saved = res.body; + window.onbeforeunload = null; + window.location = `/edit/${saved.editId}`; }; const renderSaveButton = ()=>{ @@ -106,7 +98,7 @@ const HomePage =(props)=>{ // #3 - Unsaved changes exist, click to save, show SAVE NOW if(unsavedChanges) - return save now; + return trySave(true, true, saveGoogle)} color='blue' icon='fas fa-save'>save now; // #4 - No unsaved changes, autosave is ON, show AUTO-SAVED if(autoSaveEnabled) @@ -142,9 +134,12 @@ const HomePage =(props)=>{ }; const { + resetWarnUnsavedTimer, handleSplitMove, - handleBrewChange + handleBrewChange, + trySave } = useCommonEditPageFunctions({ + saveGoogle, setError, setThemeBundle, HTMLErrors, @@ -164,7 +159,12 @@ const HomePage =(props)=>{ setUnsavedChanges, sandbox, lastSavedBrew, - editorRef + editorRef, + isSaving, + setIsSaving, + save, + lastSavedTime, + setLastSavedTime }); return ( @@ -198,7 +198,7 @@ const HomePage =(props)=>{ /> -
+
trySave(true, true, saveGoogle)}> Save current
diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 8182ac20c..1ec1720f4 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -28,10 +28,6 @@ import RecentNavItems from '@navbar/recent.navitem.jsx'; const { both: RecentNavItem } = RecentNavItems; // Page specific imports -const SAVE_TIMEOUT = 10000; -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 BREWKEY = 'HB_newPage_content'; const STYLEKEY = 'HB_newPage_style'; const SNIPKEY = 'HB_newPage_snippets'; @@ -64,8 +60,6 @@ const NewPage = (props)=>{ const editorRef = useRef(null); const lastSavedBrew = useRef(_.cloneDeep(props.brew)); - // const saveTimeout = useRef(null); - const warnUnsavedTimeout = useRef(null); useEffect(()=>{ loadBrew(); @@ -100,30 +94,22 @@ const NewPage = (props)=>{ window.history.replaceState({}, window.location.title, '/new/'); }; - 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 = useEffectEvent(async ()=>{ - setIsSaving(true); - - const updatedBrew = { ...currentBrew }; - splitTextStyleAndMetadata(updatedBrew); - - const pageRegex = updatedBrew.renderer === 'legacy' ? /\\page/g : /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/gm; - updatedBrew.pageCount = (updatedBrew.text.match(pageRegex) || []).length + 1; + const save = async (brew, saveToGoogle)=>{ + //Prepare content to send to server + const brewToSave = { + ...brew, + text : brew.text.normalize('NFC'), + pageCount : ((brew.renderer === 'legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/gm)) || []).length + 1, + textBin : undefined + }; const res = await request .post(`/api${saveGoogle ? '?saveToGoogle=true' : ''}`) - .send(updatedBrew) + .send(brewToSave) .catch((err)=>{ - setIsSaving(false); + console.error('Error Updating Local Brew'); setError(err); }); - - setIsSaving(false); if(!res) return; const savedBrew = res.body; @@ -133,7 +119,7 @@ const NewPage = (props)=>{ localStorage.removeItem(METAKEY); window.onbeforeunload = null; window.location = `/edit/${savedBrew.editId}`; - }); + }; const renderSaveButton = ()=>{ // #1 - Currently saving, show SAVING @@ -156,7 +142,7 @@ const NewPage = (props)=>{ // #3 - Unsaved changes exist, click to save, show SAVE NOW if(unsavedChanges) - return save now; + return trySave(true, true, saveGoogle)} color='blue' icon='fas fa-save'>save now; // #4 - No unsaved changes, autosave is ON, show AUTO-SAVED if(autoSaveEnabled) @@ -196,9 +182,12 @@ const NewPage = (props)=>{ ); const { + resetWarnUnsavedTimer, handleSplitMove, - handleBrewChange + handleBrewChange, + trySave } = useCommonEditPageFunctions({ + saveGoogle, setError, setThemeBundle, HTMLErrors, @@ -216,10 +205,14 @@ const NewPage = (props)=>{ setWarnUnsavedChanges, unsavedChanges, setUnsavedChanges, - trySave, sandbox, lastSavedBrew, - editorRef + editorRef, + isSaving, + setIsSaving, + save, + lastSavedTime, + setLastSavedTime }); return ( diff --git a/client/homebrew/utils/commonEditPageFunctions.js b/client/homebrew/utils/commonEditPageFunctions.js index ff53981f8..e49e29136 100644 --- a/client/homebrew/utils/commonEditPageFunctions.js +++ b/client/homebrew/utils/commonEditPageFunctions.js @@ -1,11 +1,16 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useEffectEvent, useRef } from 'react'; import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js'; import _ from 'lodash'; const AUTOSAVE_KEY = 'HB_editor_autoSaveOn'; +const SAVE_TIMEOUT = 10000; //Autosave 10 seconds after last change +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 + export default function useCommonEditPageFunctions(dependencies) { const { + saveGoogle, setError, setThemeBundle, HTMLErrors, @@ -21,16 +26,21 @@ export default function useCommonEditPageFunctions(dependencies) { autoSaveEnabled, setAutoSaveEnabled, setWarnUnsavedChanges, - trySave = ()=>{}, sandbox, - saveGoogle = false, unsavedChanges, setUnsavedChanges, lastSavedBrew, - editorRef + editorRef, + isSaving, + setIsSaving, + save, + lastSavedTime, + setLastSavedTime } = dependencies; 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 saveTimeout = useRef(null); //==--------- Page setup ----------==// useEffect(()=>{ @@ -70,6 +80,12 @@ export default function useCommonEditPageFunctions(dependencies) { if(autoSaveEnabled) trySave(false, hasChange, saveGoogle); }, [currentBrew]); + 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 handleSplitMove = ()=>{ editorRef.current.update(); }; @@ -97,8 +113,38 @@ export default function useCommonEditPageFunctions(dependencies) { } }; + const toggleAutoSave = ()=>{ + clearTimeout(warnUnsavedTimeout.current); + clearTimeout(saveTimeout.current); + localStorage.setItem(AUTOSAVE_KEY, JSON.stringify(!autoSaveEnabled)); + setAutoSaveEnabled(!autoSaveEnabled); + setWarnUnsavedChanges(autoSaveEnabled); + }; + + const trySave = useEffectEvent((forceSave = false, hasChanges = true, saveToGoogle = false)=>{ + clearTimeout(saveTimeout.current); + if(isSaving) return; + if(!forceSave && !hasChanges) return; + const newTimeout = forceSave ? 0 : SAVE_TIMEOUT; + + saveTimeout.current = setTimeout(async ()=>{ + setIsSaving(true); + setError(null); + await save(currentBrew, saveToGoogle) + .catch((err)=>{ + setError(err); + }); + setIsSaving(false); + setLastSavedTime(new Date()); + if(!autoSaveEnabled) resetWarnUnsavedTimer(); + }, newTimeout); + }); + return { + resetWarnUnsavedTimer, handleSplitMove, - handleBrewChange + handleBrewChange, + toggleAutoSave, + trySave, } } \ No newline at end of file