From 4d94129daf7629a44fe199d720923e3d556274b9 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Fri, 17 Nov 2023 15:05:35 +0100 Subject: [PATCH 1/3] Partially revert removing custom form handler This partially reverts commit a407d2a9b50672b21fb6b62799f88b75bf5af6b1 --- .../realtime-compiler/resources/live-edit.js | 20 +++++++++++++++ .../src/Http/LiveEditController.php | 25 ++++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/realtime-compiler/resources/live-edit.js b/packages/realtime-compiler/resources/live-edit.js index d58a91a10fe..85f26705493 100644 --- a/packages/realtime-compiler/resources/live-edit.js +++ b/packages/realtime-compiler/resources/live-edit.js @@ -51,6 +51,8 @@ function initLiveEdit() { showEditor(); document.getElementById('liveEditCancel').addEventListener('click', hideEditor); + + document.getElementById('liveEditForm').addEventListener('submit', handleFormSubmit); } if (hasEditorBeenSetUp()) { @@ -60,6 +62,24 @@ function initLiveEdit() { } } + function handleFormSubmit(event) { + event.preventDefault(); + + fetch('/_hyde/live-edit', { + method: "POST", + body: new FormData(event.target), + headers: new Headers({ + "Accept": "application/json", + }), + }).then(async response => { + if (response.ok) { + window.location.reload(); + } else { + alert(`Error saving content: ${response.status} ${response.statusText}\n${JSON.parse(await response.text()).error ?? 'Unknown error'}`); + } + }); + } + function handleShortcut(event) { let isEditorHidden = getLiveEditor() === null || getLiveEditor().style.display === 'none'; let isEditorVisible = getLiveEditor() !== null && getLiveEditor().style.display !== 'none'; diff --git a/packages/realtime-compiler/src/Http/LiveEditController.php b/packages/realtime-compiler/src/Http/LiveEditController.php index a4de9543f51..d970ff69d4c 100644 --- a/packages/realtime-compiler/src/Http/LiveEditController.php +++ b/packages/realtime-compiler/src/Http/LiveEditController.php @@ -6,10 +6,13 @@ use Hyde\Hyde; use Hyde\Support\Models\Route; +use Desilva\Microserve\Response; use Hyde\Support\Models\Redirect; use Hyde\Markdown\Models\Markdown; +use Desilva\Microserve\JsonResponse; use Illuminate\Support\Facades\Blade; use Hyde\Pages\Concerns\BaseMarkdownPage; +use Symfony\Component\HttpKernel\Exception\HttpException; /** * @internal This class is not intended to be edited outside the Hyde Realtime Compiler. @@ -19,14 +22,22 @@ class LiveEditController extends BaseController protected bool $withConsoleOutput = true; protected bool $withSession = true; - public function handle(): HtmlResponse + public function handle(): Response { - $this->authorizePostRequest(); + try { + $this->authorizePostRequest(); - return $this->handleRequest(); + return $this->handleRequest(); + } catch (HttpException $exception) { + if ($this->expectsJson()) { + return $this->sendJsonErrorResponse($exception->getStatusCode(), $exception->getMessage()); + } + + throw $exception; + } } - protected function handleRequest(): HtmlResponse + protected function handleRequest(): Response { $pagePath = $this->request->data['page'] ?? $this->abort(400, 'Must provide page path'); $content = $this->request->data['markdown'] ?? $this->abort(400, 'Must provide content'); @@ -42,6 +53,12 @@ protected function handleRequest(): HtmlResponse $this->writeToConsole("Updated file '$pagePath'", 'hyde@live-edit'); + if ($this->expectsJson()) { + return new JsonResponse(200, 'OK', [ + 'message' => 'Page saved successfully.', + ]); + } + return $this->redirectToPage($page->getRoute()); } From 8380602eeaab5f43c951b9c9d37c9f489e85f5e2 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Fri, 17 Nov 2023 15:20:50 +0100 Subject: [PATCH 2/3] Update live edit feature to protect against overwriting modified content Updates the live edit feature to protect against overwriting content modified in other tabs/editors --- packages/realtime-compiler/resources/live-edit.blade.php | 1 + packages/realtime-compiler/resources/live-edit.js | 8 ++++++++ .../realtime-compiler/src/Http/LiveEditController.php | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/packages/realtime-compiler/resources/live-edit.blade.php b/packages/realtime-compiler/resources/live-edit.blade.php index f75810cc616..9c585a90323 100644 --- a/packages/realtime-compiler/resources/live-edit.blade.php +++ b/packages/realtime-compiler/resources/live-edit.blade.php @@ -21,6 +21,7 @@ + diff --git a/packages/realtime-compiler/resources/live-edit.js b/packages/realtime-compiler/resources/live-edit.js index 85f26705493..8e65ccd2adf 100644 --- a/packages/realtime-compiler/resources/live-edit.js +++ b/packages/realtime-compiler/resources/live-edit.js @@ -75,6 +75,14 @@ function initLiveEdit() { if (response.ok) { window.location.reload(); } else { + if (response.status === 409) { + if (confirm('This page has been modified in another window. Do you want to overwrite the changes?')) { + document.getElementById('liveEditForm').insertAdjacentHTML('beforeend', ''); + document.getElementById('liveEditForm').submit(); + } + return; + } + alert(`Error saving content: ${response.status} ${response.statusText}\n${JSON.parse(await response.text()).error ?? 'Unknown error'}`); } }); diff --git a/packages/realtime-compiler/src/Http/LiveEditController.php b/packages/realtime-compiler/src/Http/LiveEditController.php index d970ff69d4c..96ba6669aa5 100644 --- a/packages/realtime-compiler/src/Http/LiveEditController.php +++ b/packages/realtime-compiler/src/Http/LiveEditController.php @@ -41,6 +41,8 @@ protected function handleRequest(): Response { $pagePath = $this->request->data['page'] ?? $this->abort(400, 'Must provide page path'); $content = $this->request->data['markdown'] ?? $this->abort(400, 'Must provide content'); + $currentContentHash = $this->request->data['currentContentHash'] ?? $this->abort(400, 'Must provide content hash'); + $force = $this->request->data['force'] ?? false; $page = Hyde::pages()->getPage($pagePath); @@ -48,6 +50,10 @@ protected function handleRequest(): Response $this->abort(400, 'Page is not a markdown page'); } + if (! $force && hash_file('sha256', $page->getSourcePath()) !== $currentContentHash) { + $this->abort(409, 'Content has changed in another window'); + } + $page->markdown = new Markdown($content); $page->save(); From ab3a66fad7eea982b7c46ea3916d86c50b5c1821 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Fri, 17 Nov 2023 15:31:23 +0100 Subject: [PATCH 3/3] Hash the contents as that is much faster Hashing the file takes over 0.100ms (due to having to load the file again), which arguably is not much, but considering that hashing the content already stored in memory is less than 0.007ms which is an order of magnitude faster, for something that does not affect the feature. --- packages/realtime-compiler/resources/live-edit.blade.php | 2 +- packages/realtime-compiler/src/Http/LiveEditController.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/realtime-compiler/resources/live-edit.blade.php b/packages/realtime-compiler/resources/live-edit.blade.php index 9c585a90323..e259c391dcd 100644 --- a/packages/realtime-compiler/resources/live-edit.blade.php +++ b/packages/realtime-compiler/resources/live-edit.blade.php @@ -21,7 +21,7 @@ - + diff --git a/packages/realtime-compiler/src/Http/LiveEditController.php b/packages/realtime-compiler/src/Http/LiveEditController.php index 96ba6669aa5..0c30f1aded7 100644 --- a/packages/realtime-compiler/src/Http/LiveEditController.php +++ b/packages/realtime-compiler/src/Http/LiveEditController.php @@ -50,7 +50,7 @@ protected function handleRequest(): Response $this->abort(400, 'Page is not a markdown page'); } - if (! $force && hash_file('sha256', $page->getSourcePath()) !== $currentContentHash) { + if (! $force && hash('sha256', $page->markdown()->body()) !== $currentContentHash) { $this->abort(409, 'Content has changed in another window'); }