From 421c88cc07679aced4323809f762d87148dd1c31 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Thu, 5 Sep 2024 22:35:14 +1200 Subject: [PATCH 01/33] Save brew text/style to local storage --- client/homebrew/pages/editPage/editPage.jsx | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 39a6d1931..6aefec187 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -29,6 +29,15 @@ const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpe const googleDriveIcon = require('../../googleDrive.svg'); +const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' +const HISTORY_SAVE_DELAYS = [ + 1, // 1 minute + 30, // 30 minutes + 60, // 60 minutes + 24 * 60, // 24 hours + 5 * 24 * 60 // 5 days +]; + const SAVE_TIMEOUT = 3000; const EditPage = createClass({ @@ -202,6 +211,8 @@ const EditPage = createClass({ htmlErrors : Markdown.validate(prevState.brew.text) })); + this.updateHistory(); + const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); const brew = this.state.brew; @@ -233,6 +244,40 @@ const EditPage = createClass({ })); }, + updateHistory : function(){ + const historyKeys = []; + [1,2,3,4,5].forEach((i)=>{ + historyKeys.push(`${HISTORY_PREFIX}-${this.props.brew.shareId}-${i}`); + }); + historyKeys.forEach((key, i)=>{ + // console.log(i, key); + const storedVersion = localStorage.getItem(key); + // console.log(storedVersion); + if(!storedVersion){ + this.updateStoredBrew(key); + return; + } + const storedObject = JSON.parse(storedVersion); + let targetTime = new Date(storedObject.savedAt); + targetTime.setMinutes(targetTime.getMinutes() + HISTORY_SAVE_DELAYS[i]); + // console.log('UPDATE AT: ', targetTime); + if(new Date() >= targetTime){ + // console.log('Update Stored Brew:', i); + this.updateStoredBrew(key); + } + }); + }, + + updateStoredBrew : function (key){ + const archiveBrew = {}; + archiveBrew.title = this.state.brew.title; + archiveBrew.text = this.state.brew.text; + archiveBrew.style = this.state.brew.style; + archiveBrew.savedAt = new Date(); + localStorage.setItem(key, JSON.stringify(archiveBrew)); + return; + }, + renderGoogleDriveIcon : function(){ return Google Drive icon From 03bc9a81891e8859ce52cd14b50177073ef81cde Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Thu, 5 Sep 2024 23:24:14 +1200 Subject: [PATCH 02/33] Test of combined version and time differential requirement for update --- client/homebrew/pages/editPage/editPage.jsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 6aefec187..0bf4a7e83 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -37,6 +37,13 @@ const HISTORY_SAVE_DELAYS = [ 24 * 60, // 24 hours 5 * 24 * 60 // 5 days ]; +const HISTORY_VERSION_DIFFS = [ + 1, + 10, + 50, + 100, + 250 +] const SAVE_TIMEOUT = 3000; @@ -250,19 +257,19 @@ const EditPage = createClass({ historyKeys.push(`${HISTORY_PREFIX}-${this.props.brew.shareId}-${i}`); }); historyKeys.forEach((key, i)=>{ - // console.log(i, key); const storedVersion = localStorage.getItem(key); - // console.log(storedVersion); if(!storedVersion){ this.updateStoredBrew(key); return; } + const storedObject = JSON.parse(storedVersion); let targetTime = new Date(storedObject.savedAt); targetTime.setMinutes(targetTime.getMinutes() + HISTORY_SAVE_DELAYS[i]); - // console.log('UPDATE AT: ', targetTime); - if(new Date() >= targetTime){ - // console.log('Update Stored Brew:', i); + + const targetVersion = storedObject.version + HISTORY_VERSION_DIFFS[i]; + + if(new Date() >= targetTime && this.state.brew.version >= targetVersion){ this.updateStoredBrew(key); } }); @@ -274,6 +281,7 @@ const EditPage = createClass({ archiveBrew.text = this.state.brew.text; archiveBrew.style = this.state.brew.style; archiveBrew.savedAt = new Date(); + archiveBrew.version = this.state.brew.version; localStorage.setItem(key, JSON.stringify(archiveBrew)); return; }, From 9fd581149b7c8ceee65e099fe4c2757c336367e6 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Fri, 6 Sep 2024 16:57:11 +1200 Subject: [PATCH 03/33] Shift version history to separate file --- client/homebrew/pages/editPage/editPage.jsx | 55 ++------------------- client/homebrew/utils/versionHistory.js | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+), 52 deletions(-) create mode 100644 client/homebrew/utils/versionHistory.js diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 0bf4a7e83..004dd714a 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -27,23 +27,9 @@ const Markdown = require('naturalcrit/markdown.js'); const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js'); const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js'); -const googleDriveIcon = require('../../googleDrive.svg'); +import { updateHistory } from '../../utils/versionHistory.js'; -const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' -const HISTORY_SAVE_DELAYS = [ - 1, // 1 minute - 30, // 30 minutes - 60, // 60 minutes - 24 * 60, // 24 hours - 5 * 24 * 60 // 5 days -]; -const HISTORY_VERSION_DIFFS = [ - 1, - 10, - 50, - 100, - 250 -] +const googleDriveIcon = require('../../googleDrive.svg'); const SAVE_TIMEOUT = 3000; @@ -218,7 +204,7 @@ const EditPage = createClass({ htmlErrors : Markdown.validate(prevState.brew.text) })); - this.updateHistory(); + updateHistory(this.state.brew); const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); @@ -251,41 +237,6 @@ const EditPage = createClass({ })); }, - updateHistory : function(){ - const historyKeys = []; - [1,2,3,4,5].forEach((i)=>{ - historyKeys.push(`${HISTORY_PREFIX}-${this.props.brew.shareId}-${i}`); - }); - historyKeys.forEach((key, i)=>{ - const storedVersion = localStorage.getItem(key); - if(!storedVersion){ - this.updateStoredBrew(key); - return; - } - - const storedObject = JSON.parse(storedVersion); - let targetTime = new Date(storedObject.savedAt); - targetTime.setMinutes(targetTime.getMinutes() + HISTORY_SAVE_DELAYS[i]); - - const targetVersion = storedObject.version + HISTORY_VERSION_DIFFS[i]; - - if(new Date() >= targetTime && this.state.brew.version >= targetVersion){ - this.updateStoredBrew(key); - } - }); - }, - - updateStoredBrew : function (key){ - const archiveBrew = {}; - archiveBrew.title = this.state.brew.title; - archiveBrew.text = this.state.brew.text; - archiveBrew.style = this.state.brew.style; - archiveBrew.savedAt = new Date(); - archiveBrew.version = this.state.brew.version; - localStorage.setItem(key, JSON.stringify(archiveBrew)); - return; - }, - renderGoogleDriveIcon : function(){ return Google Drive icon diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js new file mode 100644 index 000000000..d0ab4532b --- /dev/null +++ b/client/homebrew/utils/versionHistory.js @@ -0,0 +1,50 @@ +const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' +const HISTORY_SAVE_DELAYS = [ + 1, // 1 minute + 30, // 30 minutes + 60, // 60 minutes + 24 * 60, // 24 hours + 5 * 24 * 60 // 5 days +]; +const HISTORY_VERSION_DIFFS = [ + 1, + 10, + 50, + 100, + 250 +] + +function updateStoredBrew(key, brew){ + const archiveBrew = {}; + archiveBrew.title = brew.title; + archiveBrew.text = brew.text; + archiveBrew.style = brew.style; + archiveBrew.savedAt = new Date(); + archiveBrew.version = brew.version; + localStorage.setItem(key, JSON.stringify(archiveBrew)); + return; +}; + +export function updateHistory(brew) { + const historyKeys = []; + [1,2,3,4,5].forEach((i)=>{ + historyKeys.push(`${HISTORY_PREFIX}-${brew.shareId}-${i}`); + }); + historyKeys.forEach((key, i)=>{ + const storedVersion = localStorage.getItem(key); + if(!storedVersion){ + updateStoredBrew(key, brew); + return; + } + + const storedObject = JSON.parse(storedVersion); + let targetTime = new Date(storedObject.savedAt); + targetTime.setMinutes(targetTime.getMinutes() + HISTORY_SAVE_DELAYS[i]); + + const targetVersion = storedObject.version + HISTORY_VERSION_DIFFS[i]; + + if(new Date() >= targetTime && brew.version >= targetVersion){ + updateStoredBrew(key, brew); + } + }); +}; From f3011eeef9bf7041feab90e0e6ccc48e63a4b87d Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Fri, 6 Sep 2024 16:58:30 +1200 Subject: [PATCH 04/33] Update delay amounts --- client/homebrew/utils/versionHistory.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index d0ab4532b..796078028 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -1,10 +1,10 @@ const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' const HISTORY_SAVE_DELAYS = [ - 1, // 1 minute - 30, // 30 minutes + 2, // 2 minutes + 10, // 10 minutes 60, // 60 minutes - 24 * 60, // 24 hours - 5 * 24 * 60 // 5 days + 12 * 60, // 12 hours + 2 * 24 * 60 // 2 days ]; const HISTORY_VERSION_DIFFS = [ 1, From 6693eebe6467129ffde7bc8be10de718df9fd36c Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 7 Sep 2024 08:47:53 +1200 Subject: [PATCH 05/33] Updated version saving logic --- client/homebrew/utils/versionHistory.js | 78 +++++++++++++++---------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 796078028..ad952f74b 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -1,50 +1,68 @@ -const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' -const HISTORY_SAVE_DELAYS = [ - 2, // 2 minutes - 10, // 10 minutes - 60, // 60 minutes - 12 * 60, // 12 hours - 2 * 24 * 60 // 2 days -]; -const HISTORY_VERSION_DIFFS = [ - 1, - 10, - 50, - 100, - 250 -] +export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' +// const HISTORY_SAVE_DELAYS = { +// 0: 0, // 0 minutes (if not specified) +// 1: 2, // 2 minutes +// 2: 10, // 10 minutes +// 3: 60, // 60 minutes +// 4: 12 * 60, // 12 hours +// 5: 2 * 24 * 60 // 2 days +// }; -function updateStoredBrew(key, brew){ +const HISTORY_SAVE_DELAYS = { + // Test values + 0: 0, // 0 minutes (if not specified) + 1: 1, // 1 minutes + 2: 2, // 2 minutes + 3: 3, // 3 minutes + 4: 4, // 4 minutes + 5: 5 // 5 minutes +}; + +function updateStoredBrew(key, brew, slot=0){ const archiveBrew = {}; + + // Data from brew to be stored archiveBrew.title = brew.title; archiveBrew.text = brew.text; archiveBrew.style = brew.style; - archiveBrew.savedAt = new Date(); archiveBrew.version = brew.version; + + // Calculated values + archiveBrew.savedAt = new Date(); + archiveBrew.expireAt = new Date(); + archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); + + // Store data localStorage.setItem(key, JSON.stringify(archiveBrew)); return; }; export function updateHistory(brew) { - const historyKeys = []; - [1,2,3,4,5].forEach((i)=>{ - historyKeys.push(`${HISTORY_PREFIX}-${brew.shareId}-${i}`); + const numbers = [1,2,3,4,5]; + const historyKeys = {}; + numbers.forEach((i)=>{ + historyKeys[i] = `${HISTORY_PREFIX}-${brew.shareId}-${i}`; }); - historyKeys.forEach((key, i)=>{ + numbers.toReversed().every((slot)=>{ + const key = historyKeys[slot]; const storedVersion = localStorage.getItem(key); + + // If no version stored at this key, update and break if(!storedVersion){ - updateStoredBrew(key, brew); - return; + console.log('Empty slot: ', slot); + updateStoredBrew(key, brew, slot); + return false; } - + + // Parse slot data const storedObject = JSON.parse(storedVersion); - let targetTime = new Date(storedObject.savedAt); - targetTime.setMinutes(targetTime.getMinutes() + HISTORY_SAVE_DELAYS[i]); - const targetVersion = storedObject.version + HISTORY_VERSION_DIFFS[i]; - - if(new Date() >= targetTime && brew.version >= targetVersion){ - updateStoredBrew(key, brew); + // If slot has expired, update + if(new Date() >= new Date(storedObject.expireAt)){ + console.log('Expired slot: ', slot); + updateStoredBrew(key, brew, slot); + return false; } + return true; }); }; From 87ba4ee2643c540dd3306c1ac06dbebea4980295 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 7 Sep 2024 13:07:58 +1200 Subject: [PATCH 06/33] Basic functionality working --- client/homebrew/utils/versionHistory.js | 59 +++++++++++++++++-------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index ad952f74b..1faac8869 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -8,8 +8,8 @@ export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' // 5: 2 * 24 * 60 // 2 days // }; +// Test values const HISTORY_SAVE_DELAYS = { - // Test values 0: 0, // 0 minutes (if not specified) 1: 1, // 1 minutes 2: 2, // 2 minutes @@ -18,7 +18,26 @@ const HISTORY_SAVE_DELAYS = { 5: 5 // 5 minutes }; -function updateStoredBrew(key, brew, slot=0){ +const DEFAULT_STORED_BREW = { + shareId : 'default_brew', + expireAt : '2000-01-01T00:00:00.000Z' +}; + +function getKeyBySlot(brew, slot){ + return `${HISTORY_PREFIX}-${brew.shareId}-${slot}`; +}; + +function getVersionBySlot(brew, slot){ + // Read stored brew data + // - If it exists, parse data to object + // - If it doesn't exist, pass default object + const key = getKeyBySlot(brew, slot); + const storedVersion = localStorage.getItem(key); + const output = storedVersion ? JSON.parse(storedVersion) : { ...DEFAULT_STORED_BREW, ...brew }; + return output; +}; + +function updateStoredBrew(brew, slot=0){ const archiveBrew = {}; // Data from brew to be stored @@ -26,6 +45,7 @@ function updateStoredBrew(key, brew, slot=0){ archiveBrew.text = brew.text; archiveBrew.style = brew.style; archiveBrew.version = brew.version; + archiveBrew.shareId = brew.shareId; // Calculated values archiveBrew.savedAt = new Date(); @@ -33,34 +53,37 @@ function updateStoredBrew(key, brew, slot=0){ archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); // Store data + const key = getKeyBySlot(brew, slot); localStorage.setItem(key, JSON.stringify(archiveBrew)); return; }; export function updateHistory(brew) { const numbers = [1,2,3,4,5]; - const historyKeys = {}; + const history = {}; + + // Load data from local storage to History object numbers.forEach((i)=>{ - historyKeys[i] = `${HISTORY_PREFIX}-${brew.shareId}-${i}`; + history[i] = getVersionBySlot(brew, i); }); + numbers.toReversed().every((slot)=>{ - const key = historyKeys[slot]; - const storedVersion = localStorage.getItem(key); - - // If no version stored at this key, update and break - if(!storedVersion){ - console.log('Empty slot: ', slot); - updateStoredBrew(key, brew, slot); - return false; - } - - // Parse slot data - const storedObject = JSON.parse(storedVersion); + const storedVersion = history[slot]; // If slot has expired, update - if(new Date() >= new Date(storedObject.expireAt)){ + if(new Date() >= new Date(storedVersion.expireAt)){ console.log('Expired slot: ', slot); - updateStoredBrew(key, brew, slot); + const keys = Array.from(Array(slot - 1).keys()); + keys.toReversed().every((n)=>{ + const num = n + 1; + updateStoredBrew({ ...history[num], shareId: brew.shareId }, num + 1); + if(num == 1) { + updateStoredBrew(brew, 1); + return false; + } + + return true; + }); return false; } return true; From 6ed6b6d66f600dae07f5ac54e16106cd64b32ac0 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 7 Sep 2024 13:10:36 +1200 Subject: [PATCH 07/33] Remove debugging line --- client/homebrew/utils/versionHistory.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 1faac8869..51fa062e3 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -72,7 +72,6 @@ export function updateHistory(brew) { // If slot has expired, update if(new Date() >= new Date(storedVersion.expireAt)){ - console.log('Expired slot: ', slot); const keys = Array.from(Array(slot - 1).keys()); keys.toReversed().every((n)=>{ const num = n + 1; From 4d295f5f18d4930733d9447cf47279c69c88eba5 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 7 Sep 2024 13:22:44 +1200 Subject: [PATCH 08/33] Add comments to elucidate the madness --- client/homebrew/utils/versionHistory.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 51fa062e3..5d0ce15c5 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -67,24 +67,30 @@ export function updateHistory(brew) { history[i] = getVersionBySlot(brew, i); }); + // Walk each version position numbers.toReversed().every((slot)=>{ const storedVersion = history[slot]; - // If slot has expired, update + // If slot has expired, update all lower slots and break if(new Date() >= new Date(storedVersion.expireAt)){ - const keys = Array.from(Array(slot - 1).keys()); - keys.toReversed().every((n)=>{ - const num = n + 1; - updateStoredBrew({ ...history[num], shareId: brew.shareId }, num + 1); - if(num == 1) { + const slots = Array.from(Array(slot - 1).keys()); + slots.toReversed().every((slot)=>{ + const actualSlot = slot + 1; + // Move data from actualSlot to actualSlot + 1 + updateStoredBrew({ ...history[actualSlot], shareId: brew.shareId }, actualSlot + 1); + // If first slot, fill with current data + if(actualSlot == 1) { updateStoredBrew(brew, 1); + // Break after updating first slot return false; } - + // Continue loop to move data in remaining slots return true; }); + // Break out of data checks because we found an expired value return false; } + // Continue data checks because this one wasn't expired return true; }); }; From 9679e5b1304f3b5c8c395e298d6fd129397b682e Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 7 Sep 2024 14:00:31 +1200 Subject: [PATCH 09/33] Add garbage collection function to remove version data after specified period without update --- client/homebrew/pages/editPage/editPage.jsx | 3 ++- client/homebrew/utils/versionHistory.js | 25 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 004dd714a..3ecf74d73 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -27,7 +27,7 @@ const Markdown = require('naturalcrit/markdown.js'); const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js'); const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js'); -import { updateHistory } from '../../utils/versionHistory.js'; +import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js'; const googleDriveIcon = require('../../googleDrive.svg'); @@ -205,6 +205,7 @@ const EditPage = createClass({ })); updateHistory(this.state.brew); + versionHistoryGarbageCollection(); const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 5d0ce15c5..c70edbe5d 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -7,6 +7,11 @@ export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' // 4: 12 * 60, // 12 hours // 5: 2 * 24 * 60 // 2 days // }; +// +// const GARBAGE_COLLECT_AT = 28 * 24 * 60; // 28 days + + +// <=================== TEST VALUES STARTS ===================> // Test values const HISTORY_SAVE_DELAYS = { @@ -17,6 +22,11 @@ const HISTORY_SAVE_DELAYS = { 4: 4, // 4 minutes 5: 5 // 5 minutes }; +const GARBAGE_COLLECT_DELAY = 10; // 10 minutes + +// <==================== TEST VALUES ENDS ====================> + + const DEFAULT_STORED_BREW = { shareId : 'default_brew', @@ -94,3 +104,18 @@ export function updateHistory(brew) { return true; }); }; + +export function versionHistoryGarbageCollection(){ + Object.keys(localStorage) + .filter((key)=>{ + return key.startsWith(HISTORY_PREFIX); + }) + .forEach((key)=>{ + const collectAt = new Date(JSON.parse(localStorage.getItem(key)).expireAt); + collectAt.setMinutes(collectAt.getMinutes() + GARBAGE_COLLECT_DELAY); + if(new Date() > collectAt){ + console.log('GARBAGE COLLECTION:', key); + localStorage.removeItem(key); + } + }); +}; \ No newline at end of file From cd30679aac4ba860b242785eef5b1d9aeb573950 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 7 Sep 2024 14:01:25 +1200 Subject: [PATCH 10/33] Remove debugging line --- client/homebrew/utils/versionHistory.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index c70edbe5d..ca47a1964 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -114,7 +114,6 @@ export function versionHistoryGarbageCollection(){ const collectAt = new Date(JSON.parse(localStorage.getItem(key)).expireAt); collectAt.setMinutes(collectAt.getMinutes() + GARBAGE_COLLECT_DELAY); if(new Date() > collectAt){ - console.log('GARBAGE COLLECTION:', key); localStorage.removeItem(key); } }); From bc35b5245b0711db30fdabde61f2c6a46b380726 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 7 Sep 2024 14:07:30 +1200 Subject: [PATCH 11/33] Fix renamed variable --- client/homebrew/utils/versionHistory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index ca47a1964..a49664c6c 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -8,7 +8,7 @@ export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' // 5: 2 * 24 * 60 // 2 days // }; // -// const GARBAGE_COLLECT_AT = 28 * 24 * 60; // 28 days +// const GARBAGE_COLLECT_DELAY = 28 * 24 * 60; // 28 days // <=================== TEST VALUES STARTS ===================> From c77d6e5faeff8de6b42c5692710e8c5289c50155 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 16:50:36 +1200 Subject: [PATCH 12/33] Simplify logic --- client/homebrew/utils/versionHistory.js | 135 +++++++++++------------- 1 file changed, 63 insertions(+), 72 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index a49664c6c..4b4f5873c 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -1,4 +1,4 @@ -export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' +export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY'; // const HISTORY_SAVE_DELAYS = { // 0: 0, // 0 minutes (if not specified) // 1: 2, // 2 minutes @@ -7,7 +7,7 @@ export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' // 4: 12 * 60, // 12 hours // 5: 2 * 24 * 60 // 2 days // }; -// +// // const GARBAGE_COLLECT_DELAY = 28 * 24 * 60; // 28 days @@ -15,12 +15,12 @@ export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY' // Test values const HISTORY_SAVE_DELAYS = { - 0: 0, // 0 minutes (if not specified) - 1: 1, // 1 minutes - 2: 2, // 2 minutes - 3: 3, // 3 minutes - 4: 4, // 4 minutes - 5: 5 // 5 minutes + 0 : 0, // 0 minutes (if not specified) + 1 : 1, // 1 minutes + 2 : 2, // 2 minutes + 3 : 3, // 3 minutes + 4 : 4, // 4 minutes + 5 : 5 // 5 minutes }; const GARBAGE_COLLECT_DELAY = 10; // 10 minutes @@ -29,92 +29,83 @@ const GARBAGE_COLLECT_DELAY = 10; // 10 minutes const DEFAULT_STORED_BREW = { - shareId : 'default_brew', - expireAt : '2000-01-01T00:00:00.000Z' + shareId : 'default_brew', + expireAt : '2000-01-01T00:00:00.000Z' }; function getKeyBySlot(brew, slot){ - return `${HISTORY_PREFIX}-${brew.shareId}-${slot}`; + return `${HISTORY_PREFIX}-${brew.shareId}-${slot}`; }; function getVersionBySlot(brew, slot){ - // Read stored brew data - // - If it exists, parse data to object - // - If it doesn't exist, pass default object - const key = getKeyBySlot(brew, slot); - const storedVersion = localStorage.getItem(key); - const output = storedVersion ? JSON.parse(storedVersion) : { ...DEFAULT_STORED_BREW, ...brew }; - return output; + // Read stored brew data + // - If it exists, parse data to object + // - If it doesn't exist, pass default object + const key = getKeyBySlot(brew, slot); + const storedVersion = localStorage.getItem(key); + const output = storedVersion ? JSON.parse(storedVersion) : { ...DEFAULT_STORED_BREW, ...brew }; + return output; }; function updateStoredBrew(brew, slot=0){ - const archiveBrew = {}; + const archiveBrew = {}; - // Data from brew to be stored - archiveBrew.title = brew.title; - archiveBrew.text = brew.text; - archiveBrew.style = brew.style; - archiveBrew.version = brew.version; - archiveBrew.shareId = brew.shareId; + // Data from brew to be stored + archiveBrew.title = brew.title; + archiveBrew.text = brew.text; + archiveBrew.style = brew.style; + archiveBrew.version = brew.version; + archiveBrew.shareId = brew.shareId; - // Calculated values - archiveBrew.savedAt = new Date(); - archiveBrew.expireAt = new Date(); - archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); - - // Store data - const key = getKeyBySlot(brew, slot); - localStorage.setItem(key, JSON.stringify(archiveBrew)); - return; + // Calculated values + archiveBrew.savedAt = brew?.savedAt || new Date(); + archiveBrew.expireAt = new Date(); + archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); + + // Store data + const key = getKeyBySlot(brew, slot); + localStorage.setItem(key, JSON.stringify(archiveBrew)); + return; }; export function updateHistory(brew) { - const numbers = [1,2,3,4,5]; - const history = {}; + const history = {}; - // Load data from local storage to History object - numbers.forEach((i)=>{ - history[i] = getVersionBySlot(brew, i); - }); + // Load data from local storage to History object + for (let i = 1; i<=5; i++){ + history[i] = getVersionBySlot(brew, i); + }; - // Walk each version position - numbers.toReversed().every((slot)=>{ - const storedVersion = history[slot]; + // Walk each version position + for (let slot = 5; slot>0; slot--){ + const storedVersion = history[slot]; - // If slot has expired, update all lower slots and break - if(new Date() >= new Date(storedVersion.expireAt)){ - const slots = Array.from(Array(slot - 1).keys()); - slots.toReversed().every((slot)=>{ - const actualSlot = slot + 1; - // Move data from actualSlot to actualSlot + 1 - updateStoredBrew({ ...history[actualSlot], shareId: brew.shareId }, actualSlot + 1); - // If first slot, fill with current data - if(actualSlot == 1) { - updateStoredBrew(brew, 1); - // Break after updating first slot - return false; - } - // Continue loop to move data in remaining slots - return true; - }); - // Break out of data checks because we found an expired value - return false; - } - // Continue data checks because this one wasn't expired - return true; - }); + // If slot has expired, update all lower slots and break + if(new Date() >= new Date(storedVersion.expireAt)){ + for (let updateSlot = slot - 1; updateSlot>0; updateSlot--){ + // Move data from updateSlot to updateSlot + 1 + updateStoredBrew({ ...history[updateSlot], shareId: brew.shareId }, updateSlot + 1); + }; + + // Update the most recent brew + updateStoredBrew(brew, 1); + + // Break out of data checks because we found an expired value + break; + } + }; }; export function versionHistoryGarbageCollection(){ - Object.keys(localStorage) + Object.keys(localStorage) .filter((key)=>{ - return key.startsWith(HISTORY_PREFIX); + return key.startsWith(HISTORY_PREFIX); }) .forEach((key)=>{ - const collectAt = new Date(JSON.parse(localStorage.getItem(key)).expireAt); - collectAt.setMinutes(collectAt.getMinutes() + GARBAGE_COLLECT_DELAY); - if(new Date() > collectAt){ - localStorage.removeItem(key); - } + const collectAt = new Date(JSON.parse(localStorage.getItem(key)).expireAt); + collectAt.setMinutes(collectAt.getMinutes() + GARBAGE_COLLECT_DELAY); + if(new Date() > collectAt){ + localStorage.removeItem(key); + } }); }; \ No newline at end of file From ace790739f43719dc895910aad169fc8068e9de1 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 17:32:07 +1200 Subject: [PATCH 13/33] Stub out History function on Editor Nav Bar --- client/homebrew/editor/snippetbar/snippetbar.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index f2680079e..714adc001 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -70,7 +70,7 @@ const Snippetbar = createClass({ mergeCustomizer : function(oldValue, newValue, key) { if(key == 'snippets') { const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme - return result.filter(snip => snip.gen || snip.subsnippets); + return result.filter((snip)=>snip.gen || snip.subsnippets); } }, @@ -138,6 +138,10 @@ const Snippetbar = createClass({ }); }, + showHistory : function () { + console.log('show history'); + }, + renderEditorButtons : function(){ if(!this.props.showEditButtons) return; @@ -158,6 +162,10 @@ const Snippetbar = createClass({ } return
+
+ +
From 48eb42862a6019f83e0f3a8b408d95943095c4a5 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 17:32:20 +1200 Subject: [PATCH 14/33] Add History styling --- client/homebrew/editor/snippetbar/snippetbar.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less index e0a24fac9..cfda8cd74 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.less +++ b/client/homebrew/editor/snippetbar/snippetbar.less @@ -53,6 +53,12 @@ font-size : 0.75em; color : inherit; } + &.history { + .tooltipLeft('History'); + font-size : 0.75em; + color : grey; + &.active { color : inherit; } + } &.editorTheme { .tooltipLeft('Editor Themes'); font-size : 0.75em; From d01548feb666381c0ccd72a029598b2e93196f43 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 21:20:31 +1200 Subject: [PATCH 15/33] Add getHistoryItems function --- .../homebrew/editor/snippetbar/snippetbar.jsx | 49 +++++++++++++++---- .../editor/snippetbar/snippetbar.less | 7 ++- client/homebrew/utils/versionHistory.js | 30 ++++++++++-- 3 files changed, 72 insertions(+), 14 deletions(-) diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 714adc001..cfe28991c 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -5,6 +5,8 @@ const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); +import { getHistoryItems, historyExists } from '../../utils/versionHistory.js'; + //Import all themes const ThemeSnippets = {}; ThemeSnippets['Legacy_5ePHB'] = require('themes/Legacy/5ePHB/snippets.js'); @@ -46,7 +48,9 @@ const Snippetbar = createClass({ return { renderer : this.props.renderer, themeSelector : false, - snippets : [] + snippets : [], + historyExists : false, + showHistory : false }; }, @@ -58,14 +62,21 @@ const Snippetbar = createClass({ }, componentDidUpdate : async function(prevProps) { - if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) { - const snippets = this.compileSnippets(); - this.setState({ - snippets : snippets - }); - } - }, + const update = {}; + let newData = false; + if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) { + update.snippets = this.compileSnippets(); + newData = true; + }; + + if(historyExists(this.props.brew) != this.state.historyExists){ + update.historyExists = !this.state.historyExists; + newData = true; + } + + newData && this.setState(update); + }, mergeCustomizer : function(oldValue, newValue, key) { if(key == 'snippets') { @@ -139,7 +150,24 @@ const Snippetbar = createClass({ }, showHistory : function () { - console.log('show history'); + if(!this.state.historyExists) return; + + this.setState({ + showHistory : !this.state.showHistory + }); + }, + + renderHistoryItems : function() { + const historyItems = getHistoryItems(this.props.brew); + + return
+ {_.map(historyItems, (item, index)=>{ + return
+ + {item.title} +
; + })} +
; }, renderEditorButtons : function(){ @@ -162,9 +190,10 @@ const Snippetbar = createClass({ } return
-
+ {this.state.showHistory && this.renderHistoryItems() }
diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less index cfda8cd74..0b38b6afd 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.less +++ b/client/homebrew/editor/snippetbar/snippetbar.less @@ -57,7 +57,12 @@ .tooltipLeft('History'); font-size : 0.75em; color : grey; - &.active { color : inherit; } + &.active { + color : inherit; + &>.dropdown { visibility: visible; } + } + &:hover>.dropdown { visibility: visible; } + } &.editorTheme { .tooltipLeft('Editor Themes'); diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 4b4f5873c..28be09234 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -1,4 +1,5 @@ export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY'; +export const HISTORY_SLOTS = 5; // const HISTORY_SAVE_DELAYS = { // 0: 0, // 0 minutes (if not specified) // 1: 2, // 2 minutes @@ -68,16 +69,29 @@ function updateStoredBrew(brew, slot=0){ return; }; -export function updateHistory(brew) { +export function historyExists(brew){ + return Object.keys(localStorage) + .some((key)=>{ + return key.startsWith(`${HISTORY_PREFIX}-${brew.shareId}`); + }); +} + +export function loadHistory(brew){ const history = {}; // Load data from local storage to History object - for (let i = 1; i<=5; i++){ + for (let i = 1; i <= HISTORY_SLOTS; i++){ history[i] = getVersionBySlot(brew, i); }; + return history; +} + +export function updateHistory(brew) { + const history = loadHistory(brew); + // Walk each version position - for (let slot = 5; slot>0; slot--){ + for (let slot = HISTORY_SLOTS; slot > 0; slot--){ const storedVersion = history[slot]; // If slot has expired, update all lower slots and break @@ -96,6 +110,16 @@ export function updateHistory(brew) { }; }; +export function getHistoryItems(brew){ + const historyArray = []; + + for (let i = 1; i <= HISTORY_SLOTS; i++){ + historyArray.push(getVersionBySlot(brew, i)); + } + + return historyArray; +}; + export function versionHistoryGarbageCollection(){ Object.keys(localStorage) .filter((key)=>{ From 719cc0c48585d42575c4a5a7f46c6ad42785525a Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 22:49:39 +1200 Subject: [PATCH 16/33] Remove onClick from UI --- .../homebrew/editor/snippetbar/snippetbar.jsx | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index cfe28991c..f282cd8b6 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -49,8 +49,7 @@ const Snippetbar = createClass({ renderer : this.props.renderer, themeSelector : false, snippets : [], - historyExists : false, - showHistory : false + historyExists : false }; }, @@ -149,22 +148,18 @@ const Snippetbar = createClass({ }); }, - showHistory : function () { - if(!this.state.historyExists) return; - - this.setState({ - showHistory : !this.state.showHistory - }); - }, - renderHistoryItems : function() { const historyItems = getHistoryItems(this.props.brew); return
{_.map(historyItems, (item, index)=>{ + const saveTime = new Date(item.savedAt); + const diffTime = new Date() - saveTime; + const diffMins = Math.floor(diffTime / (60 * 1000)); + return
- {item.title} + v{item.version} : about {diffMins} mins ago
; })}
; @@ -190,10 +185,9 @@ const Snippetbar = createClass({ } return
-
+
- {this.state.showHistory && this.renderHistoryItems() } + {this.state.historyExists && this.renderHistoryItems() }
From c4c5ffff9b3e113a6f3eeae65129a430c170623f Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 22:49:53 +1200 Subject: [PATCH 17/33] Tweak UI styling --- client/homebrew/editor/snippetbar/snippetbar.less | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less index 0b38b6afd..c2649a13c 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.less +++ b/client/homebrew/editor/snippetbar/snippetbar.less @@ -57,12 +57,13 @@ .tooltipLeft('History'); font-size : 0.75em; color : grey; + position : relative; &.active { color : inherit; - &>.dropdown { visibility: visible; } } - &:hover>.dropdown { visibility: visible; } - + &>.dropdown{ + right : -1px; + } } &.editorTheme { .tooltipLeft('Editor Themes'); From a7cf49557a8d4ceea2f4207e5ece6f3317782e21 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 23:04:18 +1200 Subject: [PATCH 18/33] Tweak dropdown padding --- client/homebrew/editor/snippetbar/snippetbar.less | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less index c2649a13c..c50d9df4c 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.less +++ b/client/homebrew/editor/snippetbar/snippetbar.less @@ -62,7 +62,10 @@ color : inherit; } &>.dropdown{ - right : -1px; + right : -1px; + &>.snippet{ + padding-right : 10px; + } } } &.editorTheme { From 7ec2558eefd61028b73b0ee4af2e1bd96904fb98 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 23:43:26 +1200 Subject: [PATCH 19/33] Add guard clause for history UI items --- client/homebrew/editor/snippetbar/snippetbar.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index f282cd8b6..4e5908c5a 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -153,6 +153,8 @@ const Snippetbar = createClass({ return
{_.map(historyItems, (item, index)=>{ + if(!item.savedAt) return; + const saveTime = new Date(item.savedAt); const diffTime = new Date() - saveTime; const diffMins = Math.floor(diffTime / (60 * 1000)); From ff19e3875e65d295337b4d72a31626d2d80405b0 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sat, 14 Sep 2024 23:43:44 +1200 Subject: [PATCH 20/33] Shift GC to use savedAt time --- client/homebrew/utils/versionHistory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 28be09234..4a3e9c8f7 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -126,7 +126,7 @@ export function versionHistoryGarbageCollection(){ return key.startsWith(HISTORY_PREFIX); }) .forEach((key)=>{ - const collectAt = new Date(JSON.parse(localStorage.getItem(key)).expireAt); + const collectAt = new Date(JSON.parse(localStorage.getItem(key)).savedAt); collectAt.setMinutes(collectAt.getMinutes() + GARBAGE_COLLECT_DELAY); if(new Date() > collectAt){ localStorage.removeItem(key); From 7009ef4441cae48066edfff9769becae10a595ec Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sun, 15 Sep 2024 00:47:54 +1200 Subject: [PATCH 21/33] Remove unneeded brew.shareId reference --- client/homebrew/utils/versionHistory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 4a3e9c8f7..28f8724b8 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -98,7 +98,7 @@ export function updateHistory(brew) { if(new Date() >= new Date(storedVersion.expireAt)){ for (let updateSlot = slot - 1; updateSlot>0; updateSlot--){ // Move data from updateSlot to updateSlot + 1 - updateStoredBrew({ ...history[updateSlot], shareId: brew.shareId }, updateSlot + 1); + updateStoredBrew(history[updateSlot], updateSlot + 1); }; // Update the most recent brew From b456bb955a344cea0b12d7f4ee142104e4e90b08 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sun, 15 Sep 2024 14:09:47 +1200 Subject: [PATCH 22/33] Initial UI functionality --- client/homebrew/editor/editor.jsx | 4 +++- .../homebrew/editor/snippetbar/snippetbar.jsx | 21 ++++++++++++++----- client/homebrew/pages/editPage/editPage.jsx | 11 ++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 24e975ebc..15aaffdfc 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -460,7 +460,9 @@ const Editor = createClass({ currentEditorTheme={this.state.editorTheme} updateEditorTheme={this.updateEditorTheme} snippetBundle={this.props.snippetBundle} - cursorPos={this.codeEditor.current?.getCursorPosition() || {}} /> + cursorPos={this.codeEditor.current?.getCursorPosition() || {}} + updateBrew={this.props.updateBrew} + /> {this.renderEditor()}
diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 4e5908c5a..fbab67d2f 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -40,7 +40,8 @@ const Snippetbar = createClass({ unfoldCode : ()=>{}, updateEditorTheme : ()=>{}, cursorPos : {}, - snippetBundle : [] + snippetBundle : [], + updateBrew : ()=>{} }; }, @@ -148,6 +149,10 @@ const Snippetbar = createClass({ }); }, + replaceContent : function(item){ + return this.props.updateBrew(item); + }, + renderHistoryItems : function() { const historyItems = getHistoryItems(this.props.brew); @@ -156,12 +161,18 @@ const Snippetbar = createClass({ if(!item.savedAt) return; const saveTime = new Date(item.savedAt); - const diffTime = new Date() - saveTime; - const diffMins = Math.floor(diffTime / (60 * 1000)); + const diffMs = new Date() - saveTime; + const diffMins = Math.floor(diffMs / (60 * 1000)); - return
+ let diffString = `about ${diffMins} minutes ago`; + + if(diffMins > 60) diffString = `about ${Math.floor(diffMins / 60)} hours ago`; + if(diffMins > (24 * 60)) diffString = `about ${Math.floor(diffMins / (24 * 60))} days ago`; + if(diffMins > (7 * 24 * 60)) diffString = `about ${Math.floor(diffMins / (7 * 24 * 60))} weeks ago`; + + return
{this.replaceContent(item);}} > - v{item.version} : about {diffMins} mins ago + v{item.version} : {diffString}
; })}
; diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 3ecf74d73..ffd7dfe33 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -152,6 +152,16 @@ const EditPage = createClass({ return !_.isEqual(this.state.brew, this.savedBrew); }, + updateBrew : function(newData){ + this.setState((prevState)=>({ + brew : { + ...prevState.brew, + style : newData.style, + text : newData.text + } + })); + }, + trySave : function(immediate=false){ if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT); if(this.hasChanges()){ @@ -418,6 +428,7 @@ const EditPage = createClass({ renderer={this.state.brew.renderer} userThemes={this.props.userThemes} snippetBundle={this.state.themeBundle.snippets} + updateBrew={this.updateBrew} /> Date: Sun, 15 Sep 2024 14:09:56 +1200 Subject: [PATCH 23/33] Lint fixes --- client/homebrew/editor/editor.jsx | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 15aaffdfc..d320f1eee 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -1,4 +1,4 @@ -/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ +/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ require('./editor.less'); const React = require('react'); const createClass = require('create-react-class'); @@ -88,9 +88,9 @@ const Editor = createClass({ if(!(e.ctrlKey && e.metaKey)) return; const LEFTARROW_KEY = 37; const RIGHTARROW_KEY = 39; - if (e.shiftKey && (e.keyCode == RIGHTARROW_KEY)) this.brewJump(); - if (e.shiftKey && (e.keyCode == LEFTARROW_KEY)) this.sourceJump(); - if ((e.keyCode == LEFTARROW_KEY) || (e.keyCode == RIGHTARROW_KEY)) { + if(e.shiftKey && (e.keyCode == RIGHTARROW_KEY)) this.brewJump(); + if(e.shiftKey && (e.keyCode == LEFTARROW_KEY)) this.sourceJump(); + if((e.keyCode == LEFTARROW_KEY) || (e.keyCode == RIGHTARROW_KEY)) { e.stopPropagation(); e.preventDefault(); } @@ -139,13 +139,13 @@ const Editor = createClass({ codeMirror.operation(()=>{ // Batch CodeMirror styling const foldLines = []; - + //reset custom text styles const customHighlights = codeMirror.getAllMarks().filter((mark)=>{ // Record details of folded sections if(mark.__isFold) { const fold = mark.find(); - foldLines.push({from: fold.from?.line, to: fold.to?.line}); + foldLines.push({ from: fold.from?.line, to: fold.to?.line }); } return !mark.__isFold; }); //Don't undo code folding @@ -163,7 +163,7 @@ const Editor = createClass({ // Don't process lines inside folded text // If the current lineNumber is inside any folded marks, skip line styling - if (foldLines.some(fold => lineNumber >= fold.from && lineNumber <= fold.to)) + if(foldLines.some((fold)=>lineNumber >= fold.from && lineNumber <= fold.to)) return; // Styling for \page breaks @@ -189,7 +189,7 @@ const Editor = createClass({ // definition lists if(line.includes('::')){ - if(/^:*$/.test(line) == true){ return }; + if(/^:*$/.test(line) == true){ return; }; const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error. let match; while ((match = regex.exec(line)) != null){ @@ -197,10 +197,10 @@ const Editor = createClass({ codeMirror.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' }); codeMirror.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' }); const ddIndex = match.indices[2][0]; - let colons = /::/g; - let colonMatches = colons.exec(match[2]); + const colons = /::/g; + const colonMatches = colons.exec(match[2]); if(colonMatches !== null){ - codeMirror.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight'} ) + codeMirror.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight' }); } } } @@ -210,12 +210,12 @@ const Editor = createClass({ let startIndex = line.indexOf('^'); const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy; const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; - + while (startIndex >= 0) { superRegex.lastIndex = subRegex.lastIndex = startIndex; let isSuper = false; - let match = subRegex.exec(line) || superRegex.exec(line); - if (match) { + const match = subRegex.exec(line) || superRegex.exec(line); + if(match) { isSuper = !subRegex.lastIndex; codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' }); } @@ -265,18 +265,18 @@ const Editor = createClass({ while (startIndex >= 0) { emojiRegex.lastIndex = startIndex; - let match = emojiRegex.exec(line); - if (match) { + const match = emojiRegex.exec(line); + if(match) { let tokens = Markdown.marked.lexer(match[0]); - tokens = tokens[0].tokens.filter(t => t.type == 'emoji') - if (!tokens.length) + tokens = tokens[0].tokens.filter((t)=>t.type == 'emoji'); + if(!tokens.length) return; - let startPos = { line: lineNumber, ch: match.index }; - let endPos = { line: lineNumber, ch: match.index + match[0].length }; + const startPos = { line: lineNumber, ch: match.index }; + const endPos = { line: lineNumber, ch: match.index + match[0].length }; // Iterate over conflicting marks and clear them - var marks = codeMirror.findMarks(startPos, endPos); + const marks = codeMirror.findMarks(startPos, endPos); marks.forEach(function(marker) { if(!marker.__isFold) marker.clear(); }); From 531e6efa5e6c4adf19e2c2002d6b063e4b254aec Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sun, 15 Sep 2024 14:19:24 +1200 Subject: [PATCH 24/33] Get configuration from config files --- client/homebrew/utils/versionHistory.js | 38 +- server/app.js | 1121 ++++++++++++----------- 2 files changed, 574 insertions(+), 585 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 28f8724b8..8aff4c9bb 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -1,39 +1,27 @@ export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY'; export const HISTORY_SLOTS = 5; -// const HISTORY_SAVE_DELAYS = { -// 0: 0, // 0 minutes (if not specified) -// 1: 2, // 2 minutes -// 2: 10, // 10 minutes -// 3: 60, // 60 minutes -// 4: 12 * 60, // 12 hours -// 5: 2 * 24 * 60 // 2 days -// }; -// -// const GARBAGE_COLLECT_DELAY = 28 * 24 * 60; // 28 days - -// <=================== TEST VALUES STARTS ===================> - -// Test values -const HISTORY_SAVE_DELAYS = { - 0 : 0, // 0 minutes (if not specified) - 1 : 1, // 1 minutes - 2 : 2, // 2 minutes - 3 : 3, // 3 minutes - 4 : 4, // 4 minutes - 5 : 5 // 5 minutes +const DEFAULT_HISTORY_SAVE_DELAYS = { + '0' : 0, // 0 minutes (if not specified) + '1' : 2, // 2 minutes + '2' : 10, // 10 minutes + '3' : 60, // 60 minutes + '4' : 12 * 60, // 12 hours + '5' : 2 * 24 * 60 // 2 days }; -const GARBAGE_COLLECT_DELAY = 10; // 10 minutes - -// <==================== TEST VALUES ENDS ====================> - +const DEFAULT_GARBAGE_COLLECT_DELAY = 28 * 24 * 60; // 28 days const DEFAULT_STORED_BREW = { shareId : 'default_brew', expireAt : '2000-01-01T00:00:00.000Z' }; +const HISTORY_SAVE_DELAYS = global.config?.historyData?.HISTORY_SAVE_DELAYS ?? DEFAULT_HISTORY_SAVE_DELAYS; +const GARBAGE_COLLECT_DELAY = global.config?.historyData?.GARBAGE_COLLECT_DELAY ?? DEFAULT_GARBAGE_COLLECT_DELAY; + + + function getKeyBySlot(brew, slot){ return `${HISTORY_PREFIX}-${brew.shareId}-${slot}`; }; diff --git a/server/app.js b/server/app.js index c03fb2dc2..0ac2d28f3 100644 --- a/server/app.js +++ b/server/app.js @@ -1,560 +1,561 @@ -/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ -// Set working directory to project root -process.chdir(`${__dirname}/..`); - -const _ = require('lodash'); -const jwt = require('jwt-simple'); -const express = require('express'); -const yaml = require('js-yaml'); -const app = express(); -const config = require('./config.js'); - -const { homebrewApi, getBrew, getUsersBrewThemes, getCSS } = require('./homebrew.api.js'); -const GoogleActions = require('./googleActions.js'); -const serveCompressedStaticAssets = require('./static-assets.mv.js'); -const sanitizeFilename = require('sanitize-filename'); -const asyncHandler = require('express-async-handler'); -const templateFn = require('./../client/template.js'); - -const { DEFAULT_BREW } = require('./brewDefaults.js'); - -const { splitTextStyleAndMetadata } = require('../shared/helpers.js'); - - -const sanitizeBrew = (brew, accessType)=>{ - brew._id = undefined; - brew.__v = undefined; - if(accessType !== 'edit' && accessType !== 'shareAuthor') { - brew.editId = undefined; - } - return brew; -}; - -app.use('/', serveCompressedStaticAssets(`build`)); -app.use(require('./middleware/content-negotiation.js')); -app.use(require('body-parser').json({ limit: '25mb' })); -app.use(require('cookie-parser')()); -app.use(require('./forcessl.mw.js')); - -//Account Middleware -app.use((req, res, next)=>{ - if(req.cookies && req.cookies.nc_session){ - try { - req.account = jwt.decode(req.cookies.nc_session, config.get('secret')); - //console.log("Just loaded up JWT from cookie:"); - //console.log(req.account); - } catch (e){} - } - - req.config = { - google_client_id : config.get('google_client_id'), - google_client_secret : config.get('google_client_secret') - }; - return next(); -}); - -app.use(homebrewApi); -app.use(require('./admin.api.js')); -app.use(require('./vault.api.js')); - -const HomebrewModel = require('./homebrew.model.js').model; -const welcomeText = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg.md', 'utf8'); -const welcomeTextLegacy = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg_legacy.md', 'utf8'); -const migrateText = require('fs').readFileSync('client/homebrew/pages/homePage/migrate.md', 'utf8'); -const changelogText = require('fs').readFileSync('changelog.md', 'utf8'); -const faqText = require('fs').readFileSync('faq.md', 'utf8'); - -String.prototype.replaceAll = function(s, r){return this.split(s).join(r);}; - -const defaultMetaTags = { - site_name : 'The Homebrewery - Make your Homebrew content look legit!', - title : 'The Homebrewery', - description : 'A NaturalCrit Tool for creating authentic Homebrews using Markdown.', - image : `${config.get('publicUrl')}/thumbnail.png`, - type : 'website' -}; - -//Robots.txt -app.get('/robots.txt', (req, res)=>{ - return res.sendFile(`robots.txt`, { root: process.cwd() }); -}); - -//Home page -app.get('/', (req, res, next)=>{ - req.brew = { - text : welcomeText, - renderer : 'V3', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'Homepage', - description : 'Homepage' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//Home page Legacy -app.get('/legacy', (req, res, next)=>{ - req.brew = { - text : welcomeTextLegacy, - renderer : 'legacy', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'Homepage (Legacy)', - description : 'Homepage' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//Legacy/Other Document -> v3 Migration Guide -app.get('/migrate', (req, res, next)=>{ - req.brew = { - text : migrateText, - renderer : 'V3', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'v3 Migration Guide', - description : 'A brief guide to converting Legacy documents to the v3 renderer.' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//Changelog page -app.get('/changelog', async (req, res, next)=>{ - req.brew = { - title : 'Changelog', - text : changelogText, - renderer : 'V3', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'Changelog', - description : 'Development changelog.' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//FAQ page -app.get('/faq', async (req, res, next)=>{ - req.brew = { - title : 'FAQ', - text : faqText, - renderer : 'V3', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'FAQ', - description : 'Frequently Asked Questions' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//Source page -app.get('/source/:id', asyncHandler(getBrew('share')), (req, res)=>{ - const { brew } = req; - - const replaceStrings = { '&': '&', '<': '<', '>': '>' }; - let text = brew.text; - for (const replaceStr in replaceStrings) { - text = text.replaceAll(replaceStr, replaceStrings[replaceStr]); - } - text = `
${text}
`; - res.status(200).send(text); -}); - -//Download brew source page -app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{ - const { brew } = req; - sanitizeBrew(brew, 'share'); - const prefix = 'HB - '; - - const encodeRFC3986ValueChars = (str)=>{ - return ( - encodeURIComponent(str) - .replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;}) - ); - }; - - let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); - if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; }; - res.set({ - 'Cache-Control' : 'no-cache', - 'Content-Type' : 'text/plain', - 'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt` - }); - res.status(200).send(brew.text); -}); - -//Serve brew metadata -app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res) => { - const { brew } = req; - sanitizeBrew(brew, 'share'); - - const fields = [ 'title', 'pageCount', 'description', 'authors', 'lang', - 'published', 'views', 'shareId', 'createdAt', 'updatedAt', - 'lastViewed', 'thumbnail', 'tags' - ]; - - const metadata = fields.reduce((acc, field) => { - if (brew[field] !== undefined) acc[field] = brew[field]; - return acc; - }, {}); - res.status(200).json(metadata); -}); - -//Serve brew styling -app.get('/css/:id', asyncHandler(getBrew('share')), (req, res)=>{getCSS(req, res);}); - -//User Page -app.get('/user/:username', async (req, res, next)=>{ - const ownAccount = req.account && (req.account.username == req.params.username); - - req.ogMeta = { ...defaultMetaTags, - title : `${req.params.username}'s Collection`, - description : 'View my collection of homebrew on the Homebrewery.' - // type : could be 'profile'? - }; - - const fields = [ - 'googleId', - 'title', - 'pageCount', - 'description', - 'authors', - 'lang', - 'published', - 'views', - 'shareId', - 'editId', - 'createdAt', - 'updatedAt', - 'lastViewed', - 'thumbnail', - 'tags' - ]; - - let brews = await HomebrewModel.getByUser(req.params.username, ownAccount, fields) - .catch((err)=>{ - console.log(err); - }); - - if(ownAccount && req?.account?.googleId){ - const auth = await GoogleActions.authCheck(req.account, res); - let googleBrews = await GoogleActions.listGoogleBrews(auth) - .catch((err)=>{ - console.error(err); - }); - - if(googleBrews && googleBrews.length > 0) { - for (const brew of brews.filter((brew)=>brew.googleId)) { - const match = googleBrews.findIndex((b)=>b.editId === brew.editId); - if(match !== -1) { - brew.googleId = googleBrews[match].googleId; - brew.stubbed = true; - brew.pageCount = googleBrews[match].pageCount; - brew.renderer = googleBrews[match].renderer; - brew.version = googleBrews[match].version; - brew.webViewLink = googleBrews[match].webViewLink; - googleBrews.splice(match, 1); - } - } - - googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] })); - brews = _.concat(brews, googleBrews); - } - } - - req.brews = _.map(brews, (brew)=>{ - // Clean up brew data - brew.title = brew.title?.trim(); - brew.description = brew.description?.trim(); - return sanitizeBrew(brew, ownAccount ? 'edit' : 'share'); - }); - - return next(); -}); - -//Edit Page -app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, next)=>{ - req.brew = req.brew.toObject ? req.brew.toObject() : req.brew; - - req.userThemes = await(getUsersBrewThemes(req.account?.username)); - - req.ogMeta = { ...defaultMetaTags, - title : req.brew.title || 'Untitled Brew', - description : req.brew.description || 'No description.', - image : req.brew.thumbnail || defaultMetaTags.image, - type : 'article' - }; - - sanitizeBrew(req.brew, 'edit'); - splitTextStyleAndMetadata(req.brew); - res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save. - return next(); -})); - -//New Page from ID -app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res, next)=>{ - sanitizeBrew(req.brew, 'share'); - splitTextStyleAndMetadata(req.brew); - const brew = { - shareId : req.brew.shareId, - title : `CLONE - ${req.brew.title}`, - text : req.brew.text, - style : req.brew.style, - renderer : req.brew.renderer, - theme : req.brew.theme, - tags : req.brew.tags, - }; - req.brew = _.defaults(brew, DEFAULT_BREW); - - req.userThemes = await(getUsersBrewThemes(req.account?.username)); - - req.ogMeta = { ...defaultMetaTags, - title : 'New', - description : 'Start crafting your homebrew on the Homebrewery!' - }; - - return next(); -})); - -//New Page -app.get('/new', asyncHandler(async(req, res, next)=>{ - req.userThemes = await(getUsersBrewThemes(req.account?.username)); - - req.ogMeta = { ...defaultMetaTags, - title : 'New', - description : 'Start crafting your homebrew on the Homebrewery!' - }; - - return next(); -})); - -//Share Page -app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ - const { brew } = req; - req.ogMeta = { ...defaultMetaTags, - title : req.brew.title || 'Untitled Brew', - description : req.brew.description || 'No description.', - image : req.brew.thumbnail || defaultMetaTags.image, - type : 'article' - }; - - // increase visitor view count, do not include visits by author(s) - if(!brew.authors.includes(req.account?.username)){ - if(req.params.id.length > 12 && !brew._id) { - const googleId = brew.googleId; - const shareId = brew.shareId; - await GoogleActions.increaseView(googleId, shareId, 'share', brew) - .catch((err)=>{next(err);}); - } else { - await HomebrewModel.increaseView({ shareId: brew.shareId }); - } - }; - - brew.authors.includes(req.account?.username) ? sanitizeBrew(req.brew, 'shareAuthor') : sanitizeBrew(req.brew, 'share'); - splitTextStyleAndMetadata(req.brew); - return next(); -})); - -//Account Page -app.get('/account', asyncHandler(async (req, res, next)=>{ - const data = {}; - data.title = 'Account Information Page'; - - if(!req.account) { - res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); - const error = new Error('No valid account'); - error.status = 401; - error.HBErrorCode = '50'; - error.page = data.title; - return next(error); - }; - - let auth; - let googleCount = []; - if(req.account) { - if(req.account.googleId) { - try { - auth = await GoogleActions.authCheck(req.account, res, false); - } catch (e) { - auth = undefined; - console.log('Google auth check failed!'); - console.log(e); - } - if(auth.credentials.access_token) { - try { - googleCount = await GoogleActions.listGoogleBrews(auth); - } catch (e) { - googleCount = undefined; - console.log('List Google files failed!'); - console.log(e); - } - } - } - - const query = { authors: req.account.username, googleId: { $exists: false } }; - const mongoCount = await HomebrewModel.countDocuments(query) - .catch((err)=>{ - mongoCount = 0; - console.log(err); - }); - - data.accountDetails = { - username : req.account.username, - issued : req.account.issued, - googleId : Boolean(req.account.googleId), - authCheck : Boolean(req.account.googleId && auth.credentials.access_token), - mongoCount : mongoCount, - googleCount : googleCount?.length - }; - } - - req.brew = data; - - req.ogMeta = { ...defaultMetaTags, - title : `Account Page`, - description : null - }; - - return next(); -})); - -const nodeEnv = config.get('node_env'); -const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); -// Local only -if(isLocalEnvironment){ - // Login - app.post('/local/login', (req, res)=>{ - const username = req.body.username; - if(!username) return; - - const payload = jwt.encode({ username: username, issued: new Date }, config.get('secret')); - return res.json(payload); - }); -} - -//Vault Page -app.get('/vault', asyncHandler(async(req, res, next)=>{ - req.ogMeta = { ...defaultMetaTags, - title : 'The Vault', - description : 'Search for Brews' - }; - return next(); -})); - -//Send rendered page -app.use(asyncHandler(async (req, res, next)=>{ - if (!req.route) return res.redirect('/'); // Catch-all for invalid routes - - const page = await renderPage(req, res); - if(!page) return; - res.send(page); -})); - -//Render the page -const renderPage = async (req, res)=>{ - // Create configuration object - const configuration = { - local : isLocalEnvironment, - publicUrl : config.get('publicUrl') ?? '', - environment : nodeEnv - }; - const props = { - version : require('./../package.json').version, - url : req.customUrl || req.originalUrl, - brew : req.brew, - brews : req.brews, - googleBrews : req.googleBrews, - account : req.account, - enable_v3 : config.get('enable_v3'), - enable_themes : config.get('enable_themes'), - config : configuration, - ogMeta : req.ogMeta, - userThemes : req.userThemes - }; - const title = req.brew ? req.brew.title : ''; - const page = await templateFn('homebrew', title, props) - .catch((err)=>{ - console.log(err); - }); - return page; -}; - -//v=====----- Error-Handling Middleware -----=====v// -//Format Errors as plain objects so all fields will appear in the string sent -const formatErrors = (key, value)=>{ - if(value instanceof Error) { - const error = {}; - Object.getOwnPropertyNames(value).forEach(function (key) { - error[key] = value[key]; - }); - return error; - } - return value; -}; - -const getPureError = (error)=>{ - return JSON.parse(JSON.stringify(error, formatErrors)); -}; - -app.use(async (err, req, res, next)=>{ - err.originalUrl = req.originalUrl; - console.error(err); - - if(err.originalUrl?.startsWith('/api/')) { - // console.log('API error'); - res.status(err.status || err.response?.status || 500).send(err); - return; - } - - // console.log('non-API error'); - const status = err.status || err.code || 500; - - req.ogMeta = { ...defaultMetaTags, - title : 'Error Page', - description : 'Something went wrong!' - }; - req.brew = { - ...err, - title : 'Error - Something went wrong!', - text : err.errors?.map((error)=>{return error.message;}).join('\n\n') || err.message || 'Unknown error!', - status : status, - HBErrorCode : err.HBErrorCode ?? '00', - pureError : getPureError(err) - }; - req.customUrl= '/error'; - - const page = await renderPage(req, res); - if(!page) return; - res.send(page); -}); - -app.use((req, res)=>{ - if(!res.headersSent) { - console.error('Headers have not been sent, responding with a server error.', req.url); - res.status(500).send('An error occurred and the server did not send a response. The error has been logged, please note the time this occurred and report this issue.'); - } -}); -//^=====--------------------------------------=====^// - -module.exports = { - app : app -}; +/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ +// Set working directory to project root +process.chdir(`${__dirname}/..`); + +const _ = require('lodash'); +const jwt = require('jwt-simple'); +const express = require('express'); +const yaml = require('js-yaml'); +const app = express(); +const config = require('./config.js'); + +const { homebrewApi, getBrew, getUsersBrewThemes, getCSS } = require('./homebrew.api.js'); +const GoogleActions = require('./googleActions.js'); +const serveCompressedStaticAssets = require('./static-assets.mv.js'); +const sanitizeFilename = require('sanitize-filename'); +const asyncHandler = require('express-async-handler'); +const templateFn = require('./../client/template.js'); + +const { DEFAULT_BREW } = require('./brewDefaults.js'); + +const { splitTextStyleAndMetadata } = require('../shared/helpers.js'); + + +const sanitizeBrew = (brew, accessType)=>{ + brew._id = undefined; + brew.__v = undefined; + if(accessType !== 'edit' && accessType !== 'shareAuthor') { + brew.editId = undefined; + } + return brew; +}; + +app.use('/', serveCompressedStaticAssets(`build`)); +app.use(require('./middleware/content-negotiation.js')); +app.use(require('body-parser').json({ limit: '25mb' })); +app.use(require('cookie-parser')()); +app.use(require('./forcessl.mw.js')); + +//Account Middleware +app.use((req, res, next)=>{ + if(req.cookies && req.cookies.nc_session){ + try { + req.account = jwt.decode(req.cookies.nc_session, config.get('secret')); + //console.log("Just loaded up JWT from cookie:"); + //console.log(req.account); + } catch (e){} + } + + req.config = { + google_client_id : config.get('google_client_id'), + google_client_secret : config.get('google_client_secret') + }; + return next(); +}); + +app.use(homebrewApi); +app.use(require('./admin.api.js')); +app.use(require('./vault.api.js')); + +const HomebrewModel = require('./homebrew.model.js').model; +const welcomeText = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg.md', 'utf8'); +const welcomeTextLegacy = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg_legacy.md', 'utf8'); +const migrateText = require('fs').readFileSync('client/homebrew/pages/homePage/migrate.md', 'utf8'); +const changelogText = require('fs').readFileSync('changelog.md', 'utf8'); +const faqText = require('fs').readFileSync('faq.md', 'utf8'); + +String.prototype.replaceAll = function(s, r){return this.split(s).join(r);}; + +const defaultMetaTags = { + site_name : 'The Homebrewery - Make your Homebrew content look legit!', + title : 'The Homebrewery', + description : 'A NaturalCrit Tool for creating authentic Homebrews using Markdown.', + image : `${config.get('publicUrl')}/thumbnail.png`, + type : 'website' +}; + +//Robots.txt +app.get('/robots.txt', (req, res)=>{ + return res.sendFile(`robots.txt`, { root: process.cwd() }); +}); + +//Home page +app.get('/', (req, res, next)=>{ + req.brew = { + text : welcomeText, + renderer : 'V3', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'Homepage', + description : 'Homepage' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//Home page Legacy +app.get('/legacy', (req, res, next)=>{ + req.brew = { + text : welcomeTextLegacy, + renderer : 'legacy', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'Homepage (Legacy)', + description : 'Homepage' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//Legacy/Other Document -> v3 Migration Guide +app.get('/migrate', (req, res, next)=>{ + req.brew = { + text : migrateText, + renderer : 'V3', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'v3 Migration Guide', + description : 'A brief guide to converting Legacy documents to the v3 renderer.' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//Changelog page +app.get('/changelog', async (req, res, next)=>{ + req.brew = { + title : 'Changelog', + text : changelogText, + renderer : 'V3', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'Changelog', + description : 'Development changelog.' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//FAQ page +app.get('/faq', async (req, res, next)=>{ + req.brew = { + title : 'FAQ', + text : faqText, + renderer : 'V3', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'FAQ', + description : 'Frequently Asked Questions' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//Source page +app.get('/source/:id', asyncHandler(getBrew('share')), (req, res)=>{ + const { brew } = req; + + const replaceStrings = { '&': '&', '<': '<', '>': '>' }; + let text = brew.text; + for (const replaceStr in replaceStrings) { + text = text.replaceAll(replaceStr, replaceStrings[replaceStr]); + } + text = `
${text}
`; + res.status(200).send(text); +}); + +//Download brew source page +app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{ + const { brew } = req; + sanitizeBrew(brew, 'share'); + const prefix = 'HB - '; + + const encodeRFC3986ValueChars = (str)=>{ + return ( + encodeURIComponent(str) + .replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;}) + ); + }; + + let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); + if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; }; + res.set({ + 'Cache-Control' : 'no-cache', + 'Content-Type' : 'text/plain', + 'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt` + }); + res.status(200).send(brew.text); +}); + +//Serve brew metadata +app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res) => { + const { brew } = req; + sanitizeBrew(brew, 'share'); + + const fields = [ 'title', 'pageCount', 'description', 'authors', 'lang', + 'published', 'views', 'shareId', 'createdAt', 'updatedAt', + 'lastViewed', 'thumbnail', 'tags' + ]; + + const metadata = fields.reduce((acc, field) => { + if (brew[field] !== undefined) acc[field] = brew[field]; + return acc; + }, {}); + res.status(200).json(metadata); +}); + +//Serve brew styling +app.get('/css/:id', asyncHandler(getBrew('share')), (req, res)=>{getCSS(req, res);}); + +//User Page +app.get('/user/:username', async (req, res, next)=>{ + const ownAccount = req.account && (req.account.username == req.params.username); + + req.ogMeta = { ...defaultMetaTags, + title : `${req.params.username}'s Collection`, + description : 'View my collection of homebrew on the Homebrewery.' + // type : could be 'profile'? + }; + + const fields = [ + 'googleId', + 'title', + 'pageCount', + 'description', + 'authors', + 'lang', + 'published', + 'views', + 'shareId', + 'editId', + 'createdAt', + 'updatedAt', + 'lastViewed', + 'thumbnail', + 'tags' + ]; + + let brews = await HomebrewModel.getByUser(req.params.username, ownAccount, fields) + .catch((err)=>{ + console.log(err); + }); + + if(ownAccount && req?.account?.googleId){ + const auth = await GoogleActions.authCheck(req.account, res); + let googleBrews = await GoogleActions.listGoogleBrews(auth) + .catch((err)=>{ + console.error(err); + }); + + if(googleBrews && googleBrews.length > 0) { + for (const brew of brews.filter((brew)=>brew.googleId)) { + const match = googleBrews.findIndex((b)=>b.editId === brew.editId); + if(match !== -1) { + brew.googleId = googleBrews[match].googleId; + brew.stubbed = true; + brew.pageCount = googleBrews[match].pageCount; + brew.renderer = googleBrews[match].renderer; + brew.version = googleBrews[match].version; + brew.webViewLink = googleBrews[match].webViewLink; + googleBrews.splice(match, 1); + } + } + + googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] })); + brews = _.concat(brews, googleBrews); + } + } + + req.brews = _.map(brews, (brew)=>{ + // Clean up brew data + brew.title = brew.title?.trim(); + brew.description = brew.description?.trim(); + return sanitizeBrew(brew, ownAccount ? 'edit' : 'share'); + }); + + return next(); +}); + +//Edit Page +app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, next)=>{ + req.brew = req.brew.toObject ? req.brew.toObject() : req.brew; + + req.userThemes = await(getUsersBrewThemes(req.account?.username)); + + req.ogMeta = { ...defaultMetaTags, + title : req.brew.title || 'Untitled Brew', + description : req.brew.description || 'No description.', + image : req.brew.thumbnail || defaultMetaTags.image, + type : 'article' + }; + + sanitizeBrew(req.brew, 'edit'); + splitTextStyleAndMetadata(req.brew); + res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save. + return next(); +})); + +//New Page from ID +app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res, next)=>{ + sanitizeBrew(req.brew, 'share'); + splitTextStyleAndMetadata(req.brew); + const brew = { + shareId : req.brew.shareId, + title : `CLONE - ${req.brew.title}`, + text : req.brew.text, + style : req.brew.style, + renderer : req.brew.renderer, + theme : req.brew.theme, + tags : req.brew.tags, + }; + req.brew = _.defaults(brew, DEFAULT_BREW); + + req.userThemes = await(getUsersBrewThemes(req.account?.username)); + + req.ogMeta = { ...defaultMetaTags, + title : 'New', + description : 'Start crafting your homebrew on the Homebrewery!' + }; + + return next(); +})); + +//New Page +app.get('/new', asyncHandler(async(req, res, next)=>{ + req.userThemes = await(getUsersBrewThemes(req.account?.username)); + + req.ogMeta = { ...defaultMetaTags, + title : 'New', + description : 'Start crafting your homebrew on the Homebrewery!' + }; + + return next(); +})); + +//Share Page +app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ + const { brew } = req; + req.ogMeta = { ...defaultMetaTags, + title : req.brew.title || 'Untitled Brew', + description : req.brew.description || 'No description.', + image : req.brew.thumbnail || defaultMetaTags.image, + type : 'article' + }; + + // increase visitor view count, do not include visits by author(s) + if(!brew.authors.includes(req.account?.username)){ + if(req.params.id.length > 12 && !brew._id) { + const googleId = brew.googleId; + const shareId = brew.shareId; + await GoogleActions.increaseView(googleId, shareId, 'share', brew) + .catch((err)=>{next(err);}); + } else { + await HomebrewModel.increaseView({ shareId: brew.shareId }); + } + }; + + brew.authors.includes(req.account?.username) ? sanitizeBrew(req.brew, 'shareAuthor') : sanitizeBrew(req.brew, 'share'); + splitTextStyleAndMetadata(req.brew); + return next(); +})); + +//Account Page +app.get('/account', asyncHandler(async (req, res, next)=>{ + const data = {}; + data.title = 'Account Information Page'; + + if(!req.account) { + res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); + const error = new Error('No valid account'); + error.status = 401; + error.HBErrorCode = '50'; + error.page = data.title; + return next(error); + }; + + let auth; + let googleCount = []; + if(req.account) { + if(req.account.googleId) { + try { + auth = await GoogleActions.authCheck(req.account, res, false); + } catch (e) { + auth = undefined; + console.log('Google auth check failed!'); + console.log(e); + } + if(auth.credentials.access_token) { + try { + googleCount = await GoogleActions.listGoogleBrews(auth); + } catch (e) { + googleCount = undefined; + console.log('List Google files failed!'); + console.log(e); + } + } + } + + const query = { authors: req.account.username, googleId: { $exists: false } }; + const mongoCount = await HomebrewModel.countDocuments(query) + .catch((err)=>{ + mongoCount = 0; + console.log(err); + }); + + data.accountDetails = { + username : req.account.username, + issued : req.account.issued, + googleId : Boolean(req.account.googleId), + authCheck : Boolean(req.account.googleId && auth.credentials.access_token), + mongoCount : mongoCount, + googleCount : googleCount?.length + }; + } + + req.brew = data; + + req.ogMeta = { ...defaultMetaTags, + title : `Account Page`, + description : null + }; + + return next(); +})); + +const nodeEnv = config.get('node_env'); +const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); +// Local only +if(isLocalEnvironment){ + // Login + app.post('/local/login', (req, res)=>{ + const username = req.body.username; + if(!username) return; + + const payload = jwt.encode({ username: username, issued: new Date }, config.get('secret')); + return res.json(payload); + }); +} + +//Vault Page +app.get('/vault', asyncHandler(async(req, res, next)=>{ + req.ogMeta = { ...defaultMetaTags, + title : 'The Vault', + description : 'Search for Brews' + }; + return next(); +})); + +//Send rendered page +app.use(asyncHandler(async (req, res, next)=>{ + if (!req.route) return res.redirect('/'); // Catch-all for invalid routes + + const page = await renderPage(req, res); + if(!page) return; + res.send(page); +})); + +//Render the page +const renderPage = async (req, res)=>{ + // Create configuration object + const configuration = { + local : isLocalEnvironment, + publicUrl : config.get('publicUrl') ?? '', + environment : nodeEnv, + history : config.get('historyConfig') ?? {} + }; + const props = { + version : require('./../package.json').version, + url : req.customUrl || req.originalUrl, + brew : req.brew, + brews : req.brews, + googleBrews : req.googleBrews, + account : req.account, + enable_v3 : config.get('enable_v3'), + enable_themes : config.get('enable_themes'), + config : configuration, + ogMeta : req.ogMeta, + userThemes : req.userThemes + }; + const title = req.brew ? req.brew.title : ''; + const page = await templateFn('homebrew', title, props) + .catch((err)=>{ + console.log(err); + }); + return page; +}; + +//v=====----- Error-Handling Middleware -----=====v// +//Format Errors as plain objects so all fields will appear in the string sent +const formatErrors = (key, value)=>{ + if(value instanceof Error) { + const error = {}; + Object.getOwnPropertyNames(value).forEach(function (key) { + error[key] = value[key]; + }); + return error; + } + return value; +}; + +const getPureError = (error)=>{ + return JSON.parse(JSON.stringify(error, formatErrors)); +}; + +app.use(async (err, req, res, next)=>{ + err.originalUrl = req.originalUrl; + console.error(err); + + if(err.originalUrl?.startsWith('/api/')) { + // console.log('API error'); + res.status(err.status || err.response?.status || 500).send(err); + return; + } + + // console.log('non-API error'); + const status = err.status || err.code || 500; + + req.ogMeta = { ...defaultMetaTags, + title : 'Error Page', + description : 'Something went wrong!' + }; + req.brew = { + ...err, + title : 'Error - Something went wrong!', + text : err.errors?.map((error)=>{return error.message;}).join('\n\n') || err.message || 'Unknown error!', + status : status, + HBErrorCode : err.HBErrorCode ?? '00', + pureError : getPureError(err) + }; + req.customUrl= '/error'; + + const page = await renderPage(req, res); + if(!page) return; + res.send(page); +}); + +app.use((req, res)=>{ + if(!res.headersSent) { + console.error('Headers have not been sent, responding with a server error.', req.url); + res.status(500).send('An error occurred and the server did not send a response. The error has been logged, please note the time this occurred and report this issue.'); + } +}); +//^=====--------------------------------------=====^// + +module.exports = { + app : app +}; From 2f392a75172b8f72bdb4b37968c0eb14dacc6df0 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sun, 15 Sep 2024 14:19:32 +1200 Subject: [PATCH 25/33] Lint fixes --- server/app.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/server/app.js b/server/app.js index 0ac2d28f3..f5864caae 100644 --- a/server/app.js +++ b/server/app.js @@ -203,22 +203,22 @@ app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{ }); //Serve brew metadata -app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res) => { +app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res)=>{ const { brew } = req; sanitizeBrew(brew, 'share'); - - const fields = [ 'title', 'pageCount', 'description', 'authors', 'lang', - 'published', 'views', 'shareId', 'createdAt', 'updatedAt', + + const fields = ['title', 'pageCount', 'description', 'authors', 'lang', + 'published', 'views', 'shareId', 'createdAt', 'updatedAt', 'lastViewed', 'thumbnail', 'tags' ]; - - const metadata = fields.reduce((acc, field) => { - if (brew[field] !== undefined) acc[field] = brew[field]; + + const metadata = fields.reduce((acc, field)=>{ + if(brew[field] !== undefined) acc[field] = brew[field]; return acc; }, {}); res.status(200).json(metadata); }); - + //Serve brew styling app.get('/css/:id', asyncHandler(getBrew('share')), (req, res)=>{getCSS(req, res);}); @@ -378,7 +378,7 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r app.get('/account', asyncHandler(async (req, res, next)=>{ const data = {}; data.title = 'Account Information Page'; - + if(!req.account) { res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); const error = new Error('No valid account'); @@ -462,8 +462,8 @@ app.get('/vault', asyncHandler(async(req, res, next)=>{ //Send rendered page app.use(asyncHandler(async (req, res, next)=>{ - if (!req.route) return res.redirect('/'); // Catch-all for invalid routes - + if(!req.route) return res.redirect('/'); // Catch-all for invalid routes + const page = await renderPage(req, res); if(!page) return; res.send(page); From 7a2fecf50230c8d215f5e7b495027061db58fcf8 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Mon, 16 Sep 2024 19:07:42 +1200 Subject: [PATCH 26/33] Set archiveBrew object directly Co-authored-by: Trevor Buckner --- client/homebrew/utils/versionHistory.js | 31 +++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 8aff4c9bb..7c44bd0e1 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -36,26 +36,23 @@ function getVersionBySlot(brew, slot){ return output; }; -function updateStoredBrew(brew, slot=0){ - const archiveBrew = {}; +function updateStoredBrew(brew, slot = 0) { + const archiveBrew = { + title : brew.title, + text : brew.text, + style : brew.style, + version : brew.version, + shareId : brew.shareId, + savedAt : brew?.savedAt || new Date(), + expireAt : new Date() + }; - // Data from brew to be stored - archiveBrew.title = brew.title; - archiveBrew.text = brew.text; - archiveBrew.style = brew.style; - archiveBrew.version = brew.version; - archiveBrew.shareId = brew.shareId; + archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); - // Calculated values - archiveBrew.savedAt = brew?.savedAt || new Date(); - archiveBrew.expireAt = new Date(); - archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); + const key = getKeyBySlot(brew, slot); + localStorage.setItem(key, JSON.stringify(archiveBrew)); +} - // Store data - const key = getKeyBySlot(brew, slot); - localStorage.setItem(key, JSON.stringify(archiveBrew)); - return; -}; export function historyExists(brew){ return Object.keys(localStorage) From ae7404eb1f0b72218930af2b708bf927477145c2 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Mon, 16 Sep 2024 19:15:16 +1200 Subject: [PATCH 27/33] Remove comments --- client/homebrew/utils/versionHistory.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index 7c44bd0e1..c57697c99 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -1,16 +1,17 @@ export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY'; export const HISTORY_SLOTS = 5; +// History values in minutes const DEFAULT_HISTORY_SAVE_DELAYS = { - '0' : 0, // 0 minutes (if not specified) - '1' : 2, // 2 minutes - '2' : 10, // 10 minutes - '3' : 60, // 60 minutes - '4' : 12 * 60, // 12 hours - '5' : 2 * 24 * 60 // 2 days + '0' : 0, + '1' : 2, + '2' : 10, + '3' : 60, + '4' : 12 * 60, + '5' : 2 * 24 * 60 }; -const DEFAULT_GARBAGE_COLLECT_DELAY = 28 * 24 * 60; // 28 days +const DEFAULT_GARBAGE_COLLECT_DELAY = 28 * 24 * 60; const DEFAULT_STORED_BREW = { shareId : 'default_brew', From 91f9a76af22be6cb61c890b0a6a347cf42d47b46 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Mon, 16 Sep 2024 19:20:14 +1200 Subject: [PATCH 28/33] Remove spaces from code indentation --- client/homebrew/utils/versionHistory.js | 52 ++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index c57697c99..c1379d456 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -3,7 +3,7 @@ export const HISTORY_SLOTS = 5; // History values in minutes const DEFAULT_HISTORY_SAVE_DELAYS = { - '0' : 0, + '0' : 0, '1' : 2, '2' : 10, '3' : 60, @@ -38,28 +38,28 @@ function getVersionBySlot(brew, slot){ }; function updateStoredBrew(brew, slot = 0) { - const archiveBrew = { - title : brew.title, - text : brew.text, - style : brew.style, - version : brew.version, - shareId : brew.shareId, - savedAt : brew?.savedAt || new Date(), - expireAt : new Date() - }; + const archiveBrew = { + title : brew.title, + text : brew.text, + style : brew.style, + version : brew.version, + shareId : brew.shareId, + savedAt : brew?.savedAt || new Date(), + expireAt : new Date() + }; - archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); + archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); - const key = getKeyBySlot(brew, slot); - localStorage.setItem(key, JSON.stringify(archiveBrew)); + const key = getKeyBySlot(brew, slot); + localStorage.setItem(key, JSON.stringify(archiveBrew)); } export function historyExists(brew){ return Object.keys(localStorage) - .some((key)=>{ - return key.startsWith(`${HISTORY_PREFIX}-${brew.shareId}`); - }); + .some((key)=>{ + return key.startsWith(`${HISTORY_PREFIX}-${brew.shareId}`); + }); } export function loadHistory(brew){ @@ -108,14 +108,14 @@ export function getHistoryItems(brew){ export function versionHistoryGarbageCollection(){ Object.keys(localStorage) - .filter((key)=>{ - return key.startsWith(HISTORY_PREFIX); - }) - .forEach((key)=>{ - const collectAt = new Date(JSON.parse(localStorage.getItem(key)).savedAt); - collectAt.setMinutes(collectAt.getMinutes() + GARBAGE_COLLECT_DELAY); - if(new Date() > collectAt){ - localStorage.removeItem(key); - } - }); + .filter((key)=>{ + return key.startsWith(HISTORY_PREFIX); + }) + .forEach((key)=>{ + const collectAt = new Date(JSON.parse(localStorage.getItem(key)).savedAt); + collectAt.setMinutes(collectAt.getMinutes() + GARBAGE_COLLECT_DELAY); + if(new Date() > collectAt){ + localStorage.removeItem(key); + } + }); }; \ No newline at end of file From 59f6f40ace807b31e1eaa7cb2c43cf2c5b9d18b2 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Mon, 16 Sep 2024 19:24:39 +1200 Subject: [PATCH 29/33] Add seconds to display options --- client/homebrew/editor/snippetbar/snippetbar.jsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index fbab67d2f..615c707bf 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -162,13 +162,14 @@ const Snippetbar = createClass({ const saveTime = new Date(item.savedAt); const diffMs = new Date() - saveTime; - const diffMins = Math.floor(diffMs / (60 * 1000)); + const diffSecs = Math.floor(diffMs / 1000); - let diffString = `about ${diffMins} minutes ago`; + let diffString = `about ${diffSecs} seconds ago`; - if(diffMins > 60) diffString = `about ${Math.floor(diffMins / 60)} hours ago`; - if(diffMins > (24 * 60)) diffString = `about ${Math.floor(diffMins / (24 * 60))} days ago`; - if(diffMins > (7 * 24 * 60)) diffString = `about ${Math.floor(diffMins / (7 * 24 * 60))} weeks ago`; + if(diffSecs > 60) diffString = `about ${Math.floor(diffSecs / 60)} minutes ago`; + if(diffSecs > (60 * 60)) diffString = `about ${Math.floor(diffSecs / (60 * 60))} hours ago`; + if(diffSecs > (24 * 60 * 60)) diffString = `about ${Math.floor(diffSecs / (24 * 60 * 60))} days ago`; + if(diffSecs > (7 * 24 * 60 * 60)) diffString = `about ${Math.floor(diffSecs / (7 * 24 * 60 * 60))} weeks ago`; return
{this.replaceContent(item);}} > From 8315df33ae8b21918469b6e31708c784f1a1e689 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Mon, 16 Sep 2024 19:39:51 +1200 Subject: [PATCH 30/33] Change empty slot logic --- client/homebrew/utils/versionHistory.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index c1379d456..ef4cbe772 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -33,7 +33,7 @@ function getVersionBySlot(brew, slot){ // - If it doesn't exist, pass default object const key = getKeyBySlot(brew, slot); const storedVersion = localStorage.getItem(key); - const output = storedVersion ? JSON.parse(storedVersion) : { ...DEFAULT_STORED_BREW, ...brew }; + const output = storedVersion ? JSON.parse(storedVersion) : { expireAt: '2000-01-01T00:00:00.000Z', shareId: brew.shareId, noData: true }; return output; }; @@ -84,7 +84,7 @@ export function updateHistory(brew) { if(new Date() >= new Date(storedVersion.expireAt)){ for (let updateSlot = slot - 1; updateSlot>0; updateSlot--){ // Move data from updateSlot to updateSlot + 1 - updateStoredBrew(history[updateSlot], updateSlot + 1); + !history[updateSlot]?.noData && updateStoredBrew(history[updateSlot], updateSlot + 1); }; // Update the most recent brew From 8ceb422156d16d21f43e473812bc4f93c3667850 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Mon, 16 Sep 2024 19:40:11 +1200 Subject: [PATCH 31/33] Separate bundled setState calls --- .../homebrew/editor/snippetbar/snippetbar.jsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index 615c707bf..d94e4fbb4 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -62,20 +62,17 @@ const Snippetbar = createClass({ }, componentDidUpdate : async function(prevProps) { - const update = {}; - let newData = false; - if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) { - update.snippets = this.compileSnippets(); - newData = true; + this.setState({ + snippets : this.compileSnippets() + }); }; if(historyExists(this.props.brew) != this.state.historyExists){ - update.historyExists = !this.state.historyExists; - newData = true; - } - - newData && this.setState(update); + this.setState({ + historyExists : !this.state.historyExists + }); + }; }, mergeCustomizer : function(oldValue, newValue, key) { From 53c05a3ef645cdb286c976dfa0f672c465814032 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Tue, 17 Sep 2024 07:27:52 +1200 Subject: [PATCH 32/33] Remove unused default item --- client/homebrew/utils/versionHistory.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js index ef4cbe772..ad7c6102e 100644 --- a/client/homebrew/utils/versionHistory.js +++ b/client/homebrew/utils/versionHistory.js @@ -13,11 +13,6 @@ const DEFAULT_HISTORY_SAVE_DELAYS = { const DEFAULT_GARBAGE_COLLECT_DELAY = 28 * 24 * 60; -const DEFAULT_STORED_BREW = { - shareId : 'default_brew', - expireAt : '2000-01-01T00:00:00.000Z' -}; - const HISTORY_SAVE_DELAYS = global.config?.historyData?.HISTORY_SAVE_DELAYS ?? DEFAULT_HISTORY_SAVE_DELAYS; const GARBAGE_COLLECT_DELAY = global.config?.historyData?.GARBAGE_COLLECT_DELAY ?? DEFAULT_GARBAGE_COLLECT_DELAY; From 83a7636b6f1d09fbe6af607c7d4302e262bd3daa Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Tue, 17 Sep 2024 07:41:34 +1200 Subject: [PATCH 33/33] Simplify historyExists state logic --- client/homebrew/editor/snippetbar/snippetbar.jsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index d94e4fbb4..e19889cc7 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -68,11 +68,9 @@ const Snippetbar = createClass({ }); }; - if(historyExists(this.props.brew) != this.state.historyExists){ - this.setState({ - historyExists : !this.state.historyExists - }); - }; + this.setState({ + historyExists : historyExists(this.props.brew) + }); }, mergeCustomizer : function(oldValue, newValue, key) {