From 878ea1449db719d7abe413b3c3b062cd4b2bba51 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sun, 20 Jul 2025 11:42:24 +1200 Subject: [PATCH 1/8] Add indexes to HomebrewSchema --- server/homebrew.model.js | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/server/homebrew.model.js b/server/homebrew.model.js index 2e74b1de2..ff371ee42 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -7,29 +7,29 @@ import zlib from 'zlib'; const HomebrewSchema = mongoose.Schema({ shareId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, editId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, - googleId : { type: String }, - title : { type: String, default: '' }, + googleId : { type: String, index: true }, + title : { type: String, default: '', index: true }, text : { type: String, default: '' }, textBin : { type: Buffer }, - pageCount : { type: Number, default: 1 }, + pageCount : { type: Number, default: 1, index: true }, description : { type: String, default: '' }, - tags : [String], + tags : { type: [String], index: true }, systems : [String], - lang : { type: String, default: 'en' }, - renderer : { type: String, default: '' }, - authors : [String], + lang : { type: String, default: 'en', index: true }, + renderer : { type: String, default: '', index: true }, + authors : { type: [String], index: true }, invitedAuthors : [String], - published : { type: Boolean, default: false }, - thumbnail : { type: String, default: '' }, + published : { type: Boolean, default: false, index: true }, + thumbnail : { type: String, default: '', index: true }, - createdAt : { type: Date, default: Date.now }, - updatedAt : { type: Date, default: Date.now }, - lastViewed : { type: Date, default: Date.now }, + createdAt : { type: Date, default: Date.now, index: true }, + updatedAt : { type: Date, default: Date.now, index: true }, + lastViewed : { type: Date, default: Date.now, index: true }, views : { type: Number, default: 0 }, - version : { type: Number, default: 1 }, + version : { type: Number, default: 1, index: true }, - lock : { type: Object } + lock : { type: Object, index: true } }, { versionKey: false }); HomebrewSchema.statics.increaseView = async function(query) { @@ -43,6 +43,8 @@ HomebrewSchema.statics.increaseView = async function(query) { return brew; }; +// STATIC FUNCTIONS + HomebrewSchema.statics.get = async function(query, fields=null){ const brew = await Homebrew.findOne(query, fields).orFail() .catch((error)=>{throw 'Can not find brew';}); @@ -63,6 +65,15 @@ HomebrewSchema.statics.getByUser = async function(username, allowAccess=false, f return brews; }; +// INDEXES + +HomebrewSchema.index({ updatedAt: -1, lastViewed: -1 }); +HomebrewSchema.index({ published: 1, title: 'text' }); + +HomebrewSchema.index({ lock: 1, sparse: true }); +HomebrewSchema.path('lock.reviewRequested').index({ sparse: true }); + + const Homebrew = mongoose.model('Homebrew', HomebrewSchema); export { From d9cd270f3b993e02cb4a76bfd867d70247991989 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sun, 20 Jul 2025 11:43:14 +1200 Subject: [PATCH 2/8] Add autobuild to Mongoose connection (local/docker ONLY) --- server/db.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/db.js b/server/db.js index 97da56a08..f4e7fd851 100644 --- a/server/db.js +++ b/server/db.js @@ -27,7 +27,10 @@ const disconnect = async ()=>{ }; const connect = async (config)=>{ - return await Mongoose.connect(getMongoDBURL(config), { retryWrites: false }) + return await Mongoose.connect(getMongoDBURL(config), { + retryWrites : false, + autoIndex : (config.get('node_env') == 'local' || config.get('node_env') == 'docker') + }) .catch((error)=>handleConnectionError(error)); }; From 4856c803ed6102d93397be2ba544da0d30eb6694 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sun, 20 Jul 2025 11:48:22 +1200 Subject: [PATCH 3/8] Use local_environments from config --- server/db.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/db.js b/server/db.js index f4e7fd851..196eba650 100644 --- a/server/db.js +++ b/server/db.js @@ -29,7 +29,7 @@ const disconnect = async ()=>{ const connect = async (config)=>{ return await Mongoose.connect(getMongoDBURL(config), { retryWrites : false, - autoIndex : (config.get('node_env') == 'local' || config.get('node_env') == 'docker') + autoIndex : (config.get('local_environments').includes(config.get('node_env'))) }) .catch((error)=>handleConnectionError(error)); }; From 4b753970c9edc8664944c3188650d7a7e26f4b49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= Date: Mon, 29 Sep 2025 22:19:19 +0200 Subject: [PATCH 4/8] remove scrollbar --- shared/naturalcrit/codeEditor/codeEditor.less | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shared/naturalcrit/codeEditor/codeEditor.less b/shared/naturalcrit/codeEditor/codeEditor.less index 84a5c63f1..2a57ae8e6 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.less +++ b/shared/naturalcrit/codeEditor/codeEditor.less @@ -38,15 +38,6 @@ animation-duration : 0.4s; } - .CodeMirror-vscrollbar { - &::-webkit-scrollbar { width : 20px; } - &::-webkit-scrollbar-thumb { - width : 20px; - background : linear-gradient(90deg, #858585 15px, #808080 15px); - } - } - - //.cm-tab { // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right; //} From 900cf6aebb676aa7e9ea4a83ab8dcc1acfbbe122 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Thu, 2 Oct 2025 22:59:24 +1300 Subject: [PATCH 5/8] Change SAVEKEY definition to after username is populated --- client/homebrew/pages/newPage/newPage.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index bb21441cf..e7b1fdee6 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -23,7 +23,7 @@ import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '. const BREWKEY = 'homebrewery-new'; const STYLEKEY = 'homebrewery-new-style'; const METAKEY = 'homebrewery-new-meta'; -const SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`; +const SAVEKEYPREFIX = 'HOMEBREWERY-DEFAULT-SAVE-LOCATION-'; const NewPage = (props) => { props = { @@ -67,6 +67,7 @@ const NewPage = (props) => { brew.lang = metaStorage?.lang ?? brew.lang; } + const SAVEKEY = `${SAVEKEYPREFIX}${global.account?.username}`; const saveStorage = localStorage.getItem(SAVEKEY) || 'HOMEBREWERY'; setCurrentBrew(brew); From bcca5fa97d0bffb79c1251f30e43642f4ec0419e Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 2 Oct 2025 19:27:45 -0400 Subject: [PATCH 6/8] In /homepage, rename brew state to currentBrew to match /new and /edit --- client/homebrew/pages/homePage/homePage.jsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 84967b1ff..657f313e1 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -28,7 +28,7 @@ const HomePage =(props)=>{ ...props }; - const [brew , setBrew] = useState(props.brew); + const [currentBrew , setCurrentBrew] = useState(props.brew); const [welcomeText , setWelcomeText] = useState(props.brew.text); const [error , setError] = useState(undefined); const [currentEditorViewPageNum , setCurrentEditorViewPageNum] = useState(1); @@ -40,12 +40,12 @@ const HomePage =(props)=>{ const editorRef = useRef(null); useEffect(()=>{ - fetchThemeBundle(setError, setThemeBundle, brew.renderer, brew.theme); + fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); }, []); const save = ()=>{ request.post('/api') - .send(brew) + .send(currentBrew) .end((err, res)=>{ if(err) { setError(err); @@ -105,9 +105,9 @@ const HomePage =(props)=>{ { currentBrewRendererPageNum={currentBrewRendererPageNum} /> { /> -
+
Save current
From 1c6a39363ca07dacbaa70cd3789f93ae21f0d962 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 2 Oct 2025 19:33:15 -0400 Subject: [PATCH 7/8] Combine handleText/Style/Snippet/Meta functions into common function Also adds any related imports and key names --- client/homebrew/editor/editor.jsx | 15 +++--- client/homebrew/pages/editPage/editPage.jsx | 50 +++++++++--------- client/homebrew/pages/homePage/homePage.jsx | 34 ++++++++++-- client/homebrew/pages/newPage/newPage.jsx | 57 ++++++++------------- 4 files changed, 85 insertions(+), 71 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 8d331e46e..9f9e23625 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -40,11 +40,8 @@ const Editor = createClass({ style : '' }, - onTextChange : ()=>{}, - onStyleChange : ()=>{}, - onMetaChange : ()=>{}, - onSnipChange : ()=>{}, - reportError : ()=>{}, + onBrewChange : ()=>{}, + reportError : ()=>{}, onCursorPageChange : ()=>{}, onViewPageChange : ()=>{}, @@ -438,7 +435,7 @@ const Editor = createClass({ language='gfm' view={this.state.view} value={this.props.brew.text} - onChange={this.props.onTextChange} + onChange={this.props.onBrewChange('text')} editorTheme={this.state.editorTheme} rerenderParent={this.rerenderParent} style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} /> @@ -451,7 +448,7 @@ const Editor = createClass({ language='css' view={this.state.view} value={this.props.brew.style ?? DEFAULT_STYLE_TEXT} - onChange={this.props.onStyleChange} + onChange={this.props.onBrewChange('style')} enableFolding={true} editorTheme={this.state.editorTheme} rerenderParent={this.rerenderParent} @@ -467,7 +464,7 @@ const Editor = createClass({ ; @@ -481,7 +478,7 @@ const Editor = createClass({ language='gfm' view={this.state.view} value={this.props.brew.snippets} - onChange={this.props.onSnipChange} + onChange={this.props.onBrewChange('snippets')} enableFolding={true} editorTheme={this.state.editorTheme} rerenderParent={this.rerenderParent} diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 6c2220ec1..aff5dd3ea 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -39,6 +39,11 @@ 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 = 'homebrewery-new'; +const STYLEKEY = 'homebrewery-new-style'; +const SNIPKEY = 'homebrewery-new-snippets'; +const METAKEY = 'homebrewery-new-meta'; + const EditPage = (props)=>{ props = { brew : DEFAULT_BREW_LOAD, @@ -69,6 +74,8 @@ const EditPage = (props)=>{ const trySaveRef = useRef(trySave); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew const unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges + const useLocalStorage = false; + useEffect(()=>{ const autoSavePref = JSON.parse(localStorage.getItem('AUTOSAVE_ON') ?? true); setAutoSaveEnabled(autoSavePref); @@ -125,29 +132,27 @@ const EditPage = (props)=>{ setCurrentBrewRendererPageNum(pageNumber); }; - const handleTextChange = (text)=>{ + 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) - setHTMLErrors(Markdown.validate(text)); - setCurrentBrew((prevBrew)=>({ ...prevBrew, text })); - }; + if(HTMLErrors.length && (field == 'text' || field == 'snippets')) + setHTMLErrors(Markdown.validate(value)); - const handleStyleChange = (style)=>{ - setCurrentBrew((prevBrew)=>({ ...prevBrew, style })); - }; + if(field == 'metadata') setCurrentBrew(prev => ({ ...prev, ...value })); + else setCurrentBrew(prev => ({ ...prev, [field]: value })); - const handleSnipChange = (snippet)=>{ - //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length) - setHTMLErrors(Markdown.validate(snippet)); - setCurrentBrew((prevBrew)=>({ ...prevBrew, snippets: snippet })); - }; - - const handleMetaChange = (metadata, field = undefined)=>{ - if(field === 'theme' || field === 'renderer') - fetchThemeBundle(setError, setThemeBundle, metadata.renderer, metadata.theme); - - setCurrentBrew((prev)=>({ ...prev, ...metadata })); + 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)=>({ @@ -380,10 +385,7 @@ const EditPage = (props)=>{ { props = { brew : DEFAULT_BREW, @@ -31,6 +37,7 @@ const HomePage =(props)=>{ const [currentBrew , setCurrentBrew] = useState(props.brew); const [welcomeText , setWelcomeText] = useState(props.brew.text); const [error , setError] = useState(undefined); + const [HTMLErrors , setHTMLErrors ] = useState(Markdown.validate(props.brew.text)); const [currentEditorViewPageNum , setCurrentEditorViewPageNum] = useState(1); const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1); const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1); @@ -39,6 +46,8 @@ const HomePage =(props)=>{ const editorRef = useRef(null); + const useLocalStorage = false; + useEffect(()=>{ fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); }, []); @@ -72,8 +81,27 @@ const HomePage =(props)=>{ setCurrentBrewRendererPageNum(pageNumber); }; - const handleTextChange = (text)=>{ - setBrew((prevBrew) => ({ ...prevBrew, text })); + 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(Markdown.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 clearError = ()=>{ @@ -105,8 +133,8 @@ const HomePage =(props)=>{ { const editorRef = useRef(null); + const useLocalStorage = true; + useEffect(() => { document.addEventListener('keydown', handleControlKeys); loadBrew(); @@ -109,40 +112,27 @@ const NewPage = (props) => { setCurrentBrewRendererPageNum(pageNumber); }; - const handleTextChange = (text)=>{ + 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) - HTMLErrors = Markdown.validate(text); + if(HTMLErrors.length && (field == 'text' || field == 'snippets')) + setHTMLErrors(Markdown.validate(value)); - setHTMLErrors(HTMLErrors); - setCurrentBrew((prevBrew) => ({ ...prevBrew, text })); - localStorage.setItem(BREWKEY, text); - }; + if(field == 'metadata') setCurrentBrew(prev => ({ ...prev, ...value })); + else setCurrentBrew(prev => ({ ...prev, [field]: value })); - const handleStyleChange = (style) => { - setCurrentBrew(prevBrew => ({ ...prevBrew, style })); - localStorage.setItem(STYLEKEY, style); - }; - - const handleSnipChange = (snippet)=>{ - //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length) - HTMLErrors = Markdown.validate(snippet); - - setHTMLErrors(HTMLErrors); - setCurrentBrew((prevBrew) => ({ ...prevBrew, snippets: snippet })); - }; - - const handleMetaChange = (metadata, field = undefined) => { - if (field === 'theme' || field === 'renderer') - fetchThemeBundle(setError, setThemeBundle, metadata.renderer, metadata.theme); - - setCurrentBrew(prev => ({ ...prev, ...metadata })); - localStorage.setItem(METAKEY, JSON.stringify({ - renderer : metadata.renderer, - theme : metadata.theme, - lang : metadata.lang - })); + 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 save = async () => { @@ -216,10 +206,7 @@ const NewPage = (props) => { Date: Fri, 3 Oct 2025 13:41:53 +1300 Subject: [PATCH 8/8] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5206f4cbf..a33f2f073 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,9 @@ it using the two commands: 1. `npm install` 1. `npm start` -You should now be able to go to [http://localhost:8000](http://localhost:8000) -in your browser and use The Homebrewery offline. +When the Homebrewery server is started for the first time, it will modify the database to create the indexes required for better Homebrewery performance. This may take a few moments to complete for each index, dependent on how much content is in your local database - a brand new, empty database should be done in seconds. + +On completion, you should be able to go to [http://localhost:8000](http://localhost:8000) in your browser and use The Homebrewery offline. If you had any issue at all, here are some links that may be useful: - [Course](https://learn.mongodb.com/courses/m103-basic-cluster-administration) on cluster administration, useful for beginners @@ -145,3 +146,4 @@ your contribution to the project, please join our [gitter chat][gitter-url]. [github-pr-docs-url]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request [gitter-url]: https://gitter.im/naturalcrit/Lobby +