diff --git a/TODO.md b/TODO.md index 150550e..b05d0b9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,15 @@ # TODO -- [x] Create CharteController.php (public-facing controller) -- [x] Add /charte routes to Dispatcher.php -- [x] Create public/charte.php template -- [x] Add 'charte' to allowed slugs in contenus.php -- [x] Add 'charte' to allowed slugs in contenus-edit.php -- [x] Add 'charte' to allowed slugs in page.php action -- [x] Add Charte link to public header navigation (header.php) +## HTMX v2 Migration + +Reference: `docs/autosave-system.md` → "HTMX v2 Migration Plan" section. + +- [x] `contenus-edit.php` (pages): Add `hx-*` attrs, add `overtype:change` dispatch in OverType `onChange` +- [x] `contenus-edit.php` (form_help): Add `hx-*` attrs, add `overtype:change` dispatch in OverType `onChange` +- [x] `apropos-groups-form.php` (contacts): Add `hx-*` attrs only +- [x] `contenus-edit.php` (sidebar_links): Add `hx-*` attrs only +- [x] Add `handleAutosaveResponse()` shared handler + `htmx:beforeRequest` loading state +- [x] Delete `autosave.js` +- [x] Fix backend `$isAjax` detection: also recognize `HX-Request` header (page.php, apropos.php, form-help.php) +- [x] Form-help inline editors: add OverType toolbar + HTMX auto-save + remove save buttons +- [x] Markdown cheatsheet modal: reusable dialog on all OverType editors diff --git a/app/public/admin/actions/apropos.php b/app/public/admin/actions/apropos.php index 3a5122e..08f4b32 100644 --- a/app/public/admin/actions/apropos.php +++ b/app/public/admin/actions/apropos.php @@ -11,7 +11,8 @@ require_once __DIR__ . '/../../../src/AdminAuth.php'; error_log('[apropos.php] ENTRY | method=' . $_SERVER['REQUEST_METHOD'] . ' | key=' . ($_POST['apropos_key'] ?? 'none') . ' | post_keys=' . implode(',', array_keys($_POST))); AdminAuth::requireLogin(); -$isAjax = !empty($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json'); +$isAjax = (!empty($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json')) + || !empty($_SERVER['HTTP_HX_REQUEST']); // CSRF check if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || diff --git a/app/public/admin/actions/form-help.php b/app/public/admin/actions/form-help.php index 058656a..98d6e00 100644 --- a/app/public/admin/actions/form-help.php +++ b/app/public/admin/actions/form-help.php @@ -9,7 +9,8 @@ require_once __DIR__ . '/../../../src/AdminAuth.php'; error_log('[form-help.php] ENTRY | method=' . $_SERVER['REQUEST_METHOD'] . ' | key=' . ($_POST['form_help_key'] ?? 'none') . ' | post_keys=' . implode(',', array_keys($_POST))); AdminAuth::requireLogin(); -$isAjax = !empty($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json'); +$isAjax = (!empty($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json')) + || !empty($_SERVER['HTTP_HX_REQUEST']); if (!isset($_POST['csrf_token'], $_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { diff --git a/app/public/admin/actions/page.php b/app/public/admin/actions/page.php index ad4a6cb..af35480 100644 --- a/app/public/admin/actions/page.php +++ b/app/public/admin/actions/page.php @@ -8,7 +8,8 @@ require_once __DIR__ . '/../../../src/AdminAuth.php'; error_log('[page.php] ENTRY | method=' . $_SERVER['REQUEST_METHOD'] . ' | slug=' . ($_POST['slug'] ?? 'none') . ' | post_keys=' . implode(',', array_keys($_POST))); AdminAuth::requireLogin(); -$isAjax = !empty($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json'); +$isAjax = (!empty($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json')) + || !empty($_SERVER['HTTP_HX_REQUEST']); if (!isset($_POST['csrf_token'], $_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { diff --git a/app/public/admin/contenus-edit.php b/app/public/admin/contenus-edit.php index 7f9467b..feba936 100644 --- a/app/public/admin/contenus-edit.php +++ b/app/public/admin/contenus-edit.php @@ -83,7 +83,7 @@ $extraJsInline = ''; if ($editType === 'page' || $editType === 'about_page') { $initialContent = $page["content"] ?? ""; - $extraJs = ["/assets/js/vendor/overtype.min.js", "/assets/js/app/autosave.js"]; + $extraJs = ["/assets/js/vendor/overtype.min.js", "/assets/js/app/autosave-handler.js"]; $extraJsInline = <<<'JS' var OT = window.OverType.default || window.OverType; var hidden = document.getElementById('content'); @@ -92,12 +92,15 @@ var editor = new OT(document.getElementById('editor'), { minHeight: '400px', spellcheck: false, toolbar: true, - onChange: function(value) { hidden.value = value; } + onChange: function(value) { + hidden.value = value; + hidden.dispatchEvent(new CustomEvent('overtype:change', { bubbles: true })); + } }); JS; } elseif ($editType === 'form_help') { $initialContent = $formHelpContent; - $extraJs = ["/assets/js/vendor/overtype.min.js", "/assets/js/app/autosave.js"]; + $extraJs = ["/assets/js/vendor/overtype.min.js", "/assets/js/app/autosave-handler.js"]; $extraJsInline = <<<'JS' var OT = window.OverType.default || window.OverType; var hidden = document.getElementById('content'); @@ -106,11 +109,14 @@ var editor = new OT(document.getElementById('editor'), { minHeight: '400px', spellcheck: false, toolbar: true, - onChange: function(value) { hidden.value = value; } + onChange: function(value) { + hidden.value = value; + hidden.dispatchEvent(new CustomEvent('overtype:change', { bubbles: true })); + } }); JS; } elseif ($editType === 'apropos') { - $extraJs = ["/assets/js/app/autosave.js"]; + $extraJs = ["/assets/js/app/autosave-handler.js"]; } $isAdmin = true; diff --git a/app/public/admin/form-help-inline-fragment.php b/app/public/admin/form-help-inline-fragment.php index cd7b154..5603ef5 100644 --- a/app/public/admin/form-help-inline-fragment.php +++ b/app/public/admin/form-help-inline-fragment.php @@ -49,6 +49,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // save $content = $_POST['content'] ?? ''; $name = trim($_POST['name'] ?? ''); + $isAutosave = ($_GET['autosave'] ?? '') === '1'; + try { $db->setFormHelpBlock($key, $content); if ($name !== '') { @@ -64,6 +66,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + + if ($isAutosave) { + header('Content-Type: application/json'); + echo json_encode(['success' => true, 'csrf_token' => $_SESSION['csrf_token']]); + exit; + } + renderCollapsed($db, $key); exit; } @@ -142,9 +151,10 @@ function renderEditor(Database $db, string $key): void $content = $b['content'] ?? ''; ?>