Files
xxx-sphere-web/bin/main/static/community/gruppe.html
Mario e2a71ab096
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Hashtags eingeführt
2026-04-11 01:14:33 +02:00

1103 lines
58 KiB
HTML
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.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/img/icon.png" type="image/png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gruppe xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
<style>
.tabs { display:flex; gap:0; margin-bottom:1.5rem; border-bottom:1px solid var(--color-secondary); }
.tab-btn { background:none; border:none; border-bottom:3px solid transparent; border-radius:0; padding:0.6rem 1.25rem; font-size:0.95rem; font-weight:600; color:var(--color-muted); cursor:pointer; margin-bottom:-1px; transition:color 0.15s,border-color 0.15s; }
.tab-btn:hover { color:var(--color-text); background:none; }
.tab-btn.active { color:var(--color-primary); border-bottom-color:var(--color-primary); }
.tab-panel { display:none; }
.tab-panel.active { display:block; }
/* Header */
.gruppe-header { display:flex; align-items:center; gap:1rem; margin-bottom:1.5rem; flex-wrap:wrap; }
.gruppe-avatar { width:72px; height:72px; border-radius:12px; background:var(--color-secondary); display:flex; align-items:center; justify-content:center; font-size:2rem; flex-shrink:0; overflow:hidden; }
.gruppe-avatar img { width:100%; height:100%; object-fit:cover; }
.gruppe-header-info h2 { margin:0 0 0.2rem; font-size:1.3rem; }
.gruppe-header-info p { margin:0; font-size:0.85rem; color:var(--color-muted); }
.gruppe-header-actions { margin-left:auto; display:flex; gap:0.5rem; }
.gruppe-header-actions button, .gruppe-header-actions a.btn { margin:0; width:auto; padding:0.4rem 0.9rem; font-size:0.85rem; }
/* Posts */
.post-compose { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:10px; padding:1rem; margin-bottom:1rem; }
.compose-type { display:flex; gap:1.5rem; margin-bottom:0.75rem; }
.compose-type label { display:flex; align-items:center; gap:0.4rem; font-size:0.9rem; cursor:pointer; }
.post-compose textarea { width:100%; padding:0.6rem 0.85rem; border:1px solid var(--color-secondary); border-radius:6px; background:var(--color-secondary); color:var(--color-text); font-size:0.95rem; outline:none; transition:border-color 0.2s; resize:vertical; min-height:70px; box-sizing:border-box; }
.post-compose textarea:focus { border-color:var(--color-primary); }
.compose-thumbs { display:none; flex-wrap:wrap; gap:0.5rem; margin-top:0.5rem; }
.compose-thumb { position:relative; width:64px; height:64px; flex-shrink:0; }
.compose-thumb img { width:64px; height:64px; object-fit:cover; border-radius:6px; display:block; }
.compose-thumb-remove { position:absolute; top:-5px; right:-5px; background:rgba(0,0,0,0.7); border:none; color:#fff; width:18px; height:18px; border-radius:50%; font-size:0.65rem; cursor:pointer; display:flex; align-items:center; justify-content:center; padding:0; margin:0; width:auto; line-height:1; }
.compose-action-btn { background:none; border:1px solid var(--color-secondary); color:var(--color-muted); border-radius:6px; padding:0.35rem 0.6rem; font-size:0.95rem; cursor:pointer; margin:0; width:auto; transition:border-color 0.15s,color 0.15s; }
.compose-action-btn:hover { border-color:var(--color-primary); color:var(--color-primary); background:none; }
label.compose-action-btn { display:inline-flex; align-items:center; }
.umfrage-options { margin-top:0.5rem; }
.post-bild { width:100%; max-height:400px; object-fit:contain; border-radius:6px; margin-top:0.5rem; display:block; }
.umfrage-option-row { display:flex; gap:0.5rem; margin-bottom:0.4rem; }
.umfrage-option-row input { flex:1; }
.umfrage-option-row button { width:auto; margin:0; padding:0.3rem 0.6rem; font-size:0.8rem; }
.compose-footer { display:flex; justify-content:space-between; align-items:center; margin-top:0.75rem; flex-wrap:wrap; gap:0.5rem; }
.multi-toggle { font-size:0.85rem; display:flex; align-items:center; gap:0.4rem; }
.post-card { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:10px; padding:1rem; margin-bottom:0.9rem; }
.post-header { display:flex; align-items:center; gap:0.7rem; margin-bottom:0.6rem; }
.post-avatar { width:36px; height:36px; border-radius:50%; background:var(--color-secondary); display:flex; align-items:center; justify-content:center; font-size:0.95rem; flex-shrink:0; overflow:hidden; }
.post-avatar img { width:100%; height:100%; object-fit:cover; }
.post-author { font-weight:600; font-size:0.9rem; }
.post-date { font-size:0.75rem; color:var(--color-muted); margin-left:auto; }
.post-text { font-size:0.95rem; line-height:1.5; white-space:pre-wrap; word-break:break-word; }
.post-actions { display:flex; gap:1rem; margin-top:0.75rem; align-items:center; flex-wrap:wrap; }
.post-action-btn { background:none; border:none; color:var(--color-muted); cursor:pointer; font-size:0.85rem; padding:0; display:flex; align-items:center; gap:0.3rem; }
.post-action-btn:hover { color:var(--color-primary); background:none; }
.post-action-btn.active { color:var(--color-primary); }
.post-action-btn.danger:hover { color:#c0392b; }
.post-delete { margin-left:auto; }
/* Umfrage */
.umfrage-option-bar { margin:0.3rem 0; cursor:pointer; border-radius:6px; overflow:hidden; border:1px solid var(--color-secondary); position:relative; transition:border-color 0.15s; }
.umfrage-option-bar:hover { border-color:var(--color-primary); }
.umfrage-option-bar.voted { border-color:var(--color-primary); }
.umfrage-bar-fill { position:absolute; inset:0; background:rgba(var(--color-primary-rgb,180,0,60),0.15); transition:width 0.4s; }
.umfrage-bar-content { position:relative; display:flex; justify-content:space-between; padding:0.45rem 0.75rem; font-size:0.88rem; }
.umfrage-total { font-size:0.78rem; color:var(--color-muted); margin-top:0.3rem; }
/* Kommentare */
.comments-section { margin-top:0.75rem; border-top:1px solid var(--color-secondary); padding-top:0.75rem; }
.comment-compose { display:flex; gap:0.5rem; margin-top:0.5rem; }
.comment-compose input { flex:1; font-size:0.85rem; padding:0.35rem 0.6rem; height:auto; }
.comment-compose button { width:auto; margin:0; padding:0.35rem 0.75rem; font-size:0.8rem; }
/* Members */
.member-list { list-style:none; margin:0; padding:0; }
.member-item { display:flex; align-items:center; gap:0.75rem; padding:0.6rem 0; border-bottom:1px solid var(--color-secondary); }
.member-item:last-child { border-bottom:none; }
.member-avatar { width:38px; height:38px; border-radius:50%; background:var(--color-secondary); display:flex; align-items:center; justify-content:center; font-size:1rem; overflow:hidden; flex-shrink:0; }
.member-avatar img { width:100%; height:100%; object-fit:cover; }
.member-name { flex:1; font-weight:600; }
.role-badge { font-size:0.7rem; font-weight:700; padding:0.1rem 0.4rem; border-radius:4px; background:var(--color-primary); color:#fff; display:inline-block; }
.role-badge.mitglied { background:var(--color-secondary); color:var(--color-text); }
.member-actions { display:flex; gap:0.4rem; }
.member-actions button { margin:0; width:auto; padding:0.25rem 0.6rem; font-size:0.75rem; }
/* Admin */
.admin-section { margin-bottom:1.5rem; }
.admin-section h3 { font-size:1rem; font-weight:600; color:var(--color-primary); border-bottom:1px solid var(--color-secondary); padding-bottom:0.5rem; margin-bottom:0.75rem; }
.request-item { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:8px; padding:0.8rem; margin-bottom:0.6rem; }
.request-item .req-name { font-weight:600; margin-bottom:0.2rem; }
.request-item .req-msg { font-size:0.83rem; color:var(--color-muted); margin-bottom:0.6rem; font-style:italic; }
.request-item .req-actions { display:flex; gap:0.5rem; justify-content:flex-end; }
.request-item .req-actions button { margin:0; width:auto; padding:0.4rem 0.85rem; font-size:0.82rem; font-weight:600; }
.meldung-item { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:8px; padding:0.8rem; margin-bottom:0.6rem; }
.meld-post-preview { background:var(--color-secondary); border-radius:6px; padding:0.6rem 0.75rem; margin-bottom:0.6rem; }
.meld-post-meta { font-size:0.75rem; color:var(--color-muted); margin-bottom:0.3rem; }
.meld-post-text { font-size:0.88rem; white-space:pre-wrap; word-break:break-word; }
.meldung-item .meld-grund { font-size:0.83rem; color:var(--color-muted); margin-bottom:0.6rem; }
.meldung-item .meld-actions { display:flex; gap:0.5rem; justify-content:flex-end; }
.meldung-item .meld-actions button { margin:0; width:auto; padding:0.4rem 0.85rem; font-size:0.82rem; font-weight:600; }
.edit-form label { display:block; font-size:0.85rem; color:var(--color-muted); margin-bottom:0.25rem; margin-top:0.6rem; }
.edit-form input, .edit-form textarea { width:100%; box-sizing:border-box; }
.edit-form textarea { padding:0.6rem 0.85rem; border:1px solid var(--color-secondary); border-radius:6px; background:var(--color-secondary); color:var(--color-text); font-size:0.95rem; outline:none; transition:border-color 0.2s; resize:vertical; min-height:80px; }
.edit-form textarea:focus { border-color:var(--color-primary); }
.edit-form .toggle-row { display:flex; align-items:center; gap:0.75rem; margin-top:0.6rem; }
.edit-form .toggle-row label { margin:0; font-size:0.9rem; color:var(--color-text); }
.img-preview { width:100%; max-height:140px; object-fit:cover; border-radius:6px; margin-top:0.5rem; display:none; }
.empty-hint { color:var(--color-muted); font-size:0.9rem; margin-top:0.5rem; }
/* Post lightbox */
.lightbox { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.88); z-index:300; align-items:center; justify-content:center; }
.lightbox.open { display:flex; }
.lb-layout { display:flex; max-width:min(1340px, calc(100vw - 2rem)); width:95vw; height:min(90vh, 1100px); background:var(--color-card); border-radius:12px; overflow:hidden; position:relative; }
.lb-post-side .post-bild { max-height:1024px; }
.lb-close { position:absolute; top:0.6rem; right:0.6rem; background:rgba(0,0,0,0.55); border:none; color:#fff; font-size:1.1rem; width:2rem; height:2rem; border-radius:50%; cursor:pointer; z-index:10; display:flex; align-items:center; justify-content:center; padding:0; margin:0; }
.lb-post-side { flex:1; overflow-y:auto; padding:1.25rem; border-right:1px solid var(--color-secondary); min-width:0; }
.lb-comments-panel { width:300px; flex-shrink:0; display:flex; flex-direction:column; }
.lb-comments-list { flex:1; overflow-y:auto; padding:0.75rem; }
.lb-comment-compose { padding:0.75rem; border-top:1px solid var(--color-secondary); display:flex; flex-direction:column; gap:0.5rem; flex-shrink:0; }
.lb-comment-compose textarea { width:100%; font-size:0.85rem; padding:0.35rem 0.6rem; resize:none; background:var(--color-secondary); border:1px solid var(--color-secondary); border-radius:6px; color:var(--color-text); font-family:inherit; outline:none; transition:border-color 0.2s; box-sizing:border-box; }
.lb-comment-compose textarea:focus { border-color:var(--color-primary); }
.lb-comment-compose-actions { display:flex; gap:0.5rem; justify-content:flex-end; }
.lb-comment-compose button { width:auto; margin:0; padding:0.35rem 0.75rem; font-size:0.8rem; }
@media (max-width:650px) {
.lb-layout { flex-direction:column; height:95vh; }
.lb-post-side { border-right:none; border-bottom:1px solid var(--color-secondary); max-height:55vh; }
.lb-comments-panel { width:100%; }
}
/* Dialog */
.dialog-backdrop { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:200; align-items:center; justify-content:center; }
.dialog-backdrop.visible { display:flex; }
.dialog { background:var(--color-card); border:1px solid var(--color-secondary); border-radius:12px; padding:2rem; width:100%; max-width:420px; box-shadow:0 12px 40px rgba(0,0,0,0.6); }
.dialog h3 { color:var(--color-primary); font-size:1.1rem; margin-bottom:1.25rem; }
.dialog p { color:var(--color-muted); font-size:0.88rem; margin-bottom:0; }
.dialog-actions { display:flex; justify-content:flex-end; gap:0.75rem; margin-top:1.5rem; }
.dialog-actions button { flex:none; margin:0; padding:0.55rem 1.1rem; font-size:0.9rem; width:auto; }
</style>
</head>
<body class="app">
<div class="main">
<div class="content">
<!-- Group header -->
<div class="gruppe-header">
<div class="gruppe-avatar" id="gruppeAvatar">👥</div>
<div class="gruppe-header-info">
<h2 id="gruppeName">Wird geladen…</h2>
<p id="gruppeMeta"></p>
</div>
<div class="gruppe-header-actions" id="headerActions"></div>
</div>
<div class="tabs" id="tabBar">
<button class="tab-btn active" data-tab="posts" onclick="switchTab('posts', this)">Beiträge</button>
<button class="tab-btn" data-tab="members" onclick="switchTab('members', this)">Mitglieder</button>
<button class="tab-btn" data-tab="admin" id="adminTabBtn" style="display:none;" onclick="switchTab('admin', this)">Admin <span class="social-badge" id="adminBadge" style="display:none;"></span></button>
</div>
<!-- Posts Tab -->
<div class="tab-panel active" id="tab-posts">
<!-- Compose -->
<div class="post-compose" id="compose" style="display:none;">
<div class="compose-type">
<label><input type="radio" name="beitragTyp" value="TEXT" checked onchange="toggleUmfrage()"> Text</label>
<label><input type="radio" name="beitragTyp" value="UMFRAGE" onchange="toggleUmfrage()"> Umfrage</label>
</div>
<textarea id="composeText" placeholder="Was möchtest du teilen?" rows="3"></textarea>
<div class="compose-thumbs" id="composeThumbs"></div>
<div class="umfrage-options" id="umfrageOptions" style="display:none;">
<div id="optionList"></div>
<button onclick="addOption()" style="width:auto; margin:0; padding:0.3rem 0.75rem; font-size:0.8rem; margin-top:0.4rem;">+ Option</button>
</div>
<div class="compose-footer">
<label class="multi-toggle" id="multiChoiceRow" style="display:none;">
<input type="checkbox" id="multiChoice"> Multi-Choice
</label>
<div style="display:flex;gap:0.5rem;align-items:center;">
<button type="button" class="compose-action-btn" onclick="toggleEmojiPicker(this,'composeText')" title="Emoji einfügen">😊</button>
<label class="compose-action-btn" title="Fotos hinzufügen">📷
<input type="file" id="composeBildFile" accept="image/*" multiple style="display:none;" onchange="selectComposeBilder(this)">
</label>
<button onclick="submitPost()" style="width:auto; margin:0;">Veröffentlichen</button>
</div>
</div>
</div>
<div id="postsFeed"></div>
<p class="empty-hint" id="postsEmpty" style="display:none;">Noch keine Beiträge. Schreib den ersten!</p>
<div style="text-align:center; margin-top:0.75rem;">
<button id="loadMoreBtn" onclick="loadMorePosts()" style="display:none; width:auto; padding:0.4rem 1.25rem; background:var(--color-secondary); color:var(--color-text);">Mehr laden</button>
</div>
</div>
<!-- Members Tab -->
<div class="tab-panel" id="tab-members">
<ul class="member-list" id="memberList"></ul>
</div>
<!-- Admin Tab -->
<div class="tab-panel" id="tab-admin">
<div class="admin-section">
<h3>Gruppe bearbeiten</h3>
<div class="edit-form">
<label>Name</label>
<input type="text" id="editName" maxlength="100">
<label>Beschreibung</label>
<textarea id="editDesc" maxlength="1000"></textarea>
<label>Bild</label>
<input type="file" id="editBildFile" accept="image/*" onchange="previewBild(this,'editBildPreview','editBildData')">
<img id="editBildPreview" class="img-preview" alt="">
<input type="hidden" id="editBildData">
<div class="toggle-row">
<input type="checkbox" id="editPrivate">
<label for="editPrivate">Private Gruppe</label>
</div>
<button onclick="saveGruppe()" style="margin-top:0.75rem; width:auto; padding:0.4rem 1rem;">Speichern</button>
<p class="message" id="saveMsg" style="display:none; margin-top:0.5rem;"></p>
</div>
</div>
<div class="admin-section">
<h3>Beitrittsanfragen</h3>
<div id="requestsSection">
<p class="empty-hint" id="reqEmpty">Keine ausstehenden Anfragen.</p>
</div>
</div>
<div class="admin-section">
<h3>Gemeldete Beiträge</h3>
<div id="reportsSection">
<p class="empty-hint" id="repEmpty">Keine Meldungen.</p>
</div>
</div>
<div class="admin-section">
<h3>Gruppe löschen</h3>
<button onclick="openDeleteDialog()" style="background:#c0392b; width:auto; padding:0.4rem 1rem;">Gruppe löschen</button>
</div>
</div>
</div>
</div>
<!-- Leave confirmation -->
<div class="dialog-backdrop" id="leaveDialog">
<div class="dialog">
<h3>Gruppe verlassen</h3>
<p>Möchtest du diese Gruppe wirklich verlassen?</p>
<div class="dialog-actions">
<button class="secondary" onclick="document.getElementById('leaveDialog').classList.remove('visible')">Abbrechen</button>
<button onclick="confirmLeave()" style="background:#c0392b;">Verlassen</button>
</div>
</div>
</div>
<!-- Delete confirmation -->
<div class="dialog-backdrop" id="deleteDialog">
<div class="dialog">
<h3>Gruppe löschen</h3>
<p>Diese Aktion kann nicht rückgängig gemacht werden. Alle Beiträge und Mitgliedschaften werden gelöscht.</p>
<div class="dialog-actions">
<button class="secondary" onclick="document.getElementById('deleteDialog').classList.remove('visible')">Abbrechen</button>
<button onclick="deleteGruppe()" style="background:#c0392b;">Löschen</button>
</div>
</div>
</div>
<!-- Generic confirm/info modal -->
<div class="dialog-backdrop" id="genericModal">
<div class="dialog" style="text-align:center;">
<h3 id="gModalTitle"></h3>
<p id="gModalText" style="margin-bottom:0;"></p>
<div class="dialog-actions" id="gModalActions" style="justify-content:center; margin-top:1.5rem;"></div>
</div>
</div>
<!-- Post lightbox dialog -->
<div class="lightbox" id="postDialog">
<div class="lb-layout">
<button class="lb-close" onclick="closePostDialog()"></button>
<div class="lb-post-side" id="lbPostContent"></div>
<div class="lb-comments-panel">
<div class="lb-comments-list" id="lbCommentsList"></div>
<div class="lb-comment-compose">
<textarea id="lbCommentInput" placeholder="Kommentieren…" maxlength="500" rows="3"
onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();submitLbComment()}"></textarea>
<div class="lb-comment-compose-actions">
<button type="button" class="compose-action-btn" onclick="toggleEmojiPicker(this,'lbCommentInput')" title="Emoji">😊</button>
<button onclick="submitLbComment()">Senden</button>
</div>
</div>
</div>
</div>
</div>
<script src="/js/shared.js"></script>
<script src="/js/icons.js"></script>
<script src="/js/nav.js"></script>
<script src="/js/social-sidebar.js"></script>
<script src="/js/hashtag.js"></script>
<script>
// ── Generic modal helpers ──
function showModal(title, text, actions) {
document.getElementById('gModalTitle').textContent = title;
const textEl = document.getElementById('gModalText');
textEl.textContent = text;
textEl.style.display = text ? '' : 'none';
const actEl = document.getElementById('gModalActions');
actEl.innerHTML = '';
actions.forEach(a => {
const btn = document.createElement('button');
btn.textContent = a.label;
if (a.secondary) btn.className = 'secondary';
if (a.danger) btn.style.background = '#c0392b';
btn.style.cssText += ';margin:0;width:auto;';
btn.onclick = () => { closeModal(); if (a.onClick) a.onClick(); };
actEl.appendChild(btn);
});
document.getElementById('genericModal').classList.add('visible');
}
function closeModal() {
document.getElementById('genericModal').classList.remove('visible');
}
function showSaveMsg(text, type) {
const el = document.getElementById('saveMsg');
el.textContent = text;
el.className = 'message ' + type;
el.style.display = 'block';
setTimeout(() => { el.style.display = 'none'; }, 3000);
}
document.getElementById('genericModal').addEventListener('click', e => {
if (e.target === document.getElementById('genericModal')) closeModal();
});
// ──────────────────────────────────────────────────────────────────────
const params = new URLSearchParams(location.search);
const gruppeId = params.get('gruppeId');
if (!gruppeId) location.href = '/community/gruppen.html';
let myId = null;
let myRole = null;
let gruppeData = null;
let allPosts = [];
let currentPage = 0;
const PAGE_SIZE = 10;
// esc, fmtDate kommen aus shared.js
function switchTab(name, btn) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
btn.classList.add('active');
document.getElementById('tab-' + name).classList.add('active');
localStorage.setItem('tab_gruppe_' + gruppeId, name);
if (name === 'members') loadMembers();
if (name === 'admin') { loadAdminRequests(); loadReports(); }
}
function avatarHtml(pic, size) {
return pic
? `<img src="data:image/png;base64,${pic}" alt="">`
: '◉';
}
async function init() {
// Get my ID
const meRes = await fetch('/login/me');
if (!meRes.ok) { location.href='/login.html'; return; }
const me = await meRes.json();
myId = me.userId;
await loadGruppe();
await loadPosts();
const _composeText = document.getElementById('composeText');
if (_composeText) attachHashtagAutocomplete(_composeText);
const _savedTab = localStorage.getItem('tab_gruppe_' + gruppeId);
if (_savedTab) {
const _btn = document.querySelector(`.tab-btn[data-tab="${_savedTab}"]`);
if (_btn && _btn.style.display !== 'none') switchTab(_savedTab, _btn);
}
}
async function loadGruppe() {
const res = await fetch('/gruppen/' + gruppeId);
if (!res.ok) { document.getElementById('gruppeName').textContent = 'Nicht gefunden'; return; }
gruppeData = await res.json();
myRole = gruppeData.myRole;
document.title = gruppeData.name + ' xXx Sphere';
document.getElementById('gruppeName').textContent = gruppeData.name;
document.getElementById('gruppeMeta').textContent =
gruppeData.memberCount + ' Mitglied' + (gruppeData.memberCount !== 1 ? 'er' : '') +
(gruppeData.isPrivate ? ' · 🔒 Privat' : '');
const av = document.getElementById('gruppeAvatar');
if (gruppeData.bild) av.innerHTML = `<img src="data:image/jpeg;base64,${gruppeData.bild}" alt="">`;
// Header actions
const ha = document.getElementById('headerActions');
if (myRole) {
ha.innerHTML = `<button class="secondary" onclick="leaveGruppe()">Verlassen</button>`;
} else if (gruppeData.myRequestStatus === 'AUSSTEHEND') {
ha.innerHTML = `<button disabled style="opacity:0.6;">Anfrage ausstehend</button>`;
} else {
ha.innerHTML = `<a href="/community/gruppen.html" class="btn secondary">← Zurück</a>`;
}
// Admin tab
if (myRole === 'ADMIN') {
document.getElementById('adminTabBtn').style.display = '';
// Pre-fill edit form
document.getElementById('editName').value = gruppeData.name;
document.getElementById('editDesc').value = gruppeData.beschreibung || '';
document.getElementById('editPrivate').checked = gruppeData.isPrivate;
if (gruppeData.bild) {
const prev = document.getElementById('editBildPreview');
prev.src = 'data:image/jpeg;base64,' + gruppeData.bild;
prev.style.display = 'block';
document.getElementById('editBildData').value = gruppeData.bild;
}
}
// Show compose if member
if (myRole) document.getElementById('compose').style.display = '';
// Badges für ausstehende Anfragen + Meldungen (nur Admin)
if (myRole === 'ADMIN') {
Promise.all([
fetch('/gruppen/' + gruppeId + '/requests').then(r => r.ok ? r.json() : []).catch(() => []),
fetch('/gruppen/' + gruppeId + '/reports').then(r => r.ok ? r.json() : []).catch(() => [])
]).then(([reqs, reps]) => { adminJoinsCount = reqs.length; adminReportsCount = reps.length; setAdminBadge(); });
}
}
async function loadPosts() {
if (!myRole) { document.getElementById('postsEmpty').textContent = 'Tritt der Gruppe bei, um Beiträge zu sehen.'; document.getElementById('postsEmpty').style.display = ''; return; }
currentPage = 0;
allPosts = [];
document.getElementById('postsFeed').innerHTML = '';
await fetchPage(0);
}
async function loadMorePosts() {
await fetchPage(currentPage + 1);
}
async function fetchPage(page) {
const res = await fetch('/gruppen/' + gruppeId + '/posts?page=' + page + '&size=' + PAGE_SIZE);
if (!res.ok) return;
const data = await res.json();
if (page === 0 && data.posts.length === 0) {
document.getElementById('postsEmpty').style.display = '';
} else {
document.getElementById('postsEmpty').style.display = 'none';
}
data.posts.forEach(p => {
allPosts.push(p);
document.getElementById('postsFeed').insertAdjacentHTML('beforeend', renderPostCard(p));
});
currentPage = page;
const btn = document.getElementById('loadMoreBtn');
btn.style.display = data.hasMore ? '' : 'none';
}
function renderPostCard(p) {
const canDelete = (myRole === 'ADMIN' || p.authorId === myId);
const av = p.authorPicture ? `<img src="data:image/png;base64,${p.authorPicture}" alt="">` : '◉';
const bildHtml = bilderCarousel(p.bilder);
let body = '';
if (p.beitragTyp === 'UMFRAGE') {
const total = p.optionen.reduce((s, o) => s + o.stimmenCount, 0);
const bars = p.optionen.map(o => {
const pct = total > 0 ? Math.round(o.stimmenCount / total * 100) : 0;
const voted = p.myVoteOptionIds.includes(o.optionId);
return `<div class="umfrage-option-bar ${voted?'voted':''}" onclick="event.stopPropagation(); vote('${p.beitragId}','${o.optionId}',this)">
<div class="umfrage-bar-fill" style="width:${pct}%"></div>
<div class="umfrage-bar-content">
<span>${esc(o.text)}</span>
<span>${pct}% (${o.stimmenCount})</span>
</div>
</div>`;
}).join('');
body = bars + `<div class="umfrage-total">${total} Stimme${total !== 1 ? 'n' : ''} gesamt${p.multiChoice?' · Multi-Choice':''}</div>`;
} else {
body = `<div class="post-text">${renderTextWithHashtags(p.text)}</div>${bildHtml}`;
}
return `
<div class="post-card" id="post-${p.beitragId}" onclick="openPostDialog('${p.beitragId}')" style="cursor:pointer;">
<div class="post-header">
<div class="post-avatar">${av}</div>
<div>
<div class="post-author"><a href="/community/benutzer.html?userId=${p.authorId}" style="color:inherit;text-decoration:none;" onclick="event.stopPropagation()">${esc(p.authorName)}</a></div>
</div>
<div class="post-date">${fmtDate(p.createdAt)}</div>
</div>
${p.beitragTyp === 'UMFRAGE' ? `<div style="font-weight:600;margin-bottom:0.5rem;">${renderTextWithHashtags(p.text)}</div>${bildHtml}` : ''}
${body}
<div class="post-actions">
<button class="post-action-btn ${p.likedByMe?'active':''}" onclick="event.stopPropagation(); toggleLike('${p.beitragId}',this)" id="like-btn-${p.beitragId}">
♥ <span id="like-count-${p.beitragId}">${p.likeCount}</span>
</button>
<button class="post-action-btn" onclick="event.stopPropagation(); openPostDialog('${p.beitragId}')">
💬 <span id="kmt-count-${p.beitragId}">${p.kommentarCount}</span>
</button>
${!p.reported ? `<button class="post-action-btn" onclick="event.stopPropagation(); reportPost('${p.beitragId}',this)">⚑ Melden</button>` : '<span style="font-size:0.78rem;color:var(--color-muted);">Gemeldet</span>'}
${canDelete ? `<button class="post-action-btn danger post-delete" onclick="event.stopPropagation(); deletePost('${p.beitragId}',this)">✕</button>` : ''}
</div>
</div>`;
}
async function toggleLike(postId, btn) {
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId + '/like', { method:'POST' });
if (!res.ok) return;
const countEl = document.getElementById('like-count-' + postId);
const isActive = btn.classList.contains('active');
btn.classList.toggle('active');
countEl.textContent = parseInt(countEl.textContent) + (isActive ? -1 : 1);
}
async function vote(postId, optionId, barEl) {
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId + '/vote', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ optionId })
});
if (!res.ok) return;
// Reload posts to update vote counts
await loadPosts();
}
async function reportPost(postId, btn) {
btn.disabled = true;
const grund = prompt('Grund der Meldung (optional):');
if (grund === null) { btn.disabled = false; return; }
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId + '/report', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ grund })
});
if (res.ok || res.status === 201) {
btn.textContent = 'Gemeldet';
btn.disabled = true;
} else {
btn.disabled = false;
}
}
async function deletePost(postId, btn) {
showModal('Beitrag löschen', 'Beitrag wirklich löschen?', [
{ label: 'Abbrechen', secondary: true },
{ label: 'Löschen', danger: true, onClick: async () => {
btn.disabled = true;
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId, { method:'DELETE' });
if (res.ok || res.status === 204) {
document.getElementById('post-' + postId)?.remove();
allPosts = allPosts.filter(p => p.beitragId !== postId);
if (allPosts.length === 0) { document.getElementById('postsEmpty').style.display = ''; document.getElementById('loadMoreBtn').style.display = 'none'; }
} else { btn.disabled = false; }
}}
]);
}
// ── Compose image ──
let composeBilderArr = [];
function selectComposeBilder(input) {
[...input.files].forEach(f => { if (f.type.startsWith('image/')) processComposeImage(f); });
input.value = '';
}
function processComposeImage(file) {
const reader = new FileReader();
reader.onload = e => {
const img = new Image();
img.onload = () => {
const MAX = 1024;
const canvas = document.createElement('canvas');
const scale = Math.min(MAX / img.width, MAX / img.height, 1);
canvas.width = Math.round(img.width * scale);
canvas.height = Math.round(img.height * scale);
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
composeBilderArr.push(canvas.toDataURL('image/jpeg', 0.88).split(',')[1]);
renderComposeThumbs();
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
function renderComposeThumbs() {
const container = document.getElementById('composeThumbs');
container.innerHTML = '';
composeBilderArr.forEach((b, i) => {
const div = document.createElement('div');
div.className = 'compose-thumb';
div.innerHTML = `<img src="data:image/jpeg;base64,${b}" alt="">
<button class="compose-thumb-remove" onclick="removeThumb(${i})" title="Entfernen">✕</button>`;
container.appendChild(div);
});
container.style.display = composeBilderArr.length > 0 ? 'flex' : 'none';
}
function removeThumb(idx) {
composeBilderArr.splice(idx, 1);
renderComposeThumbs();
}
// ── Compose ──
function toggleUmfrage() {
const isUmfrage = document.querySelector('input[name="beitragTyp"]:checked')?.value === 'UMFRAGE';
document.getElementById('umfrageOptions').style.display = isUmfrage ? '' : 'none';
document.getElementById('multiChoiceRow').style.display = isUmfrage ? '' : 'none';
const placeholder = document.getElementById('composeText');
placeholder.placeholder = isUmfrage ? 'Frage eingeben…' : 'Was möchtest du teilen?';
if (isUmfrage && document.getElementById('optionList').children.length === 0) {
addOption(); addOption();
}
}
function addOption() {
const list = document.getElementById('optionList');
const idx = list.children.length;
const div = document.createElement('div');
div.className = 'umfrage-option-row';
div.innerHTML = `<input type="text" placeholder="Option ${idx+1}" maxlength="200"><button onclick="this.parentElement.remove()">✕</button>`;
list.appendChild(div);
}
async function submitPost() {
const text = document.getElementById('composeText').value.trim();
if (!text) return;
const beitragTyp = document.querySelector('input[name="beitragTyp"]:checked')?.value || 'TEXT';
let optionen = null;
let multiChoice = null;
if (beitragTyp === 'UMFRAGE') {
optionen = Array.from(document.querySelectorAll('#optionList input')).map(i => i.value.trim()).filter(v => v);
if (optionen.length < 2) { showModal('Hinweis', 'Bitte mindestens 2 Optionen eingeben.', [{ label: 'OK' }]); return; }
multiChoice = document.getElementById('multiChoice').checked;
}
const bilder = [...composeBilderArr];
const res = await fetch('/gruppen/' + gruppeId + '/posts', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ beitragTyp, text, multiChoice, optionen, bilder })
});
if (res.ok || res.status === 201) {
document.getElementById('composeText').value = '';
document.getElementById('optionList').innerHTML = '';
document.querySelector('input[name="beitragTyp"][value="TEXT"]').checked = true;
toggleUmfrage();
composeBilderArr = [];
renderComposeThumbs();
await loadPosts();
}
}
// ── Members ──
async function loadMembers() {
const res = await fetch('/gruppen/' + gruppeId + '/members');
if (!res.ok) return;
const members = await res.json();
const list = document.getElementById('memberList');
list.innerHTML = '';
members.forEach(m => {
const av = m.userPicture ? `<img src="data:image/png;base64,${m.userPicture}" alt="">` : '◉';
const roleBadge = m.rolle === 'ADMIN'
? '<span class="role-badge">Admin</span>'
: '<span class="role-badge mitglied">Mitglied</span>';
let actions = '';
if (myRole === 'ADMIN' && m.userId !== myId) {
actions = `<div class="member-actions">
<button onclick="promoteMember('${m.userId}',this)">Zum Admin</button>
<button onclick="removeMember('${m.userId}',this)" style="background:#c0392b;">Entfernen</button>
</div>`;
}
list.insertAdjacentHTML('beforeend', `
<li class="member-item" id="member-${m.userId}">
<div class="member-avatar">${av}</div>
<a href="/community/benutzer.html?userId=${m.userId}" class="member-name" style="text-decoration:none;color:inherit;">${esc(m.userName)}</a>
${roleBadge}
${actions}
</li>`);
});
}
async function removeMember(userId, btn) {
showModal('Mitglied entfernen', 'Dieses Mitglied wirklich aus der Gruppe entfernen?', [
{ label: 'Abbrechen', secondary: true },
{ label: 'Entfernen', danger: true, onClick: async () => {
btn.disabled = true;
const res = await fetch('/gruppen/' + gruppeId + '/members/' + userId, { method:'DELETE' });
if (res.ok || res.status === 204) {
document.getElementById('member-' + userId)?.remove();
} else { btn.disabled = false; }
}}
]);
}
async function promoteMember(userId, btn) {
showModal('Mitglied befördern', 'Dieses Mitglied zum Admin befördern?', [
{ label: 'Abbrechen', secondary: true },
{ label: 'Befördern', onClick: async () => {
btn.disabled = true;
const res = await fetch('/gruppen/' + gruppeId + '/members/' + userId + '/promote', { method:'POST' });
if (res.ok) {
await loadMembers();
} else { btn.disabled = false; }
}}
]);
}
// ── Leave / Join ──
function leaveGruppe() {
document.getElementById('leaveDialog').classList.add('visible');
}
async function confirmLeave() {
const res = await fetch('/gruppen/' + gruppeId + '/leave', { method:'DELETE' });
if (res.ok || res.status === 204) {
location.href = '/community/gruppen.html';
}
}
// ── Admin ──
let adminJoinsCount = 0;
let adminReportsCount = 0;
function setAdminBadge() {
const total = adminJoinsCount + adminReportsCount;
const badge = document.getElementById('adminBadge');
if (!badge) return;
badge.textContent = total;
badge.style.display = total > 0 ? '' : 'none';
}
async function loadAdminRequests() {
const sec = document.getElementById('requestsSection');
const res = await fetch('/gruppen/' + gruppeId + '/requests');
if (!res.ok) return;
const data = await res.json();
adminJoinsCount = data.length;
setAdminBadge();
sec.innerHTML = '';
if (data.length === 0) { sec.innerHTML = '<p class="empty-hint">Keine ausstehenden Anfragen.</p>'; return; }
data.forEach(r => {
const av = r.userPicture ? `<img src="data:image/png;base64,${r.userPicture}" alt="">` : '◉';
sec.insertAdjacentHTML('beforeend', `
<div class="request-item" id="req-${r.anfrageId}">
<div style="display:flex;align-items:center;gap:0.6rem;margin-bottom:0.4rem;">
<div class="comment-avatar">${av}</div>
<span class="req-name">${esc(r.userName)}</span>
</div>
${r.nachricht ? `<div class="req-msg">"${esc(r.nachricht)}"</div>` : ''}
<div class="req-actions">
<button onclick="rejectRequest('${r.anfrageId}',this)" class="secondary">✕ Ablehnen</button>
<button onclick="approveRequest('${r.anfrageId}',this)">✓ Genehmigen</button>
</div>
</div>`);
});
}
async function approveRequest(reqId, btn) {
btn.disabled = true;
const res = await fetch('/gruppen/' + gruppeId + '/requests/' + reqId + '/approve', { method:'POST' });
if (res.ok) {
document.getElementById('req-' + reqId)?.remove();
adminJoinsCount = Math.max(0, adminJoinsCount - 1);
setAdminBadge();
} else { btn.disabled = false; }
}
async function rejectRequest(reqId, btn) {
btn.disabled = true;
const res = await fetch('/gruppen/' + gruppeId + '/requests/' + reqId, { method:'DELETE' });
if (res.ok || res.status === 204) {
document.getElementById('req-' + reqId)?.remove();
adminJoinsCount = Math.max(0, adminJoinsCount - 1);
setAdminBadge();
} else { btn.disabled = false; }
}
async function loadReports() {
const sec = document.getElementById('reportsSection');
const res = await fetch('/gruppen/' + gruppeId + '/reports');
if (!res.ok) return;
const data = await res.json();
adminReportsCount = data.length;
setAdminBadge();
sec.innerHTML = '';
if (data.length === 0) { sec.innerHTML = '<p class="empty-hint">Keine Meldungen.</p>'; return; }
// Fetch post data for each report (use cache when available)
const postPromises = data.map(m => {
const cached = allPosts.find(p => p.beitragId === m.beitragId);
if (cached) return Promise.resolve(cached);
return fetch('/gruppen/' + gruppeId + '/posts/' + m.beitragId)
.then(r => r.ok ? r.json() : null).catch(() => null);
});
const posts = await Promise.all(postPromises);
data.forEach((m, i) => {
const post = posts[i];
let postHtml;
if (post) {
const typeLabel = post.beitragTyp === 'UMFRAGE' ? '📊 Umfrage' : '✏ Text';
const firstBild = post.bilder && post.bilder.length > 0 ? post.bilder[0] : null;
const bildHtml = firstBild
? `<img src="data:image/jpeg;base64,${firstBild}" style="max-height:100px;max-width:100%;border-radius:4px;margin-top:0.4rem;object-fit:contain;display:block;">`
: '';
postHtml = `<div class="meld-post-preview">
<div class="meld-post-meta">${typeLabel} · ${esc(post.authorName)} · ${fmtDate(post.createdAt)}</div>
<div class="meld-post-text">${esc(post.text.substring(0, 250))}${post.text.length > 250 ? '…' : ''}</div>
${bildHtml}
</div>`;
} else {
postHtml = `<div class="meld-post-preview"><div class="meld-post-meta" style="font-style:italic;">Beitrag nicht mehr verfügbar</div></div>`;
}
sec.insertAdjacentHTML('beforeend', `
<div class="meldung-item" id="meld-${m.meldungId}">
${postHtml}
<div class="meld-grund">Gemeldet von <b>${esc(m.melderName)}</b>${m.grund ? ': ' + esc(m.grund) : ''} · ${fmtDate(m.gemeldetAt)}</div>
<div class="meld-actions">
<button onclick="dismissReport('${m.meldungId}',this)" class="secondary">Meldung verwerfen</button>
${post ? `<button onclick="deletePostAdmin('${m.meldungId}','${post.beitragId}')" style="background:var(--color-primary);">Post löschen</button>` : ''}
</div>
</div>`);
});
}
async function dismissReport(meldungId, btn) {
btn.disabled = true;
const res = await fetch('/gruppen/' + gruppeId + '/reports/' + meldungId, { method:'DELETE' });
if (res.ok || res.status === 204) {
document.getElementById('meld-' + meldungId)?.remove();
adminReportsCount = Math.max(0, adminReportsCount - 1);
setAdminBadge();
} else { btn.disabled = false; }
}
async function deletePostAdmin(meldungId, postId) {
showModal('Beitrag löschen', 'Diesen Beitrag wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.', [
{ label: 'Abbrechen', secondary: true },
{ label: 'Löschen', danger: true, onClick: () => doDeletePostAdmin(meldungId, postId) }
]);
}
async function doDeletePostAdmin(meldungId, postId) {
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId, { method:'DELETE' });
if (res.ok || res.status === 204) {
document.getElementById('post-' + postId)?.remove();
allPosts = allPosts.filter(p => p.beitragId !== postId);
if (allPosts.length === 0) { document.getElementById('postsEmpty').style.display = ''; document.getElementById('loadMoreBtn').style.display = 'none'; }
await loadReports(); // refresh list (cascade deletes all reports for this post)
}
}
async function saveGruppe() {
const body = {
name: document.getElementById('editName').value.trim() || null,
beschreibung: document.getElementById('editDesc').value.trim() || null,
bild: document.getElementById('editBildData').value || null,
isPrivate: document.getElementById('editPrivate').checked
};
const res = await fetch('/gruppen/' + gruppeId, {
method: 'PUT',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(body)
});
if (res.ok) {
showSaveMsg('Gespeichert.', 'success');
await loadGruppe();
} else {
showSaveMsg('Fehler beim Speichern.', 'error');
}
}
function openDeleteDialog() {
document.getElementById('deleteDialog').classList.add('visible');
}
async function deleteGruppe() {
const res = await fetch('/gruppen/' + gruppeId, { method:'DELETE' });
if (res.ok || res.status === 204) {
location.href = '/community/gruppen.html';
} else {
showModal('Fehler', 'Fehler beim Löschen der Gruppe.', [{ label: 'OK' }]);
}
}
function previewBild(input, previewId, dataId) {
const file = input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
const original = new Image();
original.onload = () => {
const MAX = 256;
let w = original.width, h = original.height;
if (w > MAX || h > MAX) {
if (w > h) { h = Math.round(h * MAX / w); w = MAX; }
else { w = Math.round(w * MAX / h); h = MAX; }
}
const canvas = document.createElement('canvas');
canvas.width = w; canvas.height = h;
canvas.getContext('2d').drawImage(original, 0, 0, w, h);
const scaled = canvas.toDataURL('image/jpeg', 0.85);
const img = document.getElementById(previewId);
img.src = scaled;
img.style.display = 'block';
document.getElementById(dataId).value = scaled.split(',')[1];
};
original.src = e.target.result;
};
reader.readAsDataURL(file);
}
document.getElementById('deleteDialog').addEventListener('click', e => {
if (e.target === document.getElementById('deleteDialog'))
document.getElementById('deleteDialog').classList.remove('visible');
});
// ── Post dialog ──
let lbPostId = null;
async function openPostDialog(postId) {
lbPostId = postId;
const post = allPosts.find(p => p.beitragId === postId);
if (!post) return;
renderLbPost(post);
document.getElementById('postDialog').classList.add('open');
await loadLbComments();
document.getElementById('lbCommentInput').focus();
}
function closePostDialog() {
document.getElementById('postDialog').classList.remove('open');
lbPostId = null;
}
function renderLbPost(p) {
const canDelete = (myRole === 'ADMIN' || p.authorId === myId);
const av = p.authorPicture ? `<img src="data:image/png;base64,${p.authorPicture}" alt="">` : '◉';
const bildHtml = bilderCarousel(p.bilder);
let body = '';
if (p.beitragTyp === 'UMFRAGE') {
const total = p.optionen.reduce((s, o) => s + o.stimmenCount, 0);
const bars = p.optionen.map(o => {
const pct = total > 0 ? Math.round(o.stimmenCount / total * 100) : 0;
const voted = p.myVoteOptionIds.includes(o.optionId);
return `<div class="umfrage-option-bar ${voted?'voted':''}" onclick="voteLb('${p.beitragId}','${o.optionId}')">
<div class="umfrage-bar-fill" style="width:${pct}%"></div>
<div class="umfrage-bar-content">
<span>${esc(o.text)}</span>
<span>${pct}% (${o.stimmenCount})</span>
</div>
</div>`;
}).join('');
body = `<div style="font-weight:600;margin-bottom:0.5rem;">${esc(p.text)}</div>${bildHtml}${bars}<div class="umfrage-total">${total} Stimme${total !== 1 ? 'n' : ''} gesamt${p.multiChoice?' · Multi-Choice':''}</div>`;
} else {
body = `<div class="post-text">${esc(p.text)}</div>${bildHtml}`;
}
document.getElementById('lbPostContent').innerHTML = `
<div class="post-header">
<div class="post-avatar">${av}</div>
<div>
<div class="post-author"><a href="/community/benutzer.html?userId=${p.authorId}" style="color:inherit;text-decoration:none;">${esc(p.authorName)}</a></div>
<div class="post-date">${fmtDate(p.createdAt)}</div>
</div>
</div>
${body}
<div class="post-actions" style="margin-top:0.75rem;">
<button class="post-action-btn ${p.likedByMe?'active':''}" onclick="toggleLikeLb('${p.beitragId}',this)">
♥ <span id="lb-like-count-${p.beitragId}">${p.likeCount}</span>
</button>
${!p.reported ? `<button class="post-action-btn" onclick="reportPostLb('${p.beitragId}',this)">⚑ Melden</button>` : '<span style="font-size:0.78rem;color:var(--color-muted);">Gemeldet</span>'}
${canDelete ? `<button class="post-action-btn danger" onclick="deletePostLb('${p.beitragId}')">✕ Löschen</button>` : ''}
</div>`;
}
async function toggleLikeLb(postId, btn) {
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId + '/like', { method:'POST' });
if (!res.ok) return;
const isActive = btn.classList.contains('active');
btn.classList.toggle('active');
const countEl = document.getElementById('lb-like-count-' + postId);
const newCount = parseInt(countEl.textContent) + (isActive ? -1 : 1);
countEl.textContent = newCount;
const feedBtn = document.getElementById('like-btn-' + postId);
const feedCount = document.getElementById('like-count-' + postId);
if (feedBtn) feedBtn.classList.toggle('active', !isActive);
if (feedCount) feedCount.textContent = newCount;
const post = allPosts.find(p => p.beitragId === postId);
if (post) { post.likeCount = newCount; post.likedByMe = !isActive; }
}
async function deleteKommentar(kommentarId) {
await fetch('/social/kommentare/' + kommentarId, { method: 'DELETE' });
await loadLbComments();
}
async function loadLbComments() {
if (!lbPostId) return;
const list = document.getElementById('lbCommentsList');
list.innerHTML = '';
const res = await fetch('/social/kommentare?targetType=GROUP_POST&targetId=' + lbPostId);
if (!res.ok) return;
const kmts = await res.json();
kmts.forEach(k => list.insertAdjacentHTML('beforeend', renderKommentarHtml(k, 'GROUP_POST', lbPostId, { myUserId: myId })));
list.scrollTop = list.scrollHeight;
}
async function submitLbComment() {
if (!lbPostId) return;
const input = document.getElementById('lbCommentInput');
const text = input.value.trim();
if (!text) return;
const res = await fetch('/social/kommentare', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ targetType: 'GROUP_POST', targetId: lbPostId, text })
});
if (res.ok || res.status === 201) {
input.value = '';
await loadLbComments();
const countEl = document.getElementById('kmt-count-' + lbPostId);
if (countEl) countEl.textContent = parseInt(countEl.textContent) + 1;
const post = allPosts.find(p => p.beitragId === lbPostId);
if (post) post.kommentarCount++;
}
}
async function reportPostLb(postId, btn) {
btn.disabled = true;
const grund = prompt('Grund der Meldung (optional):');
if (grund === null) { btn.disabled = false; return; }
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId + '/report', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ grund })
});
if (res.ok || res.status === 201) {
btn.textContent = 'Gemeldet'; btn.disabled = true;
const post = allPosts.find(p => p.beitragId === postId);
if (post) post.reported = true;
} else { btn.disabled = false; }
}
async function deletePostLb(postId) {
showModal('Beitrag löschen', 'Beitrag wirklich löschen?', [
{ label: 'Abbrechen', secondary: true },
{ label: 'Löschen', danger: true, onClick: async () => {
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId, { method:'DELETE' });
if (res.ok || res.status === 204) {
document.getElementById('post-' + postId)?.remove();
allPosts = allPosts.filter(p => p.beitragId !== postId);
closePostDialog();
if (allPosts.length === 0) { document.getElementById('postsEmpty').style.display = ''; document.getElementById('loadMoreBtn').style.display = 'none'; }
}
}}
]);
}
async function voteLb(postId, optionId) {
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId + '/vote', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ optionId })
});
if (!res.ok) return;
await loadPosts();
const post = allPosts.find(p => p.beitragId === postId);
if (post) renderLbPost(post);
}
document.getElementById('postDialog').addEventListener('click', e => {
if (e.target === document.getElementById('postDialog')) closePostDialog();
});
document.addEventListener('keydown', e => { if (e.key === 'Escape') closePostDialog(); });
init();
</script>
</body>
</html>