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());
}