From e1e661976d2e430e0ce4964f478a714357cbfb92 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Fri, 27 Jun 2025 08:07:02 -0400 Subject: [PATCH] Initial test --- client/homebrew/pages/editPage/editPage.jsx | 24 ++++++++++++++++++++- package-lock.json | 10 +++++++++ package.json | 1 + scripts/project.json | 3 ++- server/homebrew.api.js | 11 ++++++++-- 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index f2b1e809f..42aa4f651 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -3,6 +3,7 @@ require('./editPage.less'); const React = require('react'); const _ = require('lodash'); const createClass = require('create-react-class'); +import {makePatches, applyPatches, stringifyPatches, parsePatches} from '@sanity/diff-match-patch'; import request from '../../utils/request-middleware.js'; const { Meta } = require('vitreum/headtags'); @@ -251,9 +252,30 @@ const EditPage = createClass({ const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); - const brew = this.state.brew; + const brew = { ...this.state.brew }; + + let jsonString = JSON.stringify(brew); + let bytes = new TextEncoder().encode(jsonString).length; + + console.log(`Before size: ${bytes} bytes (${(bytes / 1024).toFixed(2)} KB)`); + brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; + brew.patches = makePatches(this.savedBrew.text, brew.text); + brew.text = undefined; + brew.textBin = undefined; + console.log('Saving Brew', brew); + + jsonString = JSON.stringify(brew); + bytes = new TextEncoder().encode(jsonString).length; + + console.log(`After size: ${bytes} bytes (${(bytes / 1024).toFixed(2)} KB)`); + + jsonString = JSON.stringify(brew.patches); + bytes = new TextEncoder().encode(jsonString).length; + + console.log(`Patch size: ${bytes} bytes (${(bytes / 1024).toFixed(2)} KB)`); + const params = `${transfer ? `?${this.state.saveGoogle ? 'saveToGoogle' : 'removeFromGoogle'}=true` : ''}`; const res = await request .put(`/api/update/${brew.editId}${params}`) diff --git a/package-lock.json b/package-lock.json index 581bd5b30..bd9eca5f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@babel/preset-react": "^7.27.1", "@babel/runtime": "^7.27.1", "@googleapis/drive": "^12.1.0", + "@sanity/diff-match-patch": "^3.2.0", "body-parser": "^2.2.0", "classnames": "^2.5.1", "codemirror": "^5.65.6", @@ -2867,6 +2868,15 @@ "@noble/hashes": "^1.1.5" } }, + "node_modules/@sanity/diff-match-patch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sanity/diff-match-patch/-/diff-match-patch-3.2.0.tgz", + "integrity": "sha512-4hPADs0qUThFZkBK/crnfKKHg71qkRowfktBljH2UIxGHHTxIzt8g8fBiXItyCjxkuNy+zpYOdRMifQNv8+Yww==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", diff --git a/package.json b/package.json index 10db28857..811a05f92 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "@babel/preset-react": "^7.27.1", "@babel/runtime": "^7.27.1", "@googleapis/drive": "^12.1.0", + "@sanity/diff-match-patch": "^3.2.0", "body-parser": "^2.2.0", "classnames": "^2.5.1", "codemirror": "^5.65.6", diff --git a/scripts/project.json b/scripts/project.json index c384ae1de..340de077c 100644 --- a/scripts/project.json +++ b/scripts/project.json @@ -27,6 +27,7 @@ "codemirror/addon/selection/active-line.js", "codemirror/addon/hint/show-hint.js", "moment", - "superagent" + "superagent", + "@sanity/diff-match-patch" ] } diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 392e175ca..9a2aa8c0a 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -8,6 +8,7 @@ import Markdown from '../shared/naturalcrit/markdown.js'; import yaml from 'js-yaml'; import asyncHandler from 'express-async-handler'; import { nanoid } from 'nanoid'; +import {makePatches, applyPatches, stringifyPatches, parsePatch} from '@sanity/diff-match-patch'; import { splitTextStyleAndMetadata, brewSnippetsToJSON } from '../shared/helpers.js'; import checkClientVersion from './middleware/check-client-version.js'; @@ -337,12 +338,18 @@ 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; + if(brewFromServer.version && brewFromClient.version && brewFromServer.version > brewFromClient.version) { console.log(`Version mismatch on brew ${brewFromClient.editId}`); 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.` })); } + console.log(`Brewfromserver: ${JSON.stringify(brewFromServer)}`); + splitTextStyleAndMetadata(brewFromServer); + brewFromClient.text = applyPatches(brewFromClient.patches, brewFromServer.text)[0]; + console.log(`Server Text: ${brewFromServer.text}`); + console.log(`Brew text: ${brewFromClient.text}`); let brew = _.assign(brewFromServer, brewFromClient); const googleId = brew.googleId; const { saveToGoogle, removeFromGoogle } = req.query; @@ -484,8 +491,8 @@ const api = { }; router.post('/api', checkClientVersion, asyncHandler(api.newBrew)); -router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew)); -router.put('/api/update/: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', false)), asyncHandler(api.updateBrew)); router.delete('/api/:id', checkClientVersion, asyncHandler(api.deleteBrew)); router.get('/api/remove/:id', checkClientVersion, asyncHandler(api.deleteBrew)); router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle));