Gestion des pieces jointes dans edition + SEO par article

This commit is contained in:
Cedric Abonnel
2026-05-08 23:16:36 +02:00
parent e49939826c
commit 81eee8a35a
5 changed files with 203 additions and 49 deletions
+9 -6
View File
@@ -2,25 +2,28 @@
<html lang="fr">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($title ?? 'varlog') ?></title>
<title><?= htmlspecialchars(($seoTitle ?? '') ?: ($title ?? 'varlog')) ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- SEO -->
<meta name="description" content="Varlog est un journal personnel en ligne de Cédrix développé par ces soins. Informatique, hack et loisirs techniques.">
<meta name="description" content="<?= htmlspecialchars(($seoDescription ?? '') ?: 'Varlog est un journal personnel en ligne de Cédrix. Informatique, hack et loisirs techniques.') ?>">
<meta name="robots" content="index, follow">
<!-- Open Graph -->
<meta property="og:title" content="<?= htmlspecialchars($title ?? 'varlog') ?>">
<meta property="og:description" content="Découvrez les derniers articles publiés sur le journal personnel varlog.">
<meta property="og:type" content="website">
<meta property="og:title" content="<?= htmlspecialchars(($seoTitle ?? '') ?: ($title ?? 'varlog')) ?>">
<meta property="og:description" content="<?= htmlspecialchars(($seoDescription ?? '') ?: 'Découvrez les derniers articles publiés sur le journal personnel varlog.') ?>">
<meta property="og:type" content="<?= htmlspecialchars($ogType ?? 'website') ?>">
<meta property="og:locale" content="fr_FR">
<meta property="og:url" content="https://varlog.a5l.fr/">
<meta property="og:url" content="<?= htmlspecialchars($ogUrl ?? APP_URL) ?>">
<meta property="og:site_name" content="varlog">
<?php if (!empty($ogImage ?? '')): ?>
<meta property="og:image" content="<?= htmlspecialchars($ogImage) ?>">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<?php endif; ?>
<?php if (!empty($articlePublishedAt ?? '')): ?>
<meta property="article:published_time" content="<?= htmlspecialchars(date('c', strtotime((string)$articlePublishedAt))) ?>">
<?php endif; ?>
<!-- RSS autodiscovery -->
<link rel="alternate" type="application/rss+xml" title="varlog" href="/feed">
+112 -7
View File
@@ -73,14 +73,50 @@ $dateValue = isset($published_at)
<?php if ($action === 'edit' && !empty($existingFiles)): ?>
<div class="mb-3">
<p class="form-label">Fichiers existants</p>
<ul class="list-unstyled">
<?php foreach ($existingFiles as $f): ?>
<li>
<code><?= htmlspecialchars($f['name']) ?></code>
<small class="text-muted ms-2"><?= htmlspecialchars(number_format($f['size'] / 1024, 1)) ?> Ko</small>
</li>
<div class="list-group">
<?php foreach ($existingFiles as $i => $f): ?>
<?php $fileUrl = '/file?uuid=' . rawurlencode($uuid) . '&name=' . rawurlencode($f['name']); ?>
<div class="list-group-item d-flex align-items-center gap-3 py-2">
<?php if ($f['is_image']): ?>
<a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener">
<img src="<?= htmlspecialchars($fileUrl) ?>" alt=""
style="width:48px;height:48px;object-fit:cover;border-radius:4px;flex-shrink:0">
</a>
<?php else: ?>
<a href="<?= htmlspecialchars($fileUrl) ?>" target="_blank" rel="noopener"
style="width:48px;text-align:center;font-size:1.5rem;flex-shrink:0;text-decoration:none">
<?php
$icon = match(true) {
str_starts_with($f['mime'], 'video/') => '🎬',
str_starts_with($f['mime'], 'audio/') => '🎵',
$f['mime'] === 'application/pdf' => '📑',
default => '📄',
};
echo $icon;
?>
</a>
<?php endif; ?>
<div class="flex-grow-1 overflow-hidden">
<code class="d-block text-truncate"><?= htmlspecialchars($f['name']) ?></code>
<small class="text-muted">
<?= htmlspecialchars(number_format($f['size'] / 1024, 1)) ?> Ko
&mdash; <?= htmlspecialchars($f['mime']) ?>
</small>
</div>
<div class="d-flex gap-2 flex-shrink-0">
<button type="button" class="btn btn-sm btn-outline-secondary"
onclick="copyMdRef(<?= htmlspecialchars(json_encode($f['name'])) ?>, <?= $f['is_image'] ? 'true' : 'false' ?>, this)">
Référence MD
</button>
<button type="submit" form="del-file-<?= $i ?>"
class="btn btn-sm btn-outline-danger"
onclick="return confirm('Supprimer « <?= htmlspecialchars(addslashes($f['name'])) ?> » définitivement ?')">
Supprimer
</button>
</div>
</div>
<?php endforeach; ?>
</ul>
</div>
</div>
<?php endif; ?>
@@ -92,10 +128,70 @@ $dateValue = isset($published_at)
</div>
<?php endif; ?>
<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>
<button type="submit" class="btn btn-success">Enregistrer</button>
<a href="/" class="btn btn-secondary">Annuler</a>
</form>
<?php if ($action === 'edit' && !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; ?>
<script>
function slugify(s) {
const map = {'à':'a','â':'a','ä':'a','é':'e','è':'e','ê':'e','ë':'e','î':'i','ï':'i','ô':'o','ö':'o','ù':'u','û':'u','ü':'u','ç':'c','æ':'ae','œ':'oe'};
@@ -121,6 +217,15 @@ document.getElementById('slug').addEventListener('input', function() {
const s = document.getElementById('slug');
if (s.value !== '') s._auto = false;
})();
function copyMdRef(name, isImage, btn) {
const ref = isImage ? `![](${name})` : `[${name}](${name})`;
navigator.clipboard.writeText(ref).then(() => {
const orig = btn.textContent;
btn.textContent = 'Copié !';
setTimeout(() => { btn.textContent = orig; }, 1500);
});
}
</script>
<?php
+8 -2
View File
@@ -87,6 +87,12 @@ $ogImage = $coverFile !== ''
<?php endif; ?>
<?php
$content = ob_get_clean();
$title = htmlspecialchars($article['title']);
$content = ob_get_clean();
$title = htmlspecialchars($article['title']);
$seoTitle = ($article['seo_title'] ?? '') ?: $article['title'];
$seoDescription = $article['seo_description'] ?? '';
$ogImage = $article['og_image'] ?? '';
$ogType = 'article';
$ogUrl = url('post/' . rawurlencode($article['slug'] ?? ''));
$articlePublishedAt = $article['published_at'] ?? '';
include __DIR__ . '/layout.php';