+ + = htmlspecialchars($post['title']) ?> + + = htmlspecialchars($post['title']) ?> + +
+= htmlspecialchars($preview) ?>
+ +diff --git a/data/private_cats.json b/data/private_cats.json index 141462c..9272d7d 100644 --- a/data/private_cats.json +++ b/data/private_cats.json @@ -1 +1 @@ -["scolaire"] \ No newline at end of file +["scolaire","perso","Produits","Journal personnel","Vie pratique"] \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess index 9575fa6..359b71d 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -45,6 +45,8 @@ RewriteRule ^feed/add/?$ /index.php?action=add_feed [L,QSA] RewriteRule ^feed/delete/?$ /index.php?action=delete_feed [L,QSA] # Profil public auteur + page liens +RewriteRule ^profil/([a-z0-9][a-z0-9-]*)/article/cursor/([0-9a-f-]{36})/?$ /index.php?action=author_articles&slug=$1&cursor=$2 [L,QSA] +RewriteRule ^profil/([a-z0-9][a-z0-9-]*)/article/?$ /index.php?action=author_articles&slug=$1 [L,QSA] 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] diff --git a/public/assets/css/style.css b/public/assets/css/style.css index 850bcef..04bdc1b 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -1479,3 +1479,30 @@ footer.mt-5 { margin-top: 0 !important; } margin: 0; line-height: 1.6; } + +/* Bouton flottant "+" — nouvel article */ +.fab-new { + position: fixed; + bottom: 2rem; + right: 2rem; + z-index: 1050; + width: 3.25rem; + height: 3.25rem; + border-radius: 50%; + background: var(--vl-accent); + color: #fff; + font-size: 1.75rem; + line-height: 3.25rem; + text-align: center; + text-decoration: none; + box-shadow: 0 4px 16px rgba(79,70,229,.45); + transition: background 0.15s, box-shadow 0.15s, transform 0.15s; + user-select: none; +} +.fab-new:hover, +.fab-new:focus { + background: var(--vl-accent-dark); + color: #fff; + box-shadow: 0 6px 20px rgba(79,70,229,.55); + transform: scale(1.08); +} diff --git a/public/assets/js/bio-toggle.js b/public/assets/js/bio-toggle.js new file mode 100644 index 0000000..549bcad --- /dev/null +++ b/public/assets/js/bio-toggle.js @@ -0,0 +1,14 @@ +(function(){ + var bio = document.getElementById('author-bio'); + var btn = document.getElementById('bio-toggle'); + if (!bio || !btn) return; + requestAnimationFrame(function() { + if (bio.scrollHeight > bio.clientHeight + 2) { btn.hidden = false; } + }); + btn.addEventListener('click', function() { + var exp = btn.getAttribute('aria-expanded') === 'true'; + bio.classList.toggle('bio-clamped', exp); + btn.textContent = exp ? 'plus' : 'moins'; + btn.setAttribute('aria-expanded', exp ? 'false' : 'true'); + }); +})(); diff --git a/public/assets/js/links-sortable.js b/public/assets/js/links-sortable.js new file mode 100644 index 0000000..7a02393 --- /dev/null +++ b/public/assets/js/links-sortable.js @@ -0,0 +1,29 @@ +(function() { + const list = document.getElementById('links-sortable'); + if (!list) return; + let dragged = null; + list.querySelectorAll('li').forEach(li => { + li.setAttribute('draggable', true); + li.addEventListener('dragstart', () => { dragged = li; li.style.opacity = '.4'; }); + li.addEventListener('dragend', () => { dragged = null; li.style.opacity = ''; saveOrder(); }); + li.addEventListener('dragover', e => { e.preventDefault(); const after = getDragAfter(list, e.clientY); after ? list.insertBefore(dragged, after) : list.appendChild(dragged); }); + }); + function getDragAfter(container, y) { + return [...container.querySelectorAll('li:not([style*="opacity"])')].reduce((closest, el) => { + const box = el.getBoundingClientRect(); + const offset = y - box.top - box.height / 2; + return offset < 0 && offset > closest.offset ? { offset, element: el } : closest; + }, { offset: Number.NEGATIVE_INFINITY }).element; + } + function saveOrder() { + const form = document.getElementById('reorder-form'); + if (!form) return; + form.querySelectorAll('input').forEach(i => i.remove()); + list.querySelectorAll('li[data-id]').forEach(li => { + const inp = document.createElement('input'); + inp.type = 'hidden'; inp.name = 'order[]'; inp.value = li.dataset.id; + form.appendChild(inp); + }); + form.submit(); + } +})(); diff --git a/public/index.php b/public/index.php index 5f6abce..11b4809 100644 --- a/public/index.php +++ b/public/index.php @@ -881,6 +881,55 @@ switch ($action) { include BASE_PATH . '/templates/author_profile.php'; break; + case 'author_articles': + $authorSlug = trim($_GET['slug'] ?? ''); + $authorRow = profileBySlug($authorSlug); + if (!$authorRow) { + http_response_code(404); + $content = '
Profil introuvable.
Aucun article.
+ + diff --git a/templates/author_articles.php b/templates/author_articles.php new file mode 100644 index 0000000..9f8d530 --- /dev/null +++ b/templates/author_articles.php @@ -0,0 +1,97 @@ + + +Aucun article publié.
+ += htmlspecialchars($preview) ?>
+ +- Voir tous les articles → + Voir tous les articles →
diff --git a/templates/post_list.php b/templates/post_list.php index 6308b7b..ad85eeb 100644 --- a/templates/post_list.php +++ b/templates/post_list.php @@ -55,9 +55,6 @@ ob_start(); = htmlspecialchars(date('d/m/Y', strtotime((string)($post['published_at'] ?? $post['created_at'] ?? '')))) ?> - - modifier - → lire @@ -125,6 +122,10 @@ if (!empty($_tagCats)): + ++ + + "> - +