diff --git a/packages/realtime-compiler/resources/live-edit.blade.php b/packages/realtime-compiler/resources/live-edit.blade.php index f75810cc616..e259c391dcd 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 d58a91a10fe..8e65ccd2adf 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,32 @@ 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 { + 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'}`); + } + }); + } + 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..0c30f1aded7 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,17 +22,27 @@ 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'); + $currentContentHash = $this->request->data['currentContentHash'] ?? $this->abort(400, 'Must provide content hash'); + $force = $this->request->data['force'] ?? false; $page = Hyde::pages()->getPage($pagePath); @@ -37,11 +50,21 @@ protected function handleRequest(): HtmlResponse $this->abort(400, 'Page is not a markdown page'); } + if (! $force && hash('sha256', $page->markdown()->body()) !== $currentContentHash) { + $this->abort(409, 'Content has changed in another window'); + } + $page->markdown = new Markdown($content); $page->save(); $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()); }