feat: page Mes liens /liens/{slug} avec gestion et réordonnancement

This commit is contained in:
Cedric Abonnel
2026-05-13 00:12:49 +02:00
parent c820bdcc3f
commit a21628e5ad
6 changed files with 337 additions and 2 deletions
+5 -1
View File
@@ -44,8 +44,12 @@ RewriteRule ^flux/?$ /index.php?action=flux [L,QSA]
RewriteRule ^feed/add/?$ /index.php?action=add_feed [L,QSA]
RewriteRule ^feed/delete/?$ /index.php?action=delete_feed [L,QSA]
# Profil public auteur
# Profil public auteur + page liens
RewriteRule ^profil/([a-z0-9][a-z0-9-]*)/?$ /index.php?action=author&slug=$1 [L,QSA]
RewriteRule ^liens/([a-z0-9][a-z0-9-]*)/?$ /index.php?action=liens&slug=$1 [L,QSA]
RewriteRule ^link/add/?$ /index.php?action=add_link [L,QSA]
RewriteRule ^link/delete/?$ /index.php?action=delete_link [L,QSA]
RewriteRule ^link/reorder/?$ /index.php?action=reorder_links [L,QSA]
# Pages statiques
RewriteRule ^about/?$ /index.php?action=about [L,QSA]
+95
View File
@@ -1286,6 +1286,101 @@ footer.mt-5 { margin-top: 0 !important; }
font-size: .9375rem;
}
/* ─── Page "Mes liens" ───────────────────── */
.liens-page {
max-width: 480px;
margin: 0 auto;
padding: 2.5rem 1rem 4rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
}
.liens-header {
display: flex;
flex-direction: column;
align-items: center;
gap: .75rem;
text-align: center;
}
.liens-avatar {
width: 5rem;
height: 5rem;
border-radius: 50%;
background: var(--vl-accent);
color: #fff;
font-size: 2rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}
.liens-name {
font-size: 1.25rem;
font-weight: 700;
margin: 0;
}
.liens-bio {
font-size: .9rem;
color: var(--vl-muted);
line-height: 1.6;
margin: 0;
}
.liens-list {
width: 100%;
display: flex;
flex-direction: column;
gap: .75rem;
}
.liens-item {
display: flex;
flex-direction: column;
align-items: center;
gap: .2rem;
width: 100%;
padding: .875rem 1.25rem;
border-radius: var(--vl-radius);
border: 1.5px solid var(--vl-border);
background: var(--vl-surface);
text-align: center;
text-decoration: none;
color: var(--vl-text);
font-weight: 600;
transition: border-color .15s, background .15s, transform .1s;
box-shadow: var(--vl-shadow-sm);
}
.liens-item:hover {
border-color: var(--vl-accent);
background: var(--vl-accent-soft);
color: var(--vl-accent);
transform: translateY(-1px);
box-shadow: var(--vl-shadow-md);
}
.liens-item-title { font-size: 1rem; }
.liens-item-desc {
font-size: .8rem;
font-weight: 400;
color: var(--vl-muted);
}
.liens-item:hover .liens-item-desc { color: var(--vl-accent); opacity: .8; }
.liens-footer {
font-size: .8rem;
color: var(--vl-muted);
}
.liens-footer a { color: inherit; }
.liens-footer a:hover { color: var(--vl-accent); }
/* ─── Agrégateur de flux ─────────────────── */
.flux-list {
+98 -1
View File
@@ -22,7 +22,7 @@ $action = $_GET['action'] ?? 'list';
$uuid = $_GET['uuid'] ?? '';
$slug = $_GET['slug'] ?? '';
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category', 'admin_save_site', 'not_found', 'add_feed', 'delete_feed'];
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category', 'admin_save_site', 'not_found', 'add_feed', 'delete_feed', 'add_link', 'delete_link', 'reorder_links'];
$metaRobots = in_array($action, $_noindexActions, true) ? 'noindex, nofollow' : null;
unset($_noindexActions);
@@ -881,6 +881,92 @@ switch ($action) {
include BASE_PATH . '/templates/author_profile.php';
break;
case 'liens':
$liensSlug = trim($_GET['slug'] ?? '');
$liensRow = profileBySlug($liensSlug);
if (!$liensRow) {
http_response_code(404);
$content = '<div class="container py-5"><p class="text-muted">Page introuvable.</p></div>';
$title = 'Page introuvable';
include BASE_PATH . '/templates/layout.php';
break;
}
$_lName = $liensRow['display_name'] ?? '';
$_lBio = $liensRow['bio'] ?? '';
$_lSlug = $liensRow['profile_slug'] ?? '';
$_lInitials = mb_strtoupper(mb_substr($_lName, 0, 1, 'UTF-8'), 'UTF-8');
$profileLinks = [];
$pdo = dbPdo();
if ($pdo) {
try {
$st = $pdo->prepare(
'SELECT id, url, title, description FROM profile_links
WHERE user_email = :e ORDER BY position, id'
);
$st->execute([':e' => $liensRow['email']]);
$profileLinks = $st->fetchAll(PDO::FETCH_ASSOC);
} catch (\Throwable) {
}
}
include BASE_PATH . '/templates/liens.php';
break;
case 'add_link':
requireAuth();
$linkUrl = filter_var(trim($_POST['link_url'] ?? ''), FILTER_VALIDATE_URL) ?: '';
$linkTitle = trim($_POST['link_title'] ?? '');
$linkDesc = trim($_POST['link_desc'] ?? '');
if ($linkUrl !== '') {
$pdo = dbPdo();
if ($pdo) {
try {
$st = $pdo->prepare(
'INSERT INTO profile_links (user_email, url, title, description, position)
VALUES (:e, :u, :t, :d,
COALESCE((SELECT MAX(position)+1 FROM profile_links WHERE user_email = :e), 0))'
);
$st->execute([':e' => currentUserEmail(), ':u' => $linkUrl, ':t' => $linkTitle, ':d' => $linkDesc]);
} catch (\Throwable) {
}
}
}
header('Location: /profile#links');
exit;
case 'delete_link':
requireAuth();
$linkId = (int)($_POST['link_id'] ?? 0);
if ($linkId > 0) {
$pdo = dbPdo();
if ($pdo) {
try {
$st = $pdo->prepare('DELETE FROM profile_links WHERE id = :id AND user_email = :e');
$st->execute([':id' => $linkId, ':e' => currentUserEmail()]);
} catch (\Throwable) {
}
}
}
header('Location: /profile#links');
exit;
case 'reorder_links':
requireAuth();
$order = $_POST['order'] ?? [];
if (is_array($order)) {
$pdo = dbPdo();
if ($pdo) {
try {
$st = $pdo->prepare('UPDATE profile_links SET position = :p WHERE id = :id AND user_email = :e');
foreach (array_values($order) as $pos => $id) {
$st->execute([':p' => $pos, ':id' => (int)$id, ':e' => currentUserEmail()]);
}
} catch (\Throwable) {
}
}
}
header('Location: /profile#links');
exit;
case 'flux':
require_once BASE_PATH . '/src/FeedFetcher.php';
$fetcher = new FeedFetcher(BASE_PATH . '/data/_cache/feeds');
@@ -1878,6 +1964,17 @@ switch ($action) {
if ($profileCurrentUrl === '' && $profileCurrentSlug !== '') {
$profileCurrentUrl = rtrim(APP_URL, '/') . '/profil/' . rawurlencode($profileCurrentSlug);
}
// Liens de la page "Mes liens"
$profileLinks = [];
$pdo = dbPdo();
if ($pdo) {
try {
$st = $pdo->prepare('SELECT id, url, title, description FROM profile_links WHERE user_email = :e ORDER BY position, id');
$st->execute([':e' => currentUserEmail()]);
$profileLinks = $st->fetchAll(PDO::FETCH_ASSOC);
} catch (\Throwable) {
}
}
// Feeds RSS de l'utilisateur
$profileFeeds = [];
$pdo = dbPdo();