mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
feat: render actual elements in markdown cheatsheet instead of labels
Replace text labels (h1, bold, italic) with rendered HTML in the Rendu column: headings, strong, em, del, code, links, blockquote, lists, hr, sup, small
This commit is contained in:
@@ -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']) ||
|
||||
|
||||
@@ -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'])) {
|
||||
|
||||
@@ -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'])) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'] ?? '';
|
||||
?>
|
||||
<div class="fhb-inline fhb-inline--editing" data-key="<?= htmlspecialchars($key) ?>">
|
||||
<form hx-post="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="closest .fhb-inline"
|
||||
<form hx-post="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>&autosave=1"
|
||||
hx-trigger="overtype:change delay:1500ms"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="handleAutosaveResponse(event)"
|
||||
class="fhb-inline-form">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
|
||||
@@ -157,17 +167,24 @@ function renderEditor(Database $db, string $key): void
|
||||
</div>
|
||||
|
||||
<label for="fhb-ed-<?= htmlspecialchars($key) ?>" class="fhb-edit-label">Contenu (Markdown) :</label>
|
||||
<label for="fhb-ed-<?= htmlspecialchars($key) ?>" class="fhb-edit-label"><a href="https://herman.bearblog.dev/markdown-cheatsheet/" target="_blank">Syntax Markdown</a></label>
|
||||
<button type="button" class="btn btn--sm"
|
||||
hx-get="/admin/markdown-cheatsheet-fragment.php"
|
||||
hx-target="#md-cheatsheet-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
|
||||
Aide Markdown
|
||||
</button>
|
||||
<input type="hidden" id="fhb-content-<?= htmlspecialchars($key) ?>" name="content"
|
||||
value="<?= htmlspecialchars($content) ?>">
|
||||
<div id="fhb-editor-<?= htmlspecialchars($key) ?>" class="fhb-overtype-editor" style="height: 40vh !important; border: 1px dashed grey"></div>
|
||||
|
||||
<div class="fhb-autosave-status" data-autosave-status></div>
|
||||
|
||||
<div class="fhb-edit-buttons">
|
||||
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
|
||||
<button type="button" class="btn btn--secondary btn--sm"
|
||||
hx-get="/admin/form-help-inline-fragment.php?key=<?= urlencode($key) ?>"
|
||||
hx-target="closest .fhb-inline"
|
||||
hx-swap="outerHTML">Annuler</button>
|
||||
hx-swap="outerHTML">Fermer</button>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
@@ -180,7 +197,11 @@ function renderEditor(Database $db, string $key): void
|
||||
value: hidden.value,
|
||||
minHeight: '400px',
|
||||
spellcheck: false,
|
||||
onChange: function(v) { hidden.value = v; }
|
||||
toolbar: true,
|
||||
onChange: function(v) {
|
||||
hidden.value = v;
|
||||
hidden.dispatchEvent(new CustomEvent('overtype:change', { bubbles: true }));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ed.innerHTML = '<textarea name="content" style="width:100%;min-height:400px;font-family:monospace">'
|
||||
|
||||
124
app/public/admin/markdown-cheatsheet-fragment.php
Normal file
124
app/public/admin/markdown-cheatsheet-fragment.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* Markdown cheatsheet — reusable modal dialog loaded via HTMX.
|
||||
* Rendered as a <dialog> element; caller should call .showModal() after swap.
|
||||
*/
|
||||
|
||||
$rows = [
|
||||
[
|
||||
'syntax' => '# Titre 1',
|
||||
'render' => '<h1>Titre 1</h1>',
|
||||
'note' => 'Un # en début de ligne',
|
||||
],
|
||||
[
|
||||
'syntax' => '## Titre 2',
|
||||
'render' => '<h2>Titre 2</h2>',
|
||||
'note' => 'Deux ## en début de ligne',
|
||||
],
|
||||
[
|
||||
'syntax' => '### Titre 3',
|
||||
'render' => '<h3>Titre 3</h3>',
|
||||
'note' => 'Trois ### en début de ligne',
|
||||
],
|
||||
[
|
||||
'syntax' => '**gras**',
|
||||
'render' => '<strong>gras</strong>',
|
||||
'note' => 'Double astérisque',
|
||||
],
|
||||
[
|
||||
'syntax' => '*italique*',
|
||||
'render' => '<em>italique</em>',
|
||||
'note' => 'Simple astérisque',
|
||||
],
|
||||
[
|
||||
'syntax' => '~~barré~~',
|
||||
'render' => '<del>barré</del>',
|
||||
'note' => 'Double tilde',
|
||||
],
|
||||
[
|
||||
'syntax' => '`code`',
|
||||
'render' => '<code>code</code>',
|
||||
'note' => 'Backticks',
|
||||
],
|
||||
[
|
||||
'syntax' => '[lien](url)',
|
||||
'render' => '<a href="#" class="md-cheatsheet-link">lien</a>',
|
||||
'note' => 'Texte entre crochets, URL entre parenthèses',
|
||||
],
|
||||
[
|
||||
'syntax' => '',
|
||||
'render' => '<span class="md-cheatsheet-img">🖼 image</span>',
|
||||
'note' => 'Point d\'exclamation + même syntaxe que lien',
|
||||
],
|
||||
[
|
||||
'syntax' => '> citation',
|
||||
'render' => '<blockquote>citation</blockquote>',
|
||||
'note' => 'Chevron > en début de ligne',
|
||||
],
|
||||
[
|
||||
'syntax' => '- item',
|
||||
'render' => '<ul><li>item</li></ul>',
|
||||
'note' => 'Tiret + espace',
|
||||
],
|
||||
[
|
||||
'syntax' => '1. item',
|
||||
'render' => '<ol><li>item</li></ol>',
|
||||
'note' => 'Chiffre + point + espace',
|
||||
],
|
||||
[
|
||||
'syntax' => '---',
|
||||
'render' => '<hr>',
|
||||
'note' => 'Triple tiret = ligne horizontale',
|
||||
],
|
||||
[
|
||||
'syntax' => '',
|
||||
'render' => '',
|
||||
'note' => '',
|
||||
],
|
||||
[
|
||||
'syntax' => 'Texte avec [^1]',
|
||||
'render' => 'Texte avec <sup>1</sup>',
|
||||
'note' => 'Appel de note de bas de page',
|
||||
],
|
||||
[
|
||||
'syntax' => '[^1]: La note.',
|
||||
'render' => '<small>1. La note.</small>',
|
||||
'note' => 'Définition de la note (n\'importe où dans le document)',
|
||||
],
|
||||
];
|
||||
?>
|
||||
<dialog id="md-cheatsheet-dialog" class="md-cheatsheet-dialog">
|
||||
<div class="md-cheatsheet-header">
|
||||
<h2>Aide Markdown</h2>
|
||||
<button type="button"
|
||||
class="admin-icon-btn"
|
||||
onclick="this.closest('dialog').close()"
|
||||
title="Fermer"
|
||||
aria-label="Fermer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="md-cheatsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Syntaxe</th>
|
||||
<th>Rendu</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($rows as $row): ?>
|
||||
<?php if ($row['syntax'] === ''): ?>
|
||||
<tr class="md-cheatsheet-separator"><td colspan="3"></td></tr>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td class="md-cheatsheet-syntax"><code><?= htmlspecialchars($row['syntax']) ?></code></td>
|
||||
<td class="md-cheatsheet-render"><?= $row['render'] ?></td>
|
||||
<td class="md-cheatsheet-note"><?= htmlspecialchars($row['note']) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</dialog>
|
||||
Reference in New Issue
Block a user