Hashtags eingeführt
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled

This commit is contained in:
2026-04-11 01:14:33 +02:00
parent ec1409820b
commit e2a71ab096
57 changed files with 2365 additions and 740 deletions

View File

@@ -68,6 +68,11 @@
.empty-hint { color:var(--color-muted); font-size:0.9rem; margin-top:0.5rem; }
.sentinel { height:1px; }
.hashtag-banner { display:flex; align-items:center; gap:0.75rem; margin-bottom:1.25rem; padding:0.65rem 1rem; background:var(--color-card); border:1px solid var(--color-secondary); border-radius:8px; }
.hashtag-banner-tag { font-size:1.05rem; font-weight:700; color:var(--color-primary); }
.hashtag-banner-back { margin-left:auto; font-size:0.82rem; color:var(--color-muted); text-decoration:none; }
.hashtag-banner-back:hover { color:var(--color-primary); }
/* 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; }
@@ -92,7 +97,13 @@
<div class="main">
<div class="content">
<div class="tabs">
<!-- Hashtag-Banner (nur sichtbar wenn ?tag=… gesetzt) -->
<div class="hashtag-banner" id="hashtagBanner" style="display:none;">
<span>Posts mit </span><span class="hashtag-banner-tag" id="hashtagBannerLabel"></span>
<a class="hashtag-banner-back" href="/community/feed.html">× Zurück zum Feed</a>
</div>
<div class="tabs" id="feedTabs">
<button class="tab-btn active" id="tabMine" data-tab="mine" onclick="switchTab('mine', this)">Mein Feed</button>
<button class="tab-btn" id="tabPublic" data-tab="public" onclick="switchTab('public', this)">Öffentlicher Feed</button>
</div>
@@ -140,6 +151,13 @@
<div class="sentinel" id="publicSentinel"></div>
</div>
<!-- Hashtag-Feed (wird angezeigt wenn ?tag=… gesetzt) -->
<div id="tab-hashtag" style="display:none;">
<div id="hashtagFeed"></div>
<p class="empty-hint" id="hashtagEmpty" style="display:none;">Keine Posts mit diesem Hashtag.</p>
<div class="sentinel" id="hashtagSentinel"></div>
</div>
</div>
</div>
@@ -165,37 +183,68 @@
<script src="/js/shared.js"></script>
<script src="/js/icons.js"></script>
<script src="/js/nav.js"></script>
<script src="/js/nav.js"></script>
<script src="/js/social-sidebar.js"></script>
<script src="/js/meldung.js"></script>
<script src="/js/hashtag.js"></script>
<script>
// ── State ──
let myUserId = null;
let activeLbPostId = null;
let activeLbPostId = null;
let activeLbPostType = null;
let activeHashtag = null; // set when ?tag=... is in URL
const feedState = {
mine: { page:0, hasMore:true, loading:false, loaded:false },
public: { page:0, hasMore:true, loading:false, loaded:false }
mine: { page:0, hasMore:true, loading:false, loaded:false },
public: { page:0, hasMore:true, loading:false, loaded:false },
hashtag: { page:0, hasMore:true, loading:false, loaded:false }
};
let composeBilderArr = [];
// ── Hashtag-Modus prüfen ──
const _urlTag = new URLSearchParams(window.location.search).get('tag');
if (_urlTag) {
activeHashtag = _urlTag.replace(/^#/, '').toLowerCase();
document.getElementById('hashtagBanner').style.display = '';
document.getElementById('hashtagBannerLabel').textContent = '#' + activeHashtag;
document.getElementById('feedTabs').style.display = 'none';
document.getElementById('tab-mine').style.display = 'none';
document.getElementById('tab-public').style.display = 'none';
document.getElementById('tab-hashtag').style.display = '';
document.getElementById('compose').style.display = 'none';
}
// ── Boot ──
fetch('/login/me').then(r => r.ok ? r.json() : null).then(async user => {
if (user) {
myUserId = user.userId;
const raw = sessionStorage.getItem('feedOpenPost');
if (raw) {
sessionStorage.removeItem('feedOpenPost');
loadFeed('mine');
openLbWithData(JSON.parse(raw));
if (activeHashtag) {
await loadFeed('hashtag');
} else {
await loadFeed('mine');
const raw = sessionStorage.getItem('feedOpenPost');
if (raw) {
sessionStorage.removeItem('feedOpenPost');
loadFeed('mine');
openLbWithData(JSON.parse(raw));
} else {
await loadFeed('mine');
}
}
}
}).catch(() => {});
// ── Autocomplete für Compose ──
document.addEventListener('DOMContentLoaded', () => {
const ta = document.getElementById('composeText');
if (ta) attachHashtagAutocomplete(ta);
});
// Fallback falls DOMContentLoaded bereits gefeuert
if (document.readyState !== 'loading') {
const ta = document.getElementById('composeText');
if (ta) attachHashtagAutocomplete(ta);
}
// ── Tab switching ──
function switchTab(name, btn) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
@@ -218,8 +267,14 @@
state.loading = true;
state.loaded = true;
try {
const endpoint = tab === 'mine' ? '/feed/mine' : '/feed/public';
const res = await fetch(`${endpoint}?page=${state.page}&size=10`);
let url;
if (tab === 'hashtag') {
url = `/feed/hashtag?tag=${encodeURIComponent(activeHashtag)}&page=${state.page}&size=10`;
} else {
const base = tab === 'mine' ? '/feed/mine' : '/feed/public';
url = `${base}?page=${state.page}&size=10`;
}
const res = await fetch(url);
if (!res.ok) return;
const data = await res.json();
const feedEl = document.getElementById(tab + 'Feed');
@@ -238,12 +293,14 @@
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (!e.isIntersecting) return;
if (e.target.id === 'mineSentinel') loadFeed('mine');
if (e.target.id === 'publicSentinel') loadFeed('public');
if (e.target.id === 'mineSentinel') loadFeed('mine');
if (e.target.id === 'publicSentinel') loadFeed('public');
if (e.target.id === 'hashtagSentinel') loadFeed('hashtag');
});
}, { threshold: 0.5 });
observer.observe(document.getElementById('mineSentinel'));
observer.observe(document.getElementById('publicSentinel'));
observer.observe(document.getElementById('hashtagSentinel'));
// bilderCarousel und carNav kommen aus shared.js
@@ -297,7 +354,7 @@
</div>
${deleteBtn}
</div>
<div class="post-text">${esc(p.text)}</div>
<div class="post-text">${renderTextWithHashtags(p.text)}</div>
${bildHtml}
${umfrageHtml}
<div class="post-actions">