6895a3bf65
Remplace le formulaire unique par un wizard 5 étapes (création) et
6 étapes (édition) avec auto-sauvegarde en brouillon, détection de
tags depuis le texte (TagSuggester), aperçu SEO, diff avant validation
et plan Markdown dynamique dans l'éditeur.
Détail des changements :
- ArticleManager : +6 méthodes (updatePartialMeta, saveDraftOverlay,
getDraftOverlay, hasDraftOverlay, discardDraftOverlay, commitDraftOverlay)
- .htaccess : routes /new/{uuid}/{1-5} et /edit/{uuid}/{1-6}
- index.php : cases create et edit réécrits en switch($step),
nouveau case autosave_draft et edit_discard_draft
- assets/js/wizard.js : autosave debounce, auto-resize textarea,
scroll curseur, plan TOC dynamique, toggle pills tags
- templates/wizard/ : nav.php + step1..6.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
183 lines
9.2 KiB
PHP
183 lines
9.2 KiB
PHP
<?php
|
|
// Attendu : $mode, $step, $totalSteps, $uuid, $formAction, $title, $content (article),
|
|
// $existingFiles, $insertUrl, $article, $errors
|
|
ob_start();
|
|
$_wizUuid = $uuid ?? '';
|
|
$_backHref = '/';
|
|
$_hasUuid = $_wizUuid !== '';
|
|
?>
|
|
<div id="vl-page"
|
|
data-uuid="<?= htmlspecialchars($_wizUuid) ?>"
|
|
data-insert-url="<?= htmlspecialchars($insertUrl ?? '') ?>"
|
|
data-autosave-url="<?= $mode === 'edit'
|
|
? '/?action=autosave_draft&uuid=' . rawurlencode($_wizUuid)
|
|
: '/?action=autosave&uuid=' . rawurlencode($_wizUuid) ?>"
|
|
hidden></div>
|
|
|
|
<form method="POST" action="<?= htmlspecialchars($formAction) ?>" enctype="multipart/form-data">
|
|
|
|
<!-- En-tête avec boutons ────────────────────────────────────────────────── -->
|
|
<div class="d-flex align-items-center justify-content-between gap-3 mb-4 flex-wrap">
|
|
<div>
|
|
<h1 class="h4 mb-0"><?= $mode === 'create' ? 'Nouvel article' : htmlspecialchars('Modifier — ' . ($article['title'] ?? '')) ?></h1>
|
|
<?php if ($_hasUuid): ?>
|
|
<span id="autosave-indicator" class="text-muted small"></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="d-flex gap-2 align-items-center">
|
|
<a href="<?= htmlspecialchars($_backHref) ?>" class="btn btn-outline-secondary btn-sm">Annuler</a>
|
|
<button type="submit" class="btn btn-primary">Suivant →</button>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!empty($errors)): ?>
|
|
<div class="alert alert-danger mb-3"><ul class="mb-0"><?php foreach ($errors as $_e): ?><li><?= htmlspecialchars($_e) ?></li><?php endforeach; ?></ul></div>
|
|
<?php endif; ?>
|
|
|
|
<?php include __DIR__ . '/nav.php'; ?>
|
|
|
|
<div class="row g-3 align-items-start">
|
|
<div class="col-lg-9">
|
|
|
|
<!-- Titre ─────────────────────────────────────────────────────────────── -->
|
|
<div class="mb-3">
|
|
<label for="wz-title" class="form-label fw-semibold">Titre</label>
|
|
<input type="text" class="form-control form-control-lg" id="wz-title" name="title" required
|
|
value="<?= htmlspecialchars($title ?? '') ?>"
|
|
placeholder="Titre de l'article…">
|
|
</div>
|
|
|
|
<!-- Contenu ──────────────────────────────────────────────────────────── -->
|
|
<div class="mb-3">
|
|
<label for="wz-content" class="form-label fw-semibold">Contenu <small class="text-muted fw-normal">(Markdown)</small></label>
|
|
<textarea class="form-control font-monospace" id="wz-content" name="content" rows="18"
|
|
style="min-height:320px"><?= htmlspecialchars($content ?? '') ?></textarea>
|
|
</div>
|
|
|
|
</div><!-- /col-lg-9 -->
|
|
|
|
<!-- Plan (TOC dynamique) ───────────────────────────────────────────────── -->
|
|
<div class="col-lg-3 d-none d-lg-block">
|
|
<div class="position-sticky" style="top:1rem">
|
|
<div class="card border-secondary-subtle">
|
|
<div class="card-header bg-transparent py-2 small fw-semibold text-muted">Plan</div>
|
|
<div class="card-body p-2" style="max-height:80vh;overflow-y:auto">
|
|
<ul id="wz-toc-list" class="list-unstyled mb-0"></ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div><!-- /row -->
|
|
|
|
<!-- Fichiers / Import ─────────────────────────────────────────────────── -->
|
|
<?php if (!$_hasUuid): ?>
|
|
<div class="mb-4">
|
|
<label for="files" class="form-label fw-semibold">Ajouter des fichiers <small class="text-muted fw-normal">(optionnel)</small></label>
|
|
<input type="file" class="form-control" id="files" name="files[]" multiple>
|
|
<div class="form-text">Les fichiers seront attachés à l'article après création.</div>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="d-flex flex-wrap gap-2 mb-4">
|
|
<a href="/files/<?= rawurlencode($_wizUuid) ?>/add?back=<?= rawurlencode($formAction) ?>"
|
|
class="btn btn-outline-secondary">+ Ajouter des fichiers</a>
|
|
<a href="/import/<?= rawurlencode($_wizUuid) ?>?back=<?= rawurlencode($formAction) ?>"
|
|
class="btn btn-outline-secondary">+ Importer depuis une URL</a>
|
|
</div>
|
|
|
|
<?php if (!empty($existingFiles)): ?>
|
|
<?php $_coverFile = ($article ?? [])['cover'] ?? ''; ?>
|
|
<div class="mb-3">
|
|
<p class="fw-semibold small mb-2">Fichiers attachés (<?= count($existingFiles) ?>)</p>
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<?php foreach ($existingFiles as $_fi => $_f):
|
|
$_fUrl = '/file?uuid=' . rawurlencode($_wizUuid) . '&name=' . rawurlencode($_f['name']);
|
|
$_isCover = ($_f['name'] === $_coverFile);
|
|
?>
|
|
<div class="border rounded p-1 d-flex align-items-center gap-2" style="max-width:220px">
|
|
<?php if ($_f['is_image']): ?>
|
|
<img src="<?= htmlspecialchars($_fUrl) ?>" alt="" style="width:36px;height:36px;object-fit:cover;border-radius:3px;flex-shrink:0<?= $_isCover ? ';outline:2px solid #0d6efd' : '' ?>">
|
|
<?php else: ?>
|
|
<span style="width:36px;text-align:center;font-size:1.1rem;flex-shrink:0"><?= match(true) {
|
|
str_starts_with($_f['mime'], 'video/') => '🎬',
|
|
str_starts_with($_f['mime'], 'audio/') => '🎵',
|
|
$_f['mime'] === 'application/pdf' => '📑',
|
|
default => '📄',
|
|
} ?></span>
|
|
<?php endif; ?>
|
|
<div class="overflow-hidden flex-grow-1" style="min-width:0">
|
|
<div class="text-truncate small"><?= htmlspecialchars($_f['name']) ?></div>
|
|
<div class="d-flex gap-1 mt-1">
|
|
<button type="button" class="btn btn-xs btn-outline-secondary"
|
|
data-copy-md-name="<?= htmlspecialchars($_f['name']) ?>"
|
|
data-copy-md-is-image="<?= $_f['is_image'] ? '1' : '0' ?>"
|
|
style="font-size:.65rem;padding:.1rem .35rem">MD</button>
|
|
<button type="submit" form="del-file-wz-<?= $_fi ?>"
|
|
class="btn btn-xs btn-outline-danger"
|
|
data-confirm="Supprimer « <?= htmlspecialchars($_f['name']) ?> » ?"
|
|
style="font-size:.65rem;padding:.1rem .35rem">✕</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php $_sidebarImgs = array_filter($existingFiles ?? [], fn ($_f) => $_f['is_image']); ?>
|
|
<?php if ($_sidebarImgs): ?>
|
|
<div class="mb-3">
|
|
<p class="fw-semibold small mb-1">Images <span class="text-muted fw-normal">(clic → insère dans le contenu)</span></p>
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<?php foreach ($_sidebarImgs as $_img):
|
|
$_iUrl = '/file?uuid=' . rawurlencode($_wizUuid) . '&name=' . rawurlencode($_img['name']);
|
|
?>
|
|
<img src="<?= htmlspecialchars($_iUrl) ?>"
|
|
alt="<?= htmlspecialchars($_img['name']) ?>"
|
|
title="<?= htmlspecialchars($_img['name']) ?>"
|
|
data-insert-ref="<?= htmlspecialchars($_img['name']) ?>"
|
|
style="width:64px;height:64px;object-fit:cover;border-radius:5px;cursor:pointer;border:2px solid transparent;transition:border-color .15s">
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php $_extLinks = ($article ?? [])['external_links'] ?? []; ?>
|
|
<?php if ($_extLinks): ?>
|
|
<div class="mb-3">
|
|
<p class="fw-semibold small mb-1">Liens externes</p>
|
|
<ul class="list-group list-group-flush" style="max-width:480px">
|
|
<?php foreach ($_extLinks as $_el): ?>
|
|
<li class="list-group-item px-0 py-1 d-flex align-items-center gap-2 border-0 border-bottom">
|
|
<span class="flex-grow-1 text-truncate small"
|
|
data-insert-ref="<?= htmlspecialchars($_el['url']) ?>"
|
|
style="cursor:pointer;color:#0d6efd;text-decoration:underline dotted"
|
|
title="<?= htmlspecialchars($_el['url']) ?>"><?= htmlspecialchars($_el['name']) ?></span>
|
|
<form method="POST" action="/?action=delete_external_link&uuid=<?= rawurlencode($_wizUuid) ?>" class="d-inline flex-shrink-0">
|
|
<input type="hidden" name="url" value="<?= htmlspecialchars($_el['url']) ?>">
|
|
<button type="submit" class="btn btn-link btn-sm text-danger p-0 lh-1"
|
|
data-confirm="Supprimer ce lien ?" title="Supprimer">✕</button>
|
|
</form>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
|
|
</form>
|
|
|
|
<?php if (!empty($existingFiles)): ?>
|
|
<?php foreach ($existingFiles as $_fi => $_f): ?>
|
|
<form id="del-file-wz-<?= $_fi ?>" method="POST"
|
|
action="/?action=delete_file&uuid=<?= rawurlencode($_wizUuid) ?>&_back=<?= rawurlencode($formAction) ?>">
|
|
<input type="hidden" name="name" value="<?= htmlspecialchars($_f['name']) ?>">
|
|
</form>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
|
|
<script src="/assets/js/wizard.js"></script>
|
|
<?php
|
|
$content = ob_get_clean();
|
|
$title = ($mode === 'create' ? 'Nouvel article' : 'Modifier') . ' — Étape 1/' . $totalSteps;
|
|
include BASE_PATH . '/templates/layout.php';
|