Forum: Anpinnen pro Thema/global + Admin-Berechtigung (v1303)
- Anpinnen-Scope: pin_scope ('global' | 'kategorie'). Global haelt oben in
jeder Ansicht; Themen-Pin nur in der gefilterten Kategorie (nicht in 'Alle').
- Bugfix Berechtigung: Forum pruefte nur is_moderator -> Admins ohne das Flag
wurden ausgesperrt. Neuer Helper _can_moderate() = rolle in (admin,moderator)
ODER is_moderator, an allen 7 Forum-Checks + beiden Frontend-isMod-Gates.
- Thread-Detail-Toolbar (nur Admin/Mod): 'Global anpinnen' / 'Im Thema anpinnen'
/ 'Loesen' + Status- und Badge-Anzeige nach Scope.
- DB-Migration forum_threads.pin_scope (idempotent, Default 'global').
- Tests: tests/test_forum_pinning.py (Berechtigung + Scope-Sortierung).
This commit is contained in:
parent
901df5468c
commit
ac0814e687
9 changed files with 175 additions and 36 deletions
|
|
@ -82,7 +82,8 @@ function _fmtDate(iso) {
|
|||
// RENDER — Grundstruktur
|
||||
// ----------------------------------------------------------
|
||||
function _render() {
|
||||
const isMod = !!_appState.user?.is_moderator;
|
||||
const _u = _appState.user;
|
||||
const isMod = !!(_u && (_u.rolle === 'admin' || _u.rolle === 'moderator' || _u.is_moderator));
|
||||
|
||||
_container.innerHTML = `
|
||||
<div class="forum-layout">
|
||||
|
|
@ -438,7 +439,7 @@ function _fmtDate(iso) {
|
|||
const preview = t.text_preview
|
||||
? UI.escape(t.text_preview.slice(0, 120)) + (t.text_preview.length >= 120 ? '…' : '')
|
||||
: '';
|
||||
const pinBadge = t.is_pinned ? `<span class="forum-pin-badge" title="Angepinnt">${UI.icon('push-pin')}</span>` : '';
|
||||
const pinBadge = t.is_pinned ? `<span class="forum-pin-badge" title="${t.pin_scope === 'kategorie' ? 'Im Thema angepinnt' : 'Angepinnt'}">${UI.icon('push-pin')}</span>` : '';
|
||||
const lockBadge = t.is_locked ? `<span class="forum-lock-badge" title="Gesperrt">${UI.icon('lock')}</span>` : '';
|
||||
const fotoHtml = t.foto_preview
|
||||
? /\.(mp4|mov|webm|m4v|avi)$/i.test(t.foto_preview)
|
||||
|
|
@ -515,14 +516,25 @@ function _fmtDate(iso) {
|
|||
}
|
||||
|
||||
const uid = _appState.user?.id;
|
||||
const isMod = !!_appState.user?.is_moderator;
|
||||
const _u = _appState.user;
|
||||
const isMod = !!(_u && (_u.rolle === 'admin' || _u.rolle === 'moderator' || _u.is_moderator));
|
||||
const isOwn = uid && uid === thread.user_id;
|
||||
|
||||
const pinControls = thread.is_pinned
|
||||
? `<span class="forum-pin-state" style="display:inline-flex;align-items:center;gap:4px;font-size:var(--text-sm);color:var(--c-text-secondary)">
|
||||
${UI.icon('push-pin')} Angepinnt${thread.pin_scope === 'kategorie' ? ` (Thema „${UI.escape(thread.kategorie)}")` : ' (global)'}
|
||||
</span>
|
||||
<button class="btn btn-ghost btn-sm forum-mod-unpin" title="Anpinnen aufheben">Lösen</button>`
|
||||
: `<button class="btn btn-ghost btn-sm forum-mod-pin-global" title="Überall ganz oben halten">
|
||||
${UI.icon('push-pin')} Global anpinnen
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm forum-mod-pin-cat" title="Nur im Thema „${UI.escape(thread.kategorie)}" oben halten">
|
||||
${UI.icon('push-pin')} Im Thema anpinnen
|
||||
</button>`;
|
||||
|
||||
const modToolbar = (isMod) ? `
|
||||
<div class="forum-mod-toolbar">
|
||||
<button class="btn btn-ghost btn-sm forum-mod-pin" title="${thread.is_pinned ? 'Unpin' : 'Anpinnen'}">
|
||||
${UI.icon('push-pin')} ${thread.is_pinned ? 'Unpin' : 'Pin'}
|
||||
</button>
|
||||
${pinControls}
|
||||
<button class="btn btn-ghost btn-sm forum-mod-lock" title="${thread.is_locked ? 'Entsperren' : 'Sperren'}">
|
||||
${UI.icon('lock')} ${thread.is_locked ? 'Entsperren' : 'Sperren'}
|
||||
</button>
|
||||
|
|
@ -677,14 +689,20 @@ function _fmtDate(iso) {
|
|||
});
|
||||
|
||||
// Moderator: pin/lock/delete
|
||||
document.querySelector('.forum-mod-pin')?.addEventListener('click', async () => {
|
||||
const _applyPin = async (payload) => {
|
||||
try {
|
||||
await API.forum.patchThread(thread.id, { is_pinned: thread.is_pinned ? 0 : 1 });
|
||||
await API.forum.patchThread(thread.id, payload);
|
||||
UI.toast.success('Gespeichert.');
|
||||
UI.modal.close();
|
||||
_loadThreads(true);
|
||||
} catch (err) { UI.toast.error(err.message); }
|
||||
});
|
||||
};
|
||||
document.querySelector('.forum-mod-pin-global')?.addEventListener('click',
|
||||
() => _applyPin({ is_pinned: 1, pin_scope: 'global' }));
|
||||
document.querySelector('.forum-mod-pin-cat')?.addEventListener('click',
|
||||
() => _applyPin({ is_pinned: 1, pin_scope: 'kategorie' }));
|
||||
document.querySelector('.forum-mod-unpin')?.addEventListener('click',
|
||||
() => _applyPin({ is_pinned: 0 }));
|
||||
|
||||
document.querySelector('.forum-mod-lock')?.addEventListener('click', async () => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue