diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 8eb011cab..48d4a0b13 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -247,6 +247,9 @@ const EditPage = createClass({ save : async function(){ if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); + const brewState = this.state.brew; // freeze the current state + const preSaveSnapshot = { ...brewState }; + this.setState((prevState)=>({ isSaving : true, error : null, @@ -256,12 +259,10 @@ const EditPage = createClass({ await updateHistory(this.state.brew).catch(console.error); await versionHistoryGarbageCollection().catch(console.error); - const preSaveSnapshot = { ...this.state.brew }; - //Prepare content to send to server - const brew = { ...this.state.brew }; - brew.text = brew.text.normalize(); - this.savedBrew.text = this.savedBrew.text.normalize(); + const brew = { ...brewState }; + brew.text = brew.text.normalize('NFC'); + this.savedBrew.text = this.savedBrew.text.normalize('NFC'); brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; brew.patches = stringifyPatches(makePatches(this.savedBrew.text, brew.text)); brew.hash = await md5(this.savedBrew.text); @@ -295,8 +296,8 @@ const EditPage = createClass({ shareId : res.body.shareId, version : res.body.version }, - isSaving : false, - unsavedTime : new Date() + isSaving : false, + unsavedTime : new Date() }), ()=>{ this.setState({ unsavedChanges : this.hasChanges() }); }); diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 2c5244a60..e5d622fe1 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -339,6 +339,7 @@ const api = { // Initialize brew from request and body, destructure query params, and set the initial value for the after-save method const brewFromClient = api.excludePropsFromUpdate(req.body); const brewFromServer = req.brew; + splitTextStyleAndMetadata(brewFromServer); if(brewFromServer?.version !== brewFromClient?.version){ console.log(`Version mismatch on brew ${brewFromClient.editId}`); @@ -347,9 +348,7 @@ const api = { return res.status(409).send(JSON.stringify({ message: `The server version is out of sync with the saved brew. Please save your changes elsewhere, refresh, and try again.` })); } - splitTextStyleAndMetadata(brewFromServer); - - brewFromServer.text = brewFromServer.text.normalize(); + brewFromServer.text = brewFromServer.text.normalize('NFC'); brewFromServer.hash = await md5(brewFromServer.text); if(brewFromServer?.hash !== brewFromClient?.hash) { @@ -359,26 +358,27 @@ const api = { return res.status(409).send(JSON.stringify({ message: `The server copy is out of sync with the saved brew. Please save your changes elsewhere, refresh, and try again.` })); } - let brew = _.assign(brewFromServer, brewFromClient); - brew.title = brew.title.trim(); - brew.description = brew.description.trim() || ''; - try { const patches = parsePatch(brewFromClient.patches); // Patch to a throwaway variable while parallelizing - we're more concerned with error/no error. - const patchedResult = applyPatches(patches, brewFromServer.text)[0]; + const patchedResult = applyPatches(patches, brewFromServer.text, { allowExceedingIndices: true })[0]; + if(patchedResult != brewFromClient.text) + throw("Patches did not apply cleanly, text mismatch detected"); // brew.text = applyPatches(patches, brewFromServer.text)[0]; } catch (err) { debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`); console.error('Failed to apply patches:', { patches : brewFromClient.patches, - brewId : brew.editId || 'unknown', + brewId : brewFromClient.editId || 'unknown', error : err }); // While running in parallel, don't throw the error upstream. // throw err; // rethrow to preserve the 500 behavior } + let brew = _.assign(brewFromServer, brewFromClient); + brew.title = brew.title.trim(); + brew.description = brew.description.trim() || ''; brew.text = api.mergeBrewText(brew); const googleId = brew.googleId;