From 6e0ef96ccbd9085b58515b4750917981d77c1c08 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Wed, 9 Sep 2026 18:49:44 +1200 Subject: [PATCH 01/12] Set default values on function parameters for fetchThemeBundle --- shared/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/helpers.js b/shared/helpers.js index 667d7b6b1..db046b810 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -146,7 +146,7 @@ const printCurrentBrew = async ()=>{ } }; -const fetchThemeBundle = async (setError, setThemeBundle, renderer, theme)=>{ +const fetchThemeBundle = async (setError = ()=>{}, setThemeBundle = ()=>{}, renderer, theme)=>{ if(!renderer || !theme) return; const res = await request .get(`/api/theme/${renderer}/${theme}`) From 3ccb4e5ceae29a369addb86399104f47ef749c0b Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 11:35:31 -0400 Subject: [PATCH 02/12] Remove default ver prop from homePage.jsx Only used in the navbar component which internally already uses a default value. Edit and New already have this removed. --- client/homebrew/pages/homePage/homePage.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 4dc505ea9..3ae1c50a4 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -41,7 +41,6 @@ const neverSaved = true; const HomePage =(props)=>{ props = { brew : DEFAULT_BREW, - ver : '0.0.0', ...props }; From 15a20079dd2f6a7ec095aed7cddc4fa2e0865b17 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 11:37:25 -0400 Subject: [PATCH 03/12] Remove commented "chrome warning" from navbar.jsx Warning when non-chrome browser is detected already moved to a different file. No need to keep this commented-out chunk left here in navbar.jsx. --- client/homebrew/navbar/navbar.jsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/client/homebrew/navbar/navbar.jsx b/client/homebrew/navbar/navbar.jsx index db9a836c9..40e5aef71 100644 --- a/client/homebrew/navbar/navbar.jsx +++ b/client/homebrew/navbar/navbar.jsx @@ -9,22 +9,10 @@ const Navbar = createReactClass({ displayName : 'Navbar', getInitialState : function() { return { - // showNonChromeWarning: false, // uncomment if needed ver : global.version || '0.0.0' }; }, - /* - renderChromeWarning : function(){ - if(!this.state.showNonChromeWarning) return; - return - Optimized for Chrome -
- If you are experiencing rendering issues, use Chrome instead -
-
- }, -*/ render : function(){ return From 5865e5adfd93f98b1360b3da1a3abef4700672b9 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 18:42:39 +0000 Subject: [PATCH 04/12] Extract common "handlePageChange" to a reusable hook --- client/homebrew/pages/editPage/editPage.jsx | 61 +++++++++---------- client/homebrew/pages/homePage/homePage.jsx | 58 ++++++++---------- client/homebrew/pages/newPage/newPage.jsx | 58 ++++++++---------- .../homebrew/utils/commonEditPageFunctions.js | 43 +++++++++++++ package-lock.json | 15 ----- 5 files changed, 125 insertions(+), 110 deletions(-) create mode 100644 client/homebrew/utils/commonEditPageFunctions.js diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 36df0f1bb..29f215e08 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -10,18 +10,20 @@ import _ from 'lodash'; import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js'; import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js'; +import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.js' + import SplitPane from '@components/splitPane/splitPane.jsx'; import Editor from '../../editor/editor.jsx'; import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; -import Nav from '@navbar/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 Nav from '@navbar/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 RecentNavItems from '@navbar/recent.navitem.jsx'; const { both: RecentNavItem } = RecentNavItems; @@ -41,7 +43,6 @@ 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 AUTOSAVE_KEY = 'HB_editor_autoSaveOn'; const BREWKEY = 'HB_newPage_content'; const STYLEKEY = 'HB_newPage_style'; @@ -49,7 +50,7 @@ const SNIPKEY = 'HB_newPage_snippets'; const METAKEY = 'HB_newPage_meta'; const useLocalStorage = false; -const neverSaved = false; +const neverSaved = false; const EditPage = (props)=>{ props = { @@ -82,6 +83,23 @@ const EditPage = (props)=>{ 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 { + handleBrewChange + } = useCommonEditPageFunctions({ + setError, + setThemeBundle, + HTMLErrors, + setHTMLErrors, + setCurrentBrew, + useLocalStorage, + BREWKEY, + STYLEKEY, + SNIPKEY, + METAKEY, + fetchThemeBundle, + hbfm + }); + useEffect(()=>{ const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true); setAutoSaveEnabled(autoSavePref); @@ -126,29 +144,6 @@ const EditPage = (props)=>{ editorRef.current?.update(); }; - const handleBrewChange = (field)=>(value, subfield)=>{ //'text', 'style', 'snippets', 'metadata' - if(subfield == 'renderer' || subfield == 'theme') - fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); - - //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length && (field == 'text' || field == 'snippets')) - setHTMLErrors(hbfm.validate(value)); - - if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); - else setCurrentBrew((prev)=>({ ...prev, [field]: value })); - - if(useLocalStorage) { - if(field == 'text') localStorage.setItem(BREWKEY, value); - if(field == 'style') localStorage.setItem(STYLEKEY, value); - if(field == 'snippets') localStorage.setItem(SNIPKEY, value); - if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({ - renderer : value.renderer, - theme : value.theme, - lang : value.lang - })); - } - }; - const updateBrew = (newData)=>setCurrentBrew((prevBrew)=>({ ...prevBrew, style : newData.style, diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 4dc505ea9..34c2937f1 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -10,18 +10,20 @@ import _ from 'lodash'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js'; +import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.js' + import SplitPane from '@components/splitPane/splitPane.jsx'; import Editor from '../../editor/editor.jsx'; import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; -import Nav from '@navbar/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 Nav from '@navbar/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 RecentNavItems from '@navbar/recent.navitem.jsx'; const { both: RecentNavItem } = RecentNavItems; @@ -60,6 +62,23 @@ const HomePage =(props)=>{ const lastSavedBrew = useRef(_.cloneDeep(props.brew)); const unsavedChangesRef = useRef(unsavedChanges); + const { + handleBrewChange + } = useCommonEditPageFunctions({ + setError, + setThemeBundle, + HTMLErrors, + setHTMLErrors, + setCurrentBrew, + useLocalStorage, + BREWKEY, + STYLEKEY, + SNIPKEY, + METAKEY, + fetchThemeBundle, + hbfm + }); + useEffect(()=>{ fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); @@ -112,29 +131,6 @@ const HomePage =(props)=>{ editorRef.current.update(); }; - const handleBrewChange = (field)=>(value, subfield)=>{ //'text', 'style', 'snippets', 'metadata' - if(subfield == 'renderer' || subfield == 'theme') - fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); - - //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length && (field == 'text' || field == 'snippets')) - setHTMLErrors(hbfm.validate(value)); - - if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); - else setCurrentBrew((prev)=>({ ...prev, [field]: value })); - - if(useLocalStorage) { - if(field == 'text') localStorage.setItem(BREWKEY, value); - if(field == 'style') localStorage.setItem(STYLEKEY, value); - if(field == 'snippets') localStorage.setItem(SNIPKEY, value); - if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({ - renderer : value.renderer, - theme : value.theme, - lang : value.lang - })); - } - }; - const renderSaveButton = ()=>{ // #1 - Currently saving, show SAVING if(isSaving) diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index c45090fb7..e55434f5c 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -10,18 +10,20 @@ import _ from 'lodash'; import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '@shared/helpers.js'; +import useCommonEditPageFunctions from '../../utils/commonEditPageFunctions.js' + import SplitPane from '@components/splitPane/splitPane.jsx'; import Editor from '../../editor/editor.jsx'; import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; -import Nav from '@navbar/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 Nav from '@navbar/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 RecentNavItems from '@navbar/recent.navitem.jsx'; const { both: RecentNavItem } = RecentNavItems; @@ -61,6 +63,23 @@ 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 unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges + const { + handleBrewChange + } = useCommonEditPageFunctions({ + setError, + setThemeBundle, + HTMLErrors, + setHTMLErrors, + setCurrentBrew, + useLocalStorage, + BREWKEY, + STYLEKEY, + SNIPKEY, + METAKEY, + fetchThemeBundle, + hbfm + }); + useEffect(()=>{ loadBrew(); fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); @@ -127,29 +146,6 @@ const NewPage = (props)=>{ editorRef.current.update(); }; - const handleBrewChange = (field)=>(value, subfield)=>{ //'text', 'style', 'snippets', 'metadata' - if(subfield == 'renderer' || subfield == 'theme') - fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); - - //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length && (field == 'text' || field == 'snippets')) - setHTMLErrors(hbfm.validate(value)); - - if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); - else setCurrentBrew((prev)=>({ ...prev, [field]: value })); - - if(useLocalStorage) { - if(field == 'text') localStorage.setItem(BREWKEY, value); - if(field == 'style') localStorage.setItem(STYLEKEY, value); - if(field == 'snippets') localStorage.setItem(SNIPKEY, value); - if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({ - renderer : value.renderer, - theme : value.theme, - lang : value.lang - })); - } - }; - const trySave = async ()=>{ setIsSaving(true); diff --git a/client/homebrew/utils/commonEditPageFunctions.js b/client/homebrew/utils/commonEditPageFunctions.js new file mode 100644 index 000000000..bf397e334 --- /dev/null +++ b/client/homebrew/utils/commonEditPageFunctions.js @@ -0,0 +1,43 @@ +export default function useCommonEditPageFunctions(dependencies) { + const { + setError, + setThemeBundle, + HTMLErrors, + setHTMLErrors, + setCurrentBrew, + useLocalStorage, + BREWKEY, + STYLEKEY, + SNIPKEY, + METAKEY, + fetchThemeBundle, + hbfm + } = dependencies; + + const handleBrewChange = (field)=>(value, subfield)=>{ //'text', 'style', 'snippets', 'metadata' + if(subfield == 'renderer' || subfield == 'theme') + fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); + + //If there are HTML errors, run the validator on every change to give quick feedback + if(HTMLErrors.length && (field == 'text' || field == 'snippets')) + setHTMLErrors(hbfm.validate(value)); + + if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); + else setCurrentBrew((prev)=>({ ...prev, [field]: value })); + + if(useLocalStorage) { + if(field == 'text') localStorage.setItem(BREWKEY, value); + if(field == 'style') localStorage.setItem(STYLEKEY, value); + if(field == 'snippets') localStorage.setItem(SNIPKEY, value); + if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({ + renderer : value.renderer, + theme : value.theme, + lang : value.lang + })); + } + }; + + return { + handleBrewChange + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8309fba77..f7d138beb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12085,21 +12085,6 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/gcp-metadata": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", - "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/mongoose/node_modules/mongodb": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.5.0.tgz", From 67728afd4f4e8038b8f353a3d594550bb3e2ab0b Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 21:08:58 +0000 Subject: [PATCH 05/12] Give "unsaved change" warning to NewPage and HomePage. Autosave is always disabled on those. --- client/homebrew/pages/editPage/editPage.jsx | 22 ++++---- client/homebrew/pages/homePage/homePage.jsx | 56 +++++++++++++------- client/homebrew/pages/newPage/newPage.jsx | 57 +++++++++++++++------ 3 files changed, 90 insertions(+), 45 deletions(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 29f215e08..f6f74c269 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -40,7 +40,7 @@ import { updateHistory, versionHistoryGarbageCollection } from '../../utils/vers 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_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'; @@ -50,7 +50,7 @@ const SNIPKEY = 'HB_newPage_snippets'; const METAKEY = 'HB_newPage_meta'; const useLocalStorage = false; -const neverSaved = false; +const sandbox = false; const EditPage = (props)=>{ props = { @@ -101,7 +101,8 @@ const EditPage = (props)=>{ }); useEffect(()=>{ - const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true); + const autoSavePref = !sandbox && JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true); + setAutoSaveEnabled(autoSavePref); setWarnUnsavedChanges(!autoSavePref); setHTMLErrors(hbfm.validate(currentBrew.text)); @@ -122,6 +123,7 @@ const EditPage = (props)=>{ if(unsavedChangesRef.current) return 'You have unsaved changes!'; }; + return ()=>{ document.removeEventListener('keydown', handleControlKeys); window.onbeforeunload = null; @@ -313,12 +315,12 @@ const EditPage = (props)=>{ resetWarnUnsavedTimer(); const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60); const text = elapsedTime === 0 - ? 'Autosave is OFF.' - : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; + ? `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 - Reminder... -
{text}
+ Reminder... +
{text}
; } @@ -330,9 +332,9 @@ const EditPage = (props)=>{ if(autoSaveEnabled) return auto-saved; - // #5 - No unsaved changes, and has never been saved, hide the button - if(neverSaved) - return save now; + // #5 - Sandbox with no unsaved changes, and has never been saved, hide the button + if(sandbox) + return save now; // DEFAULT - No unsaved changes, show SAVED return saved; diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index f528f934f..c666703c3 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -32,13 +32,18 @@ const { both: RecentNavItem } = RecentNavItems; import Headtags from '@vitreum/headtags.js'; const Meta = Headtags.Meta; -const BREWKEY = 'homebrewery-new'; -const STYLEKEY = 'homebrewery-new-style'; -const SNIPKEY = 'homebrewery-new-snippets'; -const METAKEY = 'homebrewery-new-meta'; +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 STYLEKEY = 'HB_newPage_style'; +const SNIPKEY = 'HB_newPage_snippets'; +const METAKEY = 'HB_newPage_meta'; const useLocalStorage = false; -const neverSaved = true; +const sandbox = true; const HomePage =(props)=>{ props = { @@ -55,7 +60,8 @@ const HomePage =(props)=>{ const [themeBundle, setThemeBundle] = useState({}); const [unsavedChanges, setUnsavedChanges] = 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 lastSavedBrew = useRef(_.cloneDeep(props.brew)); @@ -79,6 +85,11 @@ const HomePage =(props)=>{ }); 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); const handleControlKeys = (e)=>{ @@ -96,6 +107,7 @@ const HomePage =(props)=>{ if(unsavedChangesRef.current) return 'You have unsaved changes!'; }; + return ()=>{ document.removeEventListener('keydown', handleControlKeys); window.onbeforeunload = null; @@ -130,24 +142,30 @@ const HomePage =(props)=>{ 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 = ()=>{ // #1 - Currently saving, show SAVING if(isSaving) return saving...; // #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.' - // : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; + if(unsavedChanges && warnUnsavedChanges) { + resetWarnUnsavedTimer(); + const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60); + const text = elapsedTime === 0 + ? `Autosave is OFF${sandbox ?? 'for this sandbox page'}.` + : `Autosave is OFF${sandbox ?? 'for this sandbox page'}, and you haven't saved for ${elapsedTime} minutes.`; - // return - // Reminder... - //
{text}
- //
; - // } + return + Reminder... +
{text}
+
; + } // #3 - Unsaved changes exist, click to save, show SAVE NOW if(unsavedChanges) @@ -157,8 +175,8 @@ const HomePage =(props)=>{ if(autoSaveEnabled) return auto-saved; - // #5 - No unsaved changes, and has never been saved, hide the button - if(neverSaved) + // #5 - Sandbox with no unsaved changes, and has never been saved, hide the button + if(sandbox) return save now; // DEFAULT - No unsaved changes, show SAVED diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index e55434f5c..ab024fe7a 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -28,15 +28,20 @@ import RecentNavItems from '@navbar/recent.navitem.jsx'; const { both: RecentNavItem } = RecentNavItems; // 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 STYLEKEY = 'HB_newPage_style'; -const METAKEY = 'HB_newPage_metadata'; const SNIPKEY = 'HB_newPage_snippets'; +const METAKEY = 'HB_newPage_meta'; + const SAVEKEYPREFIX = 'HB_editor_defaultSave_'; const useLocalStorage = true; -const neverSaved = true; +const sandbox = true; const NewPage = (props)=>{ props = { @@ -55,6 +60,7 @@ const NewPage = (props)=>{ const [themeBundle, setThemeBundle] = useState({}); const [unsavedChanges, setUnsavedChanges] = useState(false); const [autoSaveEnabled, setAutoSaveEnabled] = useState(false); + const [warnUnsavedChanges, setWarnUnsavedChanges] = useState(true); const editorRef = useRef(null); 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 unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges + useEffect(()=>{ + loadBrew(); + }, []); + const { handleBrewChange } = useCommonEditPageFunctions({ @@ -81,7 +91,11 @@ const NewPage = (props)=>{ }); 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); const handleControlKeys = (e)=>{ @@ -95,9 +109,14 @@ const NewPage = (props)=>{ }; document.addEventListener('keydown', handleControlKeys); + window.onbeforeunload = ()=>{ + if(unsavedChangesRef.current) + return 'You have unsaved changes!'; + }; return ()=>{ document.removeEventListener('keydown', handleControlKeys); + window.onbeforeunload = null; }; }, []); @@ -146,6 +165,12 @@ const NewPage = (props)=>{ 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 ()=>{ setIsSaving(true); @@ -180,18 +205,18 @@ const NewPage = (props)=>{ return saving...; // #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.' - // : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; + if(unsavedChanges && warnUnsavedChanges) { + resetWarnUnsavedTimer(); + const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60); + const text = elapsedTime === 0 + ? `Autosave is OFF${sandbox ?? 'for this sandbox page'}.` + : `Autosave is OFF${sandbox ?? 'for this sandbox page'}, and you haven't saved for ${elapsedTime} minutes.`; - // return - // Reminder... - //
{text}
- //
; - // } + return + Reminder... +
{text}
+
; + } // #3 - Unsaved changes exist, click to save, show SAVE NOW if(unsavedChanges) @@ -201,8 +226,8 @@ const NewPage = (props)=>{ if(autoSaveEnabled) return auto-saved; - // #5 - No unsaved changes, and has never been saved, hide the button - if(neverSaved) + // #5 - Sandbox with no unsaved changes, and has never been saved, hide the button + if(sandbox) return save now; // DEFAULT - No unsaved changes, show SAVED From 76541cd836cb25a3b010826d06ef480c87fa49f5 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 21:21:00 +0000 Subject: [PATCH 06/12] Add missing timeout ref --- client/homebrew/pages/homePage/homePage.jsx | 1 + client/homebrew/pages/newPage/newPage.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index c666703c3..1240066a7 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -65,6 +65,7 @@ const HomePage =(props)=>{ const editorRef = useRef(null); const lastSavedBrew = useRef(_.cloneDeep(props.brew)); + const warnUnsavedTimeout = useRef(null); const unsavedChangesRef = useRef(unsavedChanges); const { diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index ab024fe7a..b9450882f 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -65,7 +65,7 @@ const NewPage = (props)=>{ const editorRef = useRef(null); const lastSavedBrew = useRef(_.cloneDeep(props.brew)); // const saveTimeout = useRef(null); - // const warnUnsavedTimeout = useRef(null); + const warnUnsavedTimeout = useRef(null); 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 From 2c416dcdb90dbb0030cd87b792ade894a7453e02 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 21:23:50 +0000 Subject: [PATCH 07/12] Add missing lastSavedTime state --- client/homebrew/pages/homePage/homePage.jsx | 1 + client/homebrew/pages/newPage/newPage.jsx | 1 + 2 files changed, 2 insertions(+) diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 1240066a7..34874d37c 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -60,6 +60,7 @@ const HomePage =(props)=>{ 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); diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index b9450882f..83e91b1a2 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -51,6 +51,7 @@ const NewPage = (props)=>{ 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 [error, setError] = useState(null); const [HTMLErrors, setHTMLErrors] = useState(hbfm.validate(props.brew.text)); From 719b30a04ee01b19250c70309f57da2be04dd863 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 21:33:02 +0000 Subject: [PATCH 08/12] Fix message syntax --- client/homebrew/pages/editPage/editPage.jsx | 4 ++-- client/homebrew/pages/homePage/homePage.jsx | 4 ++-- client/homebrew/pages/newPage/newPage.jsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index f6f74c269..386f2afb0 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -315,8 +315,8 @@ const EditPage = (props)=>{ 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.`; + ? `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 Reminder... diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 34874d37c..0c85fa48c 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -160,8 +160,8 @@ const HomePage =(props)=>{ 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.`; + ? `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 Reminder... diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 83e91b1a2..f28d97777 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -210,8 +210,8 @@ const NewPage = (props)=>{ 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.`; + ? `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 Reminder... From e4d66110274ce6132bd0c6b2ffe41354167bde35 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 21:39:51 +0000 Subject: [PATCH 09/12] Restore time to 15 minutes --- client/homebrew/pages/editPage/editPage.jsx | 6 +++--- client/homebrew/pages/homePage/homePage.jsx | 6 +++--- client/homebrew/pages/newPage/newPage.jsx | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 386f2afb0..5f369e7c5 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -40,7 +40,7 @@ import { updateHistory, versionHistoryGarbageCollection } from '../../utils/vers import googleDriveIcon from '../../googleDrive.svg'; const SAVE_TIMEOUT = 10000; -const UNSAVED_WARNING_TIMEOUT = 90000; //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 AUTOSAVE_KEY = 'HB_editor_autoSaveOn'; @@ -315,8 +315,8 @@ const EditPage = (props)=>{ 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.`; + ? `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 Reminder... diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 0c85fa48c..2e4137049 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -33,7 +33,7 @@ import Headtags from '@vitreum/headtags.js'; const Meta = Headtags.Meta; const SAVE_TIMEOUT = 10000; -const UNSAVED_WARNING_TIMEOUT = 90000; //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 AUTOSAVE_KEY = 'HB_editor_autoSaveOn'; @@ -160,8 +160,8 @@ const HomePage =(props)=>{ 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.`; + ? `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 Reminder... diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index f28d97777..f0c9ac323 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -29,7 +29,7 @@ const { both: RecentNavItem } = RecentNavItems; // Page specific imports const SAVE_TIMEOUT = 10000; -const UNSAVED_WARNING_TIMEOUT = 90000; //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 AUTOSAVE_KEY = 'HB_editor_autoSaveOn'; @@ -210,8 +210,8 @@ const NewPage = (props)=>{ 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.`; + ? `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 Reminder... From c3a2d0641a33b3db594fa95de43bea650517b252 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 20:23:30 -0400 Subject: [PATCH 10/12] Fix setAlertOwnershipToTransfer typo --- client/homebrew/pages/editPage/editPage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 5f369e7c5..30cf67153 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -161,7 +161,7 @@ const EditPage = (props)=>{ const handleGoogleClick = ()=>{ if(global.account !== currentBrew.authors[0]) { - setalertOwnershipToTransfer(true); + setAlertOwnershipToTransfer(true); return; } if(!global.account?.googleId) { @@ -178,7 +178,7 @@ const EditPage = (props)=>{ setAlertTrashedGoogleBrew(false); setAlertNoGoogleToTransfer(false); setConfirmGoogleTransfer(false); - setalertOwnershipToTransfer(false); + setAlertOwnershipToTransfer(false); }; const toggleGoogleStorage = (e)=>{ From 4a884233c19915446e8c0f650d172c03cb75307c Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 20:29:39 -0400 Subject: [PATCH 11/12] Remove page leave warning when saving on New --- client/homebrew/pages/newPage/newPage.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index f0c9ac323..ff686e0a1 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -197,6 +197,7 @@ const NewPage = (props)=>{ localStorage.removeItem(BREWKEY); localStorage.removeItem(STYLEKEY); localStorage.removeItem(METAKEY); + window.onbeforeunload = null; window.location = `/edit/${savedBrew.editId}`; }; From da882d6f006c5961bf7a1f102b19b1b6261e57aa Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Wed, 9 Sep 2026 20:55:30 -0400 Subject: [PATCH 12/12] Fix google transfer ownership popup priority Only show alertOwnershipToTransfer if: 1) current user is logged in 2) brew actually has authors 3) current user is not author[0] In all other cases, it should prompt to log in with a google account instead. --- client/homebrew/pages/editPage/editPage.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 30cf67153..df5e8e592 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -160,7 +160,7 @@ const EditPage = (props)=>{ }; const handleGoogleClick = ()=>{ - if(global.account !== currentBrew.authors[0]) { + if(currentBrew.authors.length > 0 && global.account?.username !== currentBrew.authors[0]) { setAlertOwnershipToTransfer(true); return; } @@ -264,12 +264,11 @@ const EditPage = (props)=>{ Google Drive icon - - {alertOwnershipToTransfer && (
- You must be the Owner to transfer between the Homebrewery and Google Drive! - The owner of this file is {currentBrew.authors[0]} + You must be the Owner to transfer between the Homebrewery and Google Drive! + The owner of this file is {currentBrew.authors[0]}. +

Okay
)}