Locations können nun auch Posten - bugfixes im Feed
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
This commit is contained in:
@@ -7,14 +7,8 @@
|
||||
<title>Gruppe – xXx Sphere</title>
|
||||
<link rel="stylesheet" href="/css/variables.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/community.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; }
|
||||
@@ -24,48 +18,9 @@
|
||||
.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-Typ (Gruppe-spezifisch) */
|
||||
.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; }
|
||||
@@ -111,26 +66,6 @@
|
||||
|
||||
.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; }
|
||||
@@ -164,25 +99,24 @@
|
||||
<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 style="display:flex;justify-content:space-between;align-items:center;margin-top:0.5rem;">
|
||||
<button onclick="addOption()" style="width:auto; margin:0; padding:0.3rem 0.75rem; font-size:0.8rem;">+ Option</button>
|
||||
<label class="multi-toggle">
|
||||
<input type="checkbox" id="multiChoice"> Mehrfachauswahl möglich
|
||||
</label>
|
||||
</div>
|
||||
</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 type="button" id="umfrageBtn" class="compose-action-btn" onclick="toggleUmfrage(this)" title="Umfrage hinzufügen">📊</button>
|
||||
<button onclick="submitPost()" style="width:auto; margin:0;">Veröffentlichen</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -277,18 +211,19 @@
|
||||
</div>
|
||||
|
||||
<!-- Post lightbox dialog -->
|
||||
<div class="lightbox" id="postDialog">
|
||||
<div class="lightbox" id="postLightbox">
|
||||
<div class="lb-layout">
|
||||
<button class="lb-close" onclick="closePostDialog()">✕</button>
|
||||
<div class="lb-post-side" id="lbPostContent"></div>
|
||||
<button class="lb-close" onclick="closeLb()">✕</button>
|
||||
<div class="lb-post-side" id="lbPostBody"></div>
|
||||
<div class="lb-comments-panel">
|
||||
<div class="lb-comments-header">Kommentare</div>
|
||||
<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>
|
||||
onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();postLbComment()}"></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>
|
||||
<button onclick="postLbComment()">Senden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -374,6 +309,7 @@
|
||||
if (!meRes.ok) { location.href='/login.html'; return; }
|
||||
const me = await meRes.json();
|
||||
myId = me.userId;
|
||||
initLb(myId);
|
||||
|
||||
await loadGruppe();
|
||||
await loadPosts();
|
||||
@@ -471,15 +407,24 @@
|
||||
}
|
||||
|
||||
|
||||
const gruppeEditBilder = new Map();
|
||||
|
||||
function renderPostCard(p) {
|
||||
const canEdit = p.authorId === myId;
|
||||
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);
|
||||
const bildHtml = bilderGrid(p.bilder);
|
||||
const editedLabel = p.editedAt ? ` <span style="font-size:0.75rem;color:var(--color-muted);">(bearbeitet)</span>` : '';
|
||||
|
||||
let body = '';
|
||||
// editable view area: question/text + images
|
||||
const textStyle = p.beitragTyp === 'UMFRAGE' ? ' style="font-weight:600;margin-bottom:0.5rem;"' : '';
|
||||
const editableHtml = `<div class="post-text"${textStyle}>${renderTextWithHashtags(p.text)}</div><div id="gpbi-${p.beitragId}">${bildHtml}</div>`;
|
||||
|
||||
// poll bars (only for UMFRAGE, not editable)
|
||||
let barsHtml = '';
|
||||
if (p.beitragTyp === 'UMFRAGE') {
|
||||
const total = p.optionen.reduce((s, o) => s + o.stimmenCount, 0);
|
||||
const bars = p.optionen.map(o => {
|
||||
barsHtml = 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)">
|
||||
@@ -489,23 +434,25 @@
|
||||
<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}`;
|
||||
}).join('') + `<div class="umfrage-total">${total} Stimme${total !== 1 ? 'n' : ''} gesamt${p.multiChoice?' · Multi-Choice':''}</div>`;
|
||||
}
|
||||
|
||||
const rightBtns = (canEdit ? `<button class="post-action-btn" onclick="event.stopPropagation();startGruppeEdit('${p.beitragId}')" title="Bearbeiten" style="color:var(--color-muted)">✏</button>` : '')
|
||||
+ (canDelete ? `<button class="post-action-btn danger post-delete" onclick="event.stopPropagation(); deletePost('${p.beitragId}',this)">✕</button>` : '');
|
||||
|
||||
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 class="post-avatar"><a href="/community/benutzer.html?userId=${p.authorId}" onclick="event.stopPropagation()" style="display:contents;">${av}</a></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 class="post-meta" id="gpm-${p.beitragId}">${fmtDate(p.createdAt)}${editedLabel}</div>
|
||||
</div>
|
||||
<div class="post-date">${fmtDate(p.createdAt)}</div>
|
||||
${rightBtns ? `<div style="margin-left:auto;display:flex;gap:0.25rem;">${rightBtns}</div>` : ''}
|
||||
</div>
|
||||
${p.beitragTyp === 'UMFRAGE' ? `<div style="font-weight:600;margin-bottom:0.5rem;">${renderTextWithHashtags(p.text)}</div>${bildHtml}` : ''}
|
||||
${body}
|
||||
<div id="gpva-${p.beitragId}">${editableHtml}</div>
|
||||
<div id="gpea-${p.beitragId}" style="display:none;"></div>
|
||||
<div id="gpum-${p.beitragId}">${barsHtml}</div>
|
||||
<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>
|
||||
@@ -514,11 +461,162 @@
|
||||
💬 <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>`;
|
||||
}
|
||||
|
||||
function buildGruppeUmfrageHtml(beitragId, optionen, myVoteOptionIds, multiChoice) {
|
||||
if (!optionen || optionen.length === 0) return '';
|
||||
const total = optionen.reduce((s, o) => s + o.stimmenCount, 0);
|
||||
return optionen.map(o => {
|
||||
const pct = total > 0 ? Math.round(o.stimmenCount / total * 100) : 0;
|
||||
const voted = myVoteOptionIds.includes(o.optionId);
|
||||
return `<div class="umfrage-option-bar ${voted?'voted':''}" onclick="event.stopPropagation(); vote('${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('') + `<div class="umfrage-total">${total} Stimme${total !== 1 ? 'n' : ''} gesamt${multiChoice?' · Multi-Choice':''}</div>`;
|
||||
}
|
||||
|
||||
function startGruppeEdit(beitragId) {
|
||||
const post = allPosts.find(p => p.beitragId === beitragId);
|
||||
if (!post) return;
|
||||
gruppeEditBilder.set(beitragId, [...(post.bilder || [])]);
|
||||
document.getElementById('gpva-' + beitragId).style.display = 'none';
|
||||
document.getElementById('gpum-' + beitragId).style.display = 'none';
|
||||
|
||||
const isUmfrage = post.beitragTyp === 'UMFRAGE';
|
||||
const optionenHtml = isUmfrage
|
||||
? `<div id="gpeo-${beitragId}" style="margin-top:0.5rem;">${(post.optionen || []).map((o) =>
|
||||
`<div class="umfrage-option-row">
|
||||
<input type="text" value="${esc(o.text)}" maxlength="200" data-option-id="${o.optionId}"
|
||||
style="flex:1;padding:0.4rem 0.6rem;border:1px solid var(--color-secondary);border-radius:6px;background:var(--color-secondary);color:var(--color-text);font-size:0.9rem;"
|
||||
onmousedown="event.stopPropagation()" onclick="event.stopPropagation()" onkeydown="event.stopPropagation()">
|
||||
<button onmousedown="event.stopPropagation()" onclick="event.stopPropagation();this.closest('.umfrage-option-row').remove()" style="width:auto;margin:0;padding:0.3rem 0.6rem;font-size:0.8rem;">✕</button>
|
||||
</div>`).join('')}
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:0.3rem;" onclick="event.stopPropagation()">
|
||||
<button onmousedown="event.stopPropagation()" onclick="event.stopPropagation();gruppeEditAddOption('${beitragId}')" style="width:auto;margin:0;padding:0.3rem 0.75rem;font-size:0.8rem;">+ Option</button>
|
||||
<label class="multi-toggle" onmousedown="event.stopPropagation()" onclick="event.stopPropagation()">
|
||||
<input type="checkbox" id="gpmc-${beitragId}" ${post.multiChoice ? 'checked' : ''}> Mehrfachauswahl möglich
|
||||
</label>
|
||||
</div>
|
||||
</div>`
|
||||
: '';
|
||||
const actionRow = `<div style="display:flex;gap:0.5rem;align-items:center;margin-top:0.5rem;" onclick="event.stopPropagation()">
|
||||
<label class="compose-action-btn" title="Fotos hinzufügen">📷
|
||||
<input type="file" accept="image/*" multiple style="display:none;" onchange="event.stopPropagation();gruppeEditAddImg(this,'${beitragId}')">
|
||||
</label>
|
||||
<button onclick="event.stopPropagation();saveGruppeEdit('${beitragId}')" style="width:auto;margin:0;">Speichern</button>
|
||||
<button onclick="event.stopPropagation();cancelGruppeEdit('${beitragId}')" style="width:auto;margin:0;background:var(--color-secondary);color:var(--color-text);">Abbrechen</button>
|
||||
</div>`;
|
||||
|
||||
const ea = document.getElementById('gpea-' + beitragId);
|
||||
ea.style.display = '';
|
||||
ea.onclick = e => e.stopPropagation();
|
||||
ea.innerHTML = `
|
||||
<textarea id="gpet-${beitragId}" style="width:100%;box-sizing:border-box;padding:0.6rem;border:1px solid var(--color-secondary);border-radius:6px;background:var(--color-secondary);color:var(--color-text);font-size:0.95rem;resize:vertical;min-height:70px;" onclick="event.stopPropagation()" onkeydown="event.stopPropagation()">${esc(post.text)}</textarea>
|
||||
<div class="compose-thumbs" id="gpet-tb-${beitragId}" style="margin-top:0.4rem;"></div>
|
||||
${optionenHtml}
|
||||
${actionRow}`;
|
||||
renderGruppeEditThumbs(beitragId);
|
||||
}
|
||||
|
||||
function cancelGruppeEdit(beitragId) {
|
||||
document.getElementById('gpva-' + beitragId).style.display = '';
|
||||
document.getElementById('gpum-' + beitragId).style.display = '';
|
||||
document.getElementById('gpea-' + beitragId).style.display = 'none';
|
||||
gruppeEditBilder.delete(beitragId);
|
||||
}
|
||||
|
||||
function renderGruppeEditThumbs(beitragId) {
|
||||
const bilder = gruppeEditBilder.get(beitragId) || [];
|
||||
const c = document.getElementById('gpet-tb-' + beitragId);
|
||||
c.innerHTML = bilder.map((b, i) =>
|
||||
`<div class="compose-thumb"><img src="data:image/jpeg;base64,${b}" alt="">
|
||||
<button class="compose-thumb-remove" onclick="event.stopPropagation();gruppeEditRmImg('${beitragId}',${i})">✕</button></div>`
|
||||
).join('');
|
||||
c.style.display = bilder.length > 0 ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function gruppeEditRmImg(beitragId, idx) {
|
||||
gruppeEditBilder.get(beitragId).splice(idx, 1);
|
||||
renderGruppeEditThumbs(beitragId);
|
||||
}
|
||||
|
||||
function gruppeEditAddImg(input, beitragId) {
|
||||
[...input.files].forEach(f => {
|
||||
if (!f.type.startsWith('image/')) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const MAX = 1024, canvas = document.createElement('canvas');
|
||||
const s = Math.min(MAX / img.width, MAX / img.height, 1);
|
||||
canvas.width = Math.round(img.width * s); canvas.height = Math.round(img.height * s);
|
||||
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
gruppeEditBilder.get(beitragId).push(canvas.toDataURL('image/jpeg', 0.85).split(',')[1]);
|
||||
renderGruppeEditThumbs(beitragId);
|
||||
};
|
||||
img.src = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(f);
|
||||
});
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
function gruppeEditAddOption(beitragId) {
|
||||
const container = document.getElementById('gpeo-' + beitragId);
|
||||
const count = container.querySelectorAll('input[type=text]').length;
|
||||
const row = document.createElement('div');
|
||||
row.className = 'umfrage-option-row';
|
||||
row.innerHTML = `<input type="text" placeholder="Option ${count + 1}" maxlength="200"
|
||||
style="flex:1;padding:0.4rem 0.6rem;border:1px solid var(--color-secondary);border-radius:6px;background:var(--color-secondary);color:var(--color-text);font-size:0.9rem;"
|
||||
onmousedown="event.stopPropagation()" onclick="event.stopPropagation()" onkeydown="event.stopPropagation()">
|
||||
<button onmousedown="event.stopPropagation()" onclick="event.stopPropagation();this.closest('.umfrage-option-row').remove()" style="width:auto;margin:0;padding:0.3rem 0.6rem;font-size:0.8rem;">✕</button>`;
|
||||
container.insertBefore(row, container.querySelector('div:last-child'));
|
||||
}
|
||||
|
||||
async function saveGruppeEdit(beitragId) {
|
||||
const text = document.getElementById('gpet-' + beitragId).value.trim();
|
||||
if (!text) return;
|
||||
const post = allPosts.find(p => p.beitragId === beitragId);
|
||||
const bilder = gruppeEditBilder.get(beitragId) || [];
|
||||
const isUmfrageEdit = post?.beitragTyp === 'UMFRAGE';
|
||||
const optionen = isUmfrageEdit
|
||||
? Array.from(document.querySelectorAll(`#gpeo-${beitragId} input[type=text]`))
|
||||
.map(inp => ({ optionId: inp.dataset.optionId || null, text: inp.value.trim() }))
|
||||
.filter(o => o.text)
|
||||
: null;
|
||||
const multiChoice = isUmfrageEdit ? (document.getElementById('gpmc-' + beitragId)?.checked ?? false) : null;
|
||||
|
||||
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + beitragId, {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, bilder, optionen, multiChoice })
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const updated = await res.json();
|
||||
|
||||
const idx = allPosts.findIndex(p => p.beitragId === beitragId);
|
||||
if (idx >= 0) allPosts[idx] = { ...allPosts[idx], text: updated.text, bilder: updated.bilder || [], optionen: updated.optionen || [], multiChoice: updated.multiChoice };
|
||||
gruppeEditBilder.delete(beitragId);
|
||||
|
||||
const gpva = document.getElementById('gpva-' + beitragId);
|
||||
gpva.querySelector('.post-text').innerHTML = renderTextWithHashtags(updated.text);
|
||||
const pbi = document.getElementById('gpbi-' + beitragId);
|
||||
if (pbi) pbi.innerHTML = bilderGrid(updated.bilder);
|
||||
gpva.style.display = '';
|
||||
document.getElementById('gpea-' + beitragId).style.display = 'none';
|
||||
|
||||
const gpum = document.getElementById('gpum-' + beitragId);
|
||||
gpum.innerHTML = buildGruppeUmfrageHtml(beitragId, updated.optionen, post?.myVoteOptionIds || [], post?.multiChoice);
|
||||
gpum.style.display = '';
|
||||
|
||||
const meta = document.getElementById('gpm-' + beitragId);
|
||||
if (meta && !meta.querySelector('.edited-label')) {
|
||||
meta.insertAdjacentHTML('beforeend', ' <span class="edited-label" style="font-size:0.75rem;color:var(--color-muted);">(bearbeitet)</span>');
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleLike(postId, btn) {
|
||||
const res = await fetch('/gruppen/' + gruppeId + '/posts/' + postId + '/like', { method:'POST' });
|
||||
if (!res.ok) return;
|
||||
@@ -620,17 +718,25 @@
|
||||
|
||||
// ── 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';
|
||||
function toggleUmfrage(btn) {
|
||||
const options = document.getElementById('umfrageOptions');
|
||||
const isShowing = options.style.display !== 'none';
|
||||
options.style.display = isShowing ? 'none' : '';
|
||||
const placeholder = document.getElementById('composeText');
|
||||
placeholder.placeholder = isUmfrage ? 'Frage eingeben…' : 'Was möchtest du teilen?';
|
||||
if (isUmfrage && document.getElementById('optionList').children.length === 0) {
|
||||
placeholder.placeholder = isShowing ? 'Was möchtest du teilen?' : 'Frage eingeben…';
|
||||
if (btn) btn.classList.toggle('active', !isShowing);
|
||||
if (!isShowing && document.getElementById('optionList').children.length === 0) {
|
||||
addOption(); addOption();
|
||||
}
|
||||
}
|
||||
|
||||
function resetUmfrage() {
|
||||
document.getElementById('umfrageOptions').style.display = 'none';
|
||||
document.getElementById('optionList').innerHTML = '';
|
||||
document.getElementById('composeText').placeholder = 'Was möchtest du teilen?';
|
||||
document.getElementById('umfrageBtn').classList.remove('active');
|
||||
}
|
||||
|
||||
function addOption() {
|
||||
const list = document.getElementById('optionList');
|
||||
const idx = list.children.length;
|
||||
@@ -642,11 +748,12 @@
|
||||
|
||||
async function submitPost() {
|
||||
const text = document.getElementById('composeText').value.trim();
|
||||
const hasUmfrage = document.getElementById('umfrageOptions').style.display !== 'none';
|
||||
if (!text) return;
|
||||
const beitragTyp = document.querySelector('input[name="beitragTyp"]:checked')?.value || 'TEXT';
|
||||
const beitragTyp = hasUmfrage ? 'UMFRAGE' : 'TEXT';
|
||||
let optionen = null;
|
||||
let multiChoice = null;
|
||||
if (beitragTyp === 'UMFRAGE') {
|
||||
if (hasUmfrage) {
|
||||
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;
|
||||
@@ -659,9 +766,7 @@
|
||||
});
|
||||
if (res.ok || res.status === 201) {
|
||||
document.getElementById('composeText').value = '';
|
||||
document.getElementById('optionList').innerHTML = '';
|
||||
document.querySelector('input[name="beitragTyp"][value="TEXT"]').checked = true;
|
||||
toggleUmfrage();
|
||||
resetUmfrage();
|
||||
composeBilderArr = [];
|
||||
renderComposeThumbs();
|
||||
await loadPosts();
|
||||
@@ -938,28 +1043,22 @@
|
||||
|
||||
// ── 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();
|
||||
_lbSetupContent(postId, 'gp', post.bilder);
|
||||
document.getElementById('postLightbox').classList.add('open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
await loadLbComments(postId, 'GROUP');
|
||||
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 = '';
|
||||
|
||||
let umfrageHtml = '';
|
||||
if (p.beitragTyp === 'UMFRAGE') {
|
||||
const total = p.optionen.reduce((s, o) => s + o.stimmenCount, 0);
|
||||
const bars = p.optionen.map(o => {
|
||||
@@ -973,19 +1072,23 @@
|
||||
</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}`;
|
||||
umfrageHtml = bars + `<div class="umfrage-total">${total} Stimme${total !== 1 ? 'n' : ''} gesamt${p.multiChoice?' · Multi-Choice':''}</div>`;
|
||||
}
|
||||
document.getElementById('lbPostContent').innerHTML = `
|
||||
|
||||
const textStyle = p.beitragTyp === 'UMFRAGE' ? ' style="font-weight:600;margin-bottom:0.5rem;"' : '';
|
||||
document.getElementById('lbPostBody').innerHTML = `
|
||||
<div class="post-header">
|
||||
<div class="post-avatar">${av}</div>
|
||||
<div class="post-avatar"><a href="/community/benutzer.html?userId=${p.authorId}" style="display:contents;">${av}</a></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 id="gpva-${p.beitragId}">
|
||||
<div class="post-text"${textStyle}>${renderTextWithHashtags(p.text)}</div>
|
||||
<div id="gpbi-${p.beitragId}"></div>
|
||||
</div>
|
||||
<div id="gpum-${p.beitragId}">${umfrageHtml}</div>
|
||||
<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>
|
||||
@@ -1011,41 +1114,7 @@
|
||||
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;
|
||||
@@ -1071,7 +1140,7 @@
|
||||
if (res.ok || res.status === 204) {
|
||||
document.getElementById('post-' + postId)?.remove();
|
||||
allPosts = allPosts.filter(p => p.beitragId !== postId);
|
||||
closePostDialog();
|
||||
closeLb();
|
||||
if (allPosts.length === 0) { document.getElementById('postsEmpty').style.display = ''; document.getElementById('loadMoreBtn').style.display = 'none'; }
|
||||
}
|
||||
}}
|
||||
@@ -1087,13 +1156,15 @@
|
||||
if (!res.ok) return;
|
||||
await loadPosts();
|
||||
const post = allPosts.find(p => p.beitragId === postId);
|
||||
if (post) renderLbPost(post);
|
||||
if (post) {
|
||||
renderLbPost(post);
|
||||
_lbSetupContent(postId, 'gp', post.bilder);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('postDialog').addEventListener('click', e => {
|
||||
if (e.target === document.getElementById('postDialog')) closePostDialog();
|
||||
document.getElementById('postLightbox').addEventListener('click', e => {
|
||||
if (e.target === document.getElementById('postLightbox')) closeLb();
|
||||
});
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') closePostDialog(); });
|
||||
|
||||
|
||||
init();
|
||||
|
||||
Reference in New Issue
Block a user