Files
varlog/templates/post_form.php
T
2026-05-12 10:04:58 +02:00

489 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
ob_start();
$dateValue = isset($published_at)
? (str_contains($published_at, ' ')
? date('Y-m-d\TH:i', strtotime($published_at))
: $published_at)
: date('Y-m-d\TH:i');
?>
<?php if ($action === 'edit'): ?>
<div id="vl-page"
data-uuid="<?= htmlspecialchars($uuid) ?>"
data-insert-url="<?= htmlspecialchars($insertUrl ?? '') ?>"
hidden></div>
<form method="POST" action="<?= htmlspecialchars($formAction) ?>">
<div class="row g-4">
<div class="col-lg-8">
<h1 class="mb-4">Modifier l'article</h1>
<?php else: ?>
<h1 class="mb-4">Nouvel article</h1>
<form method="POST" action="<?= htmlspecialchars($formAction) ?>" enctype="multipart/form-data">
<?php endif; ?>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($errors as $error): ?>
<li><?= htmlspecialchars($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="mb-3">
<label for="title" class="form-label">Titre</label>
<input type="text" class="form-control" id="title" name="title" required
value="<?= htmlspecialchars($title ?? '') ?>">
</div>
<div class="mb-3">
<label for="slug" class="form-label">
Slug <small class="text-muted">(URL : /post/<span id="slug-preview"><?= htmlspecialchars($postSlug ?? '') ?></span>)</small>
</label>
<input type="text" class="form-control form-control-sm font-monospace" id="slug" name="slug"
value="<?= htmlspecialchars($postSlug ?? '') ?>"
pattern="[a-z0-9][a-z0-9\-]*"
placeholder="généré automatiquement depuis le titre">
</div>
<?php if ($action === 'create'): ?>
<div class="mb-3">
<label for="category" class="form-label">Catégorie</label>
<div class="d-flex align-items-center gap-2">
<input type="text" class="form-control form-control-sm" id="category" name="category"
value="<?= htmlspecialchars($category ?? '') ?>"
placeholder="ex : informatique, loisirs, photo…"
autocomplete="off">
<div id="cat-swatch" title="" style="width:40px;height:28px;border-radius:6px;flex-shrink:0;background:#e5e7eb;transition:background .25s"></div>
</div>
<small id="cat-hint" class="text-muted d-block mt-1"></small>
<div id="cat-free-swatches" class="d-flex flex-wrap gap-1 mt-2"></div>
</div>
<?php endif; ?>
<div class="mb-2">
<small class="text-muted">
Écris en <strong>Markdown</strong> — les fichiers uploadés sont référençables dans le contenu :
<code>![alt](nom-du-fichier.jpg)</code>
</small>
</div>
<div class="mb-3">
<label for="content" class="form-label">Contenu</label>
<textarea class="form-control font-monospace" id="content" name="content" rows="12"><?= htmlspecialchars($content ?? '') ?></textarea>
</div>
<?php if ($action === 'create'): ?>
<div class="row mb-3">
<div class="col-md-6">
<label for="published_at" class="form-label">Date de publication</label>
<input type="datetime-local" class="form-control" id="published_at" name="published_at" value="<?= $dateValue ?>">
</div>
<div class="col-md-6 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="published" name="published"
<?= ($published ?? false) ? 'checked' : '' ?>>
<label class="form-check-label" for="published">Publié</label>
</div>
</div>
</div>
<div class="mb-3">
<label for="files" class="form-label">Ajouter des fichiers</label>
<input type="file" class="form-control" id="files" name="files[]" multiple>
<div class="form-text">Images → nommées <code>sha256-taille.ext</code>. Vidéos, PDF → nom sanitisé.</div>
</div>
<?php endif; ?>
<?php if ($action === 'edit'): ?>
</div><!-- /col-lg-8 -->
<div class="col-lg-4">
<div class="mb-3">
<label for="category" class="form-label">Catégorie</label>
<div class="d-flex align-items-center gap-2">
<input type="text" class="form-control form-control-sm" id="category" name="category"
value="<?= htmlspecialchars($category ?? '') ?>"
placeholder="ex : informatique, loisirs, photo…"
autocomplete="off">
<div id="cat-swatch" title="" style="width:40px;height:28px;border-radius:6px;flex-shrink:0;background:#e5e7eb;transition:background .25s"></div>
</div>
<small id="cat-hint" class="text-muted d-block mt-1"></small>
<div id="cat-free-swatches" class="d-flex flex-wrap gap-1 mt-2"></div>
</div>
<div class="mb-3">
<label for="published_at" class="form-label">Date de publication</label>
<input type="datetime-local" class="form-control" id="published_at" name="published_at" value="<?= $dateValue ?>">
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="published" name="published"
<?= ($published ?? false) ? 'checked' : '' ?>>
<label class="form-check-label" for="published">Publié</label>
</div>
</div>
<div class="mb-3">
<label for="revision_comment" class="form-label">Commentaire de révision <small class="text-muted">(optionnel)</small></label>
<input type="text" class="form-control" id="revision_comment" name="revision_comment"
placeholder="ex. Correction typos, ajout section X…">
</div>
<div class="card mb-3 border-secondary">
<div class="card-header bg-transparent py-2">
<button class="btn btn-sm btn-link text-secondary text-decoration-none p-0 fw-semibold"
type="button" data-bs-toggle="collapse" data-bs-target="#seoPanel" aria-expanded="false">
▸ SEO — titre, description, image
</button>
</div>
<div class="collapse" id="seoPanel">
<div class="card-body">
<div class="mb-3">
<label for="seo_title" class="form-label">
Titre SEO
<small class="text-muted">(balise &lt;title&gt; et og:title)</small>
</label>
<input type="text" class="form-control" id="seo_title" name="seo_title"
maxlength="70"
value="<?= htmlspecialchars($seoTitle ?? '') ?>"
placeholder="Généré automatiquement depuis le titre">
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Idéal : 3060 caractères</small>
<small id="seo_title_counter" class="text-muted">0 / 60</small>
</div>
</div>
<div class="mb-3">
<label for="seo_description" class="form-label">
Description SEO
<small class="text-muted">(meta description et og:description)</small>
</label>
<textarea class="form-control" id="seo_description" name="seo_description"
rows="3" maxlength="200"
placeholder="Générée automatiquement depuis le début du contenu"><?= htmlspecialchars($seoDescription ?? '') ?></textarea>
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Idéal : 120155 caractères</small>
<small id="seo_desc_counter" class="text-muted">0 / 155</small>
</div>
</div>
<div class="mb-2">
<label for="og_image" class="form-label">
Image Open Graph
<small class="text-muted">(URL absolue, optionnel)</small>
</label>
<input type="url" class="form-control font-monospace" id="og_image" name="og_image"
value="<?= htmlspecialchars($ogImage ?? '') ?>"
placeholder="https://varlog.a5l.fr/…">
</div>
</div>
</div>
</div>
<div class="d-flex align-items-center gap-3 flex-wrap mb-4">
<button type="submit" class="btn btn-success">Enregistrer</button>
<a href="/" class="btn btn-secondary">Annuler</a>
<span id="autosave-indicator" class="text-muted small"></span>
</div>
<hr class="my-3">
<?php if (!empty($existingFiles)): ?>
<?php $coverFile = $article['cover'] ?? ''; ?>
<?php $filesMeta = $article['files_meta'] ?? []; ?>
<div class="mb-3">
<p class="form-label fw-semibold small mb-2">Fichiers existants</p>
<div class="list-group">
<?php foreach ($existingFiles as $i => $f): ?>
<?php
$fileUrl = '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($f['name']);
$fmeta = $filesMeta[$f['name']] ?? [];
$isCoverFile = ($f['name'] === $coverFile);
?>
<div class="list-group-item py-2 px-2">
<div class="d-flex align-items-start gap-2">
<!-- Miniature -->
<?php if ($f['is_image']): ?>
<a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener" class="flex-shrink-0">
<img src="<?= htmlspecialchars($fileUrl) ?>" alt=""
style="width:48px;height:48px;object-fit:cover;border-radius:4px;<?= $isCoverFile ? 'outline:2px solid #0d6efd' : '' ?>">
</a>
<?php else: ?>
<span style="width:48px;text-align:center;font-size:1.4rem;flex-shrink:0;line-height:48px">
<?= match(true) {
str_starts_with($f['mime'], 'video/') => '🎬',
str_starts_with($f['mime'], 'audio/') => '🎵',
$f['mime'] === 'application/pdf' => '📑',
default => '📄',
} ?>
</span>
<?php endif; ?>
<!-- Infos + méta -->
<div class="flex-grow-1 overflow-hidden" style="min-width:0">
<div class="d-flex align-items-center gap-1 mb-1 flex-wrap">
<code class="text-truncate small" style="max-width:100%"><?= htmlspecialchars($f['name']) ?></code>
<small class="text-muted text-nowrap"><?= number_format($f['size'] / 1024, 1) ?> Ko</small>
<?php if ($isCoverFile): ?>
<span class="badge bg-primary">cover</span>
<?php endif; ?>
</div>
<div class="d-flex gap-1 mb-1">
<input type="hidden" name="fmeta_name[]" value="<?= htmlspecialchars($f['name']) ?>">
<input type="text" name="fmeta_author[]"
class="form-control form-control-sm"
placeholder="Auteur / crédit"
value="<?= htmlspecialchars($fmeta['author'] ?? '') ?>">
<input type="url" name="fmeta_source[]"
class="form-control form-control-sm font-monospace"
placeholder="URL source"
value="<?= htmlspecialchars($fmeta['source_url'] ?? '') ?>">
</div>
<div class="d-flex align-items-center gap-1 flex-wrap">
<?php if ($f['is_image'] && !$isCoverFile): ?>
<div class="form-check mb-0">
<input class="form-check-input" type="radio"
name="cover_file" id="cover_<?= $i ?>"
value="<?= htmlspecialchars($f['name']) ?>">
<label class="form-check-label small" for="cover_<?= $i ?>">Cover</label>
</div>
<?php elseif ($isCoverFile): ?>
<input type="hidden" name="cover_file" value="<?= htmlspecialchars($f['name']) ?>">
<small class="text-primary">✓ Cover</small>
<?php endif; ?>
<div class="d-flex gap-1 ms-auto">
<button type="button" class="btn btn-sm btn-outline-secondary"
data-copy-md-name="<?= htmlspecialchars($fmeta['title'] ?? $f['name']) ?>"
data-copy-md-is-image="<?= $f['is_image'] ? '1' : '0' ?>">
MD
</button>
<button type="submit" form="del-file-<?= $i ?>"
class="btn btn-sm btn-outline-danger"
data-confirm="Supprimer « <?= htmlspecialchars($f['name']) ?> » ?">
</button>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<hr class="my-3">
<?php endif; ?>
<?php $sidebarImages = array_filter($existingFiles ?? [], fn ($f) => $f['is_image']); ?>
<?php if ($sidebarImages): ?>
<div class="mb-3">
<p class="fw-semibold small mb-2">
Images disponibles
<span class="text-muted fw-normal">(clic → insère dans le contenu)</span>
</p>
<div class="d-flex flex-wrap gap-2">
<?php foreach ($sidebarImages as $img): ?>
<?php $imgUrl = '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($img['name']); ?>
<img src="<?= htmlspecialchars($imgUrl) ?>"
alt="<?= htmlspecialchars($img['name']) ?>"
title="<?= htmlspecialchars($img['name']) ?>"
data-insert-ref="<?= htmlspecialchars($img['name']) ?>"
style="width:72px;height:72px;object-fit:cover;border-radius:6px;cursor:pointer;border:2px solid transparent;transition:border-color .15s">
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php $externalLinks = $article['external_links'] ?? []; ?>
<?php if ($externalLinks): ?>
<div class="mb-3">
<p class="fw-semibold small mb-2">Liens externes</p>
<ul class="list-group list-group-flush">
<?php foreach ($externalLinks as $extLink): ?>
<?php
$elUrl = $extLink['url'];
$elName = $extLink['name'];
$elIsImg = (bool)preg_match('/\.(jpe?g|png|gif|webp|svg|avif)(\?.*)?$/i', $elUrl);
?>
<li class="list-group-item px-0 py-1 d-flex align-items-center gap-2 border-0 border-bottom">
<span class="flex-shrink-0" style="font-size:1rem">
<?= $elIsImg ? '🖼' : '📄' ?>
</span>
<span class="flex-grow-1 text-truncate small"
title="<?= htmlspecialchars($elUrl) ?>"
data-insert-ref="<?= htmlspecialchars($elUrl) ?>"
style="cursor:pointer;color:#0d6efd;text-decoration:underline dotted">
<?= htmlspecialchars($elName) ?>
</span>
<form method="POST" action="/?action=delete_external_link&uuid=<?= rawurlencode($uuid) ?>"
class="d-inline flex-shrink-0">
<input type="hidden" name="url" value="<?= htmlspecialchars($elUrl) ?>">
<button type="submit" class="btn btn-link btn-sm text-danger p-0 lh-1"
data-confirm="Supprimer ce lien externe ?"
title="Supprimer">✕</button>
</form>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="d-flex flex-column gap-2">
<a href="/files/<?= rawurlencode($uuid) ?>/add" class="btn btn-outline-secondary btn-sm">
+ Ajouter des fichiers
</a>
<a href="/import/<?= rawurlencode($uuid) ?>" class="btn btn-outline-secondary btn-sm">
+ Importer depuis une URL
</a>
<?php
$hasSources = !empty($article['external_links']) || !empty($existingFiles);
if ($hasSources):
?>
<a href="/sources/<?= rawurlencode($uuid) ?>" class="btn btn-outline-secondary btn-sm">
Sources &amp; métadonnées
</a>
<?php endif; ?>
</div>
</div><!-- /col-lg-4 -->
</div><!-- /row -->
</form>
<?php if (!empty($existingFiles)): ?>
<?php foreach ($existingFiles as $i => $f): ?>
<form id="del-file-<?= $i ?>" method="POST"
action="/?action=delete_file&uuid=<?= rawurlencode($uuid) ?>">
<input type="hidden" name="name" value="<?= htmlspecialchars($f['name']) ?>">
</form>
<?php endforeach; ?>
<?php endif; ?>
<?php if (!empty($article['revisions'])): ?>
<hr class="my-4">
<div>
<div class="d-flex align-items-center gap-3 mb-1">
<button class="btn btn-sm btn-link text-secondary text-decoration-none p-0 fw-semibold"
type="button" data-bs-toggle="collapse" data-bs-target="#historyPanel">
▸ Historique des révisions (<?= count($article['revisions']) ?>)
</button>
<form method="POST" action="/?action=delete_all_revisions&uuid=<?= rawurlencode($uuid) ?>" class="d-inline ms-auto">
<button type="submit" class="btn btn-sm btn-outline-danger"
data-confirm="Supprimer tout l'historique des révisions ?">
Tout supprimer
</button>
</form>
</div>
<div class="collapse mt-3" id="historyPanel">
<table class="table table-sm table-hover align-middle">
<thead>
<tr><th>#</th><th>Date</th><th>Titre à l'époque</th><th>Commentaire</th><th></th></tr>
</thead>
<tbody>
<?php foreach (array_reverse($article['revisions']) as $rev): ?>
<?php $revN = (int)($rev['n'] ?? 0); ?>
<tr>
<td class="text-muted small"><?= $revN ?></td>
<td class="small text-nowrap">
<?= htmlspecialchars(date('d/m/Y H:i', strtotime((string)($rev['date'] ?? '')))) ?>
</td>
<td class="small text-truncate" style="max-width:200px">
<?= htmlspecialchars($rev['title'] ?? '') ?>
</td>
<td class="small text-muted">
<?= htmlspecialchars($rev['comment'] ?? '') ?: '<span class="text-muted"></span>' ?>
</td>
<td class="text-nowrap">
<a href="/diff/<?= rawurlencode($uuid) ?>/<?= $revN ?>"
class="btn btn-outline-secondary btn-sm" target="_blank">
Diff
</a>
<form method="POST" action="/?action=delete_revision&uuid=<?= rawurlencode($uuid) ?>" class="d-inline">
<input type="hidden" name="rev_n" value="<?= $revN ?>">
<button type="submit" class="btn btn-sm btn-outline-danger"
data-confirm="Supprimer la révision #<?= $revN ?> ?">
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php else: /* create */ ?>
<div class="card mb-3 border-secondary">
<div class="card-header bg-transparent py-2">
<button class="btn btn-sm btn-link text-secondary text-decoration-none p-0 fw-semibold"
type="button" data-bs-toggle="collapse" data-bs-target="#seoPanel" aria-expanded="false">
▸ SEO — titre, description, image
</button>
</div>
<div class="collapse" id="seoPanel">
<div class="card-body">
<div class="mb-3">
<label for="seo_title" class="form-label">
Titre SEO
<small class="text-muted">(balise &lt;title&gt; et og:title)</small>
</label>
<input type="text" class="form-control" id="seo_title" name="seo_title"
maxlength="70"
value="<?= htmlspecialchars($seoTitle ?? '') ?>"
placeholder="Généré automatiquement depuis le titre">
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Idéal : 3060 caractères</small>
<small id="seo_title_counter" class="text-muted">0 / 60</small>
</div>
</div>
<div class="mb-3">
<label for="seo_description" class="form-label">
Description SEO
<small class="text-muted">(meta description et og:description)</small>
</label>
<textarea class="form-control" id="seo_description" name="seo_description"
rows="3" maxlength="200"
placeholder="Générée automatiquement depuis le début du contenu"><?= htmlspecialchars($seoDescription ?? '') ?></textarea>
<div class="d-flex justify-content-between mt-1">
<small class="text-muted">Idéal : 120155 caractères</small>
<small id="seo_desc_counter" class="text-muted">0 / 155</small>
</div>
</div>
<div class="mb-2">
<label for="og_image" class="form-label">
Image Open Graph
<small class="text-muted">(URL absolue, optionnel)</small>
</label>
<input type="url" class="form-control font-monospace" id="og_image" name="og_image"
value="<?= htmlspecialchars($ogImage ?? '') ?>"
placeholder="https://varlog.a5l.fr/…">
</div>
</div>
</div>
</div>
<div class="d-flex align-items-center gap-3 flex-wrap">
<button type="submit" class="btn btn-success">Enregistrer</button>
<a href="/" class="btn btn-secondary">Annuler</a>
</div>
</form>
<?php endif; ?>
<?php
$content = ob_get_clean();
$title = $action === 'edit' ? 'Modifier l\'article' : 'Nouvel article';
include __DIR__ . '/layout.php';