mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-01 15:12:40 +00:00
Merge branch 'master' into opengraph_locale
This commit is contained in:
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@@ -5,6 +5,15 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
open-pull-requests-limit: 99
|
open-pull-requests-limit: 99
|
||||||
|
groups:
|
||||||
|
dev-dependencies:
|
||||||
|
dependency-type: "development"
|
||||||
|
patterns: ["*"]
|
||||||
|
update-types: ["patch", "minor"]
|
||||||
|
prod-dependencies:
|
||||||
|
dependency-type: "production"
|
||||||
|
patterns: ["*"]
|
||||||
|
update-types: ["patch", "minor"]
|
||||||
ignore:
|
ignore:
|
||||||
- dependency-name: eslint
|
- dependency-name: eslint
|
||||||
versions:
|
versions:
|
||||||
|
|||||||
16
changelog.md
16
changelog.md
@@ -88,6 +88,22 @@ pre {
|
|||||||
## changelog
|
## changelog
|
||||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||||
|
|
||||||
|
### Wednesday 7/09/2025 - v3.19.2
|
||||||
|
|
||||||
|
{{taskList
|
||||||
|
##### calculuschild
|
||||||
|
* [x] Hotfix for saving issues - Please refresh your browser and report if problems continue
|
||||||
|
}}
|
||||||
|
|
||||||
|
### Wednesday 7/09/2025 - v3.19.1
|
||||||
|
|
||||||
|
{{taskList
|
||||||
|
##### calculuschild
|
||||||
|
* [x] Send diffs instead of full file on save - should help with timeout/disconnect errors
|
||||||
|
}}
|
||||||
|
|
||||||
|
\column
|
||||||
|
|
||||||
### Thursday 05/22/2025 - v3.19.0
|
### Thursday 05/22/2025 - v3.19.0
|
||||||
|
|
||||||
{{taskList
|
{{taskList
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ require('./editPage.less');
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const createClass = require('create-react-class');
|
const createClass = require('create-react-class');
|
||||||
|
import {makePatches, applyPatches, stringifyPatches, parsePatches} from '@sanity/diff-match-patch';
|
||||||
|
import { md5 } from 'hash-wasm';
|
||||||
|
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
const { Meta } = require('vitreum/headtags');
|
const { Meta } = require('vitreum/headtags');
|
||||||
@@ -47,7 +49,7 @@ const EditPage = createClass({
|
|||||||
return {
|
return {
|
||||||
brew : this.props.brew,
|
brew : this.props.brew,
|
||||||
isSaving : false,
|
isSaving : false,
|
||||||
isPending : false,
|
unsavedChanges : false,
|
||||||
alertTrashedGoogleBrew : this.props.brew.trashed,
|
alertTrashedGoogleBrew : this.props.brew.trashed,
|
||||||
alertLoginToTransfer : false,
|
alertLoginToTransfer : false,
|
||||||
saveGoogle : this.props.brew.googleId ? true : false,
|
saveGoogle : this.props.brew.googleId ? true : false,
|
||||||
@@ -85,7 +87,7 @@ const EditPage = createClass({
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.onbeforeunload = ()=>{
|
window.onbeforeunload = ()=>{
|
||||||
if(this.state.isSaving || this.state.isPending){
|
if(this.state.isSaving || this.state.unsavedChanges){
|
||||||
return 'You have unsaved changes!';
|
return 'You have unsaved changes!';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -104,9 +106,9 @@ const EditPage = createClass({
|
|||||||
},
|
},
|
||||||
componentDidUpdate : function(){
|
componentDidUpdate : function(){
|
||||||
const hasChange = this.hasChanges();
|
const hasChange = this.hasChanges();
|
||||||
if(this.state.isPending != hasChange){
|
if(this.state.unsavedChanges != hasChange){
|
||||||
this.setState({
|
this.setState({
|
||||||
isPending : hasChange
|
unsavedChanges : hasChange
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -156,9 +158,9 @@ const EditPage = createClass({
|
|||||||
if(htmlErrors.length) htmlErrors = Markdown.validate(snippet);
|
if(htmlErrors.length) htmlErrors = Markdown.validate(snippet);
|
||||||
|
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
brew : { ...prevState.brew, snippets: snippet },
|
brew : { ...prevState.brew, snippets: snippet },
|
||||||
isPending : true,
|
unsavedChanges : true,
|
||||||
htmlErrors : htmlErrors,
|
htmlErrors : htmlErrors,
|
||||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -196,12 +198,19 @@ const EditPage = createClass({
|
|||||||
|
|
||||||
trySave : function(immediate=false){
|
trySave : function(immediate=false){
|
||||||
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
|
||||||
if(this.hasChanges()){
|
if(this.state.isSaving)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(immediate) {
|
||||||
this.debounceSave();
|
this.debounceSave();
|
||||||
} else {
|
this.debounceSave.flush();
|
||||||
this.debounceSave.cancel();
|
return;
|
||||||
}
|
}
|
||||||
if(immediate) this.debounceSave.flush();
|
|
||||||
|
if(this.hasChanges())
|
||||||
|
this.debounceSave();
|
||||||
|
else
|
||||||
|
this.debounceSave.cancel();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleGoogleClick : function(){
|
handleGoogleClick : function(){
|
||||||
@@ -215,8 +224,7 @@ const EditPage = createClass({
|
|||||||
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
|
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
|
||||||
}));
|
}));
|
||||||
this.setState({
|
this.setState({
|
||||||
error : null,
|
error : null
|
||||||
isSaving : false
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -232,9 +240,8 @@ const EditPage = createClass({
|
|||||||
toggleGoogleStorage : function(){
|
toggleGoogleStorage : function(){
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
saveGoogle : !prevState.saveGoogle,
|
saveGoogle : !prevState.saveGoogle,
|
||||||
isSaving : false,
|
|
||||||
error : null
|
error : null
|
||||||
}), ()=>this.save());
|
}), ()=>this.trySave(true));
|
||||||
},
|
},
|
||||||
|
|
||||||
save : async function(){
|
save : async function(){
|
||||||
@@ -249,11 +256,19 @@ const EditPage = createClass({
|
|||||||
await updateHistory(this.state.brew).catch(console.error);
|
await updateHistory(this.state.brew).catch(console.error);
|
||||||
await versionHistoryGarbageCollection().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();
|
||||||
|
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);
|
||||||
|
brew.text = undefined;
|
||||||
|
brew.textBin = undefined;
|
||||||
|
|
||||||
const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId);
|
const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId);
|
||||||
|
|
||||||
const brew = this.state.brew;
|
|
||||||
brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
|
|
||||||
|
|
||||||
const params = `${transfer ? `?${this.state.saveGoogle ? 'saveToGoogle' : 'removeFromGoogle'}=true` : ''}`;
|
const params = `${transfer ? `?${this.state.saveGoogle ? 'saveToGoogle' : 'removeFromGoogle'}=true` : ''}`;
|
||||||
const res = await request
|
const res = await request
|
||||||
.put(`/api/update/${brew.editId}${params}`)
|
.put(`/api/update/${brew.editId}${params}`)
|
||||||
@@ -265,20 +280,28 @@ const EditPage = createClass({
|
|||||||
if(!res) return;
|
if(!res) return;
|
||||||
|
|
||||||
this.savedBrew = {
|
this.savedBrew = {
|
||||||
...this.state.brew,
|
...preSaveSnapshot,
|
||||||
googleId : res.body.googleId ? res.body.googleId : null,
|
googleId : res.body.googleId ? res.body.googleId : null,
|
||||||
editId : res.body.editId,
|
editId : res.body.editId,
|
||||||
shareId : res.body.shareId,
|
shareId : res.body.shareId,
|
||||||
version : res.body.version
|
version : res.body.version
|
||||||
};
|
};
|
||||||
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
|
|
||||||
|
|
||||||
this.setState(()=>({
|
this.setState((prevState) => ({
|
||||||
brew : this.savedBrew,
|
brew: {
|
||||||
isPending : false,
|
...prevState.brew,
|
||||||
isSaving : false,
|
googleId : res.body.googleId ? res.body.googleId : null,
|
||||||
unsavedTime : new Date()
|
editId : res.body.editId,
|
||||||
}));
|
shareId : res.body.shareId,
|
||||||
|
version : res.body.version
|
||||||
|
},
|
||||||
|
isSaving : false,
|
||||||
|
unsavedTime : new Date()
|
||||||
|
}), ()=>{
|
||||||
|
this.setState({ unsavedChanges : this.hasChanges() });
|
||||||
|
});
|
||||||
|
|
||||||
|
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderGoogleDriveIcon : function(){
|
renderGoogleDriveIcon : function(){
|
||||||
@@ -336,7 +359,7 @@ const EditPage = createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
|
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
|
||||||
if(this.state.isPending && this.state.autoSaveWarning){
|
if(this.state.unsavedChanges && this.state.autoSaveWarning){
|
||||||
this.setAutosaveWarning();
|
this.setAutosaveWarning();
|
||||||
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
|
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
|
||||||
const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
|
const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
|
||||||
@@ -351,7 +374,7 @@ const EditPage = createClass({
|
|||||||
|
|
||||||
// #3 - Unsaved changes exist, click to save, show SAVE NOW
|
// #3 - Unsaved changes exist, click to save, show SAVE NOW
|
||||||
// Use trySave(true) instead of save() to use debounced save function
|
// Use trySave(true) instead of save() to use debounced save function
|
||||||
if(this.state.isPending){
|
if(this.state.unsavedChanges){
|
||||||
return <Nav.item className='save' onClick={()=>this.trySave(true)} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
|
return <Nav.item className='save' onClick={()=>this.trySave(true)} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
|
||||||
}
|
}
|
||||||
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
|
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ const NewPage = createClass({
|
|||||||
|
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
brew : { ...prevState.brew, snippets: snippet },
|
brew : { ...prevState.brew, snippets: snippet },
|
||||||
isPending : true,
|
|
||||||
htmlErrors : htmlErrors,
|
htmlErrors : htmlErrors,
|
||||||
}), ()=>{if(this.state.autoSave) this.trySave();});
|
}), ()=>{if(this.state.autoSave) this.trySave();});
|
||||||
},
|
},
|
||||||
|
|||||||
4497
package-lock.json
generated
4497
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "3.19.0",
|
"version": "3.19.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": "^10.8.x",
|
"npm": "^10.8.x",
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"lines": 50
|
"lines": 50
|
||||||
},
|
},
|
||||||
"server/homebrew.api.js": {
|
"server/homebrew.api.js": {
|
||||||
"statements": 70,
|
"statements": 69,
|
||||||
"branches": 50,
|
"branches": 50,
|
||||||
"functions": 65,
|
"functions": 65,
|
||||||
"lines": 70
|
"lines": 70
|
||||||
@@ -84,16 +84,17 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.27.1",
|
"@babel/core": "^7.27.1",
|
||||||
"@babel/plugin-transform-runtime": "^7.27.1",
|
"@babel/plugin-transform-runtime": "^7.28.0",
|
||||||
"@babel/preset-env": "^7.27.2",
|
"@babel/preset-env": "^7.28.0",
|
||||||
"@babel/preset-react": "^7.27.1",
|
"@babel/preset-react": "^7.27.1",
|
||||||
"@babel/runtime": "^7.27.1",
|
"@babel/runtime": "^7.27.6",
|
||||||
"@googleapis/drive": "^12.1.0",
|
"@googleapis/drive": "^13.0.1",
|
||||||
|
"@sanity/diff-match-patch": "^3.2.0",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"codemirror": "^5.65.6",
|
"codemirror": "^5.65.6",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"core-js": "^3.42.0",
|
"core-js": "^3.43.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"create-react-class": "^15.7.0",
|
"create-react-class": "^15.7.0",
|
||||||
"dedent-tabs": "^0.10.3",
|
"dedent-tabs": "^0.10.3",
|
||||||
@@ -102,6 +103,7 @@
|
|||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "3.0.0",
|
"express-static-gzip": "3.0.0",
|
||||||
"fs-extra": "11.3.0",
|
"fs-extra": "11.3.0",
|
||||||
|
"hash-wasm": "^4.12.0",
|
||||||
"idb-keyval": "^6.2.2",
|
"idb-keyval": "^6.2.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
@@ -110,40 +112,40 @@
|
|||||||
"marked": "15.0.12",
|
"marked": "15.0.12",
|
||||||
"marked-alignment-paragraphs": "^1.0.0",
|
"marked-alignment-paragraphs": "^1.0.0",
|
||||||
"marked-definition-lists": "^1.0.1",
|
"marked-definition-lists": "^1.0.1",
|
||||||
"marked-emoji": "^2.0.0",
|
"marked-emoji": "^2.0.1",
|
||||||
"marked-extended-tables": "^2.0.1",
|
"marked-extended-tables": "^2.0.1",
|
||||||
"marked-gfm-heading-id": "^4.0.1",
|
"marked-gfm-heading-id": "^4.1.2",
|
||||||
"marked-nonbreaking-spaces": "^1.0.1",
|
"marked-nonbreaking-spaces": "^1.0.1",
|
||||||
"marked-smartypants-lite": "^1.0.3",
|
"marked-smartypants-lite": "^1.0.3",
|
||||||
"marked-subsuper-text": "^1.0.3",
|
"marked-subsuper-text": "^1.0.3",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongoose": "^8.15.0",
|
"mongoose": "^8.16.1",
|
||||||
"nanoid": "5.1.5",
|
"nanoid": "5.1.5",
|
||||||
"nconf": "^0.13.0",
|
"nconf": "^0.13.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-frame-component": "^4.1.3",
|
"react-frame-component": "^4.1.3",
|
||||||
"react-router": "^7.6.0",
|
"react-router": "^7.6.3",
|
||||||
"romans": "^3.0.0",
|
"romans": "^3.1.0",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
"superagent": "^10.2.1",
|
"superagent": "^10.2.1",
|
||||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git",
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git",
|
||||||
"written-number": "^0.11.1"
|
"written-number": "^0.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/stylelint-plugin": "^3.1.2",
|
"@stylistic/stylelint-plugin": "^3.1.3",
|
||||||
"babel-plugin-transform-import-meta": "^2.3.2",
|
"babel-plugin-transform-import-meta": "^2.3.3",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.30.1",
|
||||||
"eslint-plugin-jest": "^28.11.0",
|
"eslint-plugin-jest": "^29.0.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^16.1.0",
|
"globals": "^16.3.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^30.0.4",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
"jsdom-global": "^3.0.2",
|
"jsdom-global": "^3.0.2",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
"stylelint": "^16.19.1",
|
"stylelint": "^16.21.1",
|
||||||
"stylelint-config-recess-order": "^6.0.0",
|
"stylelint-config-recess-order": "^7.1.0",
|
||||||
"stylelint-config-recommended": "^16.0.0",
|
"stylelint-config-recommended": "^16.0.0",
|
||||||
"supertest": "^7.1.1"
|
"supertest": "^7.1.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"codemirror/addon/selection/active-line.js",
|
"codemirror/addon/selection/active-line.js",
|
||||||
"codemirror/addon/hint/show-hint.js",
|
"codemirror/addon/hint/show-hint.js",
|
||||||
"moment",
|
"moment",
|
||||||
"superagent"
|
"superagent",
|
||||||
|
"@sanity/diff-match-patch"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import Markdown from '../shared/naturalcrit/markdown.js';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import asyncHandler from 'express-async-handler';
|
import asyncHandler from 'express-async-handler';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
import {makePatches, applyPatches, stringifyPatches, parsePatch} from '@sanity/diff-match-patch';
|
||||||
|
import { md5 } from 'hash-wasm';
|
||||||
import { splitTextStyleAndMetadata,
|
import { splitTextStyleAndMetadata,
|
||||||
brewSnippetsToJSON } from '../shared/helpers.js';
|
brewSnippetsToJSON } from '../shared/helpers.js';
|
||||||
import checkClientVersion from './middleware/check-client-version.js';
|
import checkClientVersion from './middleware/check-client-version.js';
|
||||||
@@ -337,21 +339,41 @@ const api = {
|
|||||||
// Initialize brew from request and body, destructure query params, and set the initial value for the after-save method
|
// 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 brewFromClient = api.excludePropsFromUpdate(req.body);
|
||||||
const brewFromServer = req.brew;
|
const brewFromServer = req.brew;
|
||||||
if(brewFromServer.version && brewFromClient.version && brewFromServer.version > brewFromClient.version) {
|
splitTextStyleAndMetadata(brewFromServer);
|
||||||
console.log(`Version mismatch on brew ${brewFromClient.editId}`);
|
|
||||||
|
brewFromServer.text = brewFromServer.text.normalize();
|
||||||
|
brewFromServer.hash = await md5(brewFromServer.text);
|
||||||
|
|
||||||
|
if((brewFromServer?.version !== brewFromClient?.version) || (brewFromServer?.hash !== brewFromClient?.hash)) {
|
||||||
|
if(brewFromClient?.version !== brewFromClient?.version)
|
||||||
|
console.log(`Version mismatch on brew ${brewFromClient.editId}`);
|
||||||
|
if(brewFromServer?.hash !== brewFromClient?.hash) {
|
||||||
|
console.log(`Hash mismatch on brew ${brewFromClient.editId}`);
|
||||||
|
}
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
return res.status(409).send(JSON.stringify({ message: `The brew has been changed on a different device. Please save your changes elsewhere, refresh, and try again.` }));
|
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);
|
let brew = _.assign(brewFromServer, brewFromClient);
|
||||||
|
brew.title = brew.title.trim();
|
||||||
|
brew.description = brew.description.trim() || '';
|
||||||
|
try {
|
||||||
|
const patches = parsePatch(brewFromClient.patches);
|
||||||
|
brew.text = applyPatches(patches, brewFromServer.text)[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to apply patches:', {
|
||||||
|
patches: brewFromClient.patches,
|
||||||
|
brewId: brew.editId || 'unknown'
|
||||||
|
});
|
||||||
|
throw err; // rethrow to preserve the 500 behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
brew.text = api.mergeBrewText(brew);
|
||||||
|
|
||||||
const googleId = brew.googleId;
|
const googleId = brew.googleId;
|
||||||
const { saveToGoogle, removeFromGoogle } = req.query;
|
const { saveToGoogle, removeFromGoogle } = req.query;
|
||||||
let afterSave = async ()=>true;
|
let afterSave = async ()=>true;
|
||||||
|
|
||||||
brew.title = brew.title.trim();
|
|
||||||
brew.description = brew.description.trim() || '';
|
|
||||||
brew.text = api.mergeBrewText(brew);
|
|
||||||
|
|
||||||
if(brew.googleId && removeFromGoogle) {
|
if(brew.googleId && removeFromGoogle) {
|
||||||
// If the google id exists and we're removing it from google, set afterSave to delete the google brew and mark the brew's google id as undefined
|
// If the google id exists and we're removing it from google, set afterSave to delete the google brew and mark the brew's google id as undefined
|
||||||
afterSave = async ()=>{
|
afterSave = async ()=>{
|
||||||
@@ -484,8 +506,8 @@ const api = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
router.post('/api', checkClientVersion, asyncHandler(api.newBrew));
|
router.post('/api', checkClientVersion, asyncHandler(api.newBrew));
|
||||||
router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
|
router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
|
||||||
router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
|
router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
|
||||||
router.delete('/api/:id', checkClientVersion, asyncHandler(api.deleteBrew));
|
router.delete('/api/:id', checkClientVersion, asyncHandler(api.deleteBrew));
|
||||||
router.get('/api/remove/:id', checkClientVersion, asyncHandler(api.deleteBrew));
|
router.get('/api/remove/:id', checkClientVersion, asyncHandler(api.deleteBrew));
|
||||||
router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle));
|
router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle));
|
||||||
|
|||||||
Reference in New Issue
Block a user