Repost
This commit is contained in:
@@ -248,10 +248,21 @@ img.avatar-lg { width: 72px; height: 72px; }
|
||||
oben in der Byline). */
|
||||
.interactions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Bump-Button: tritt an die Stelle der früheren Bump-Kommentare. Im Cooldown
|
||||
gesperrt (gilt pro Beitrag für alle), der Button zeigt dann die Restzeit. */
|
||||
.bump:disabled {
|
||||
color: gray;
|
||||
border-color: #ccc;
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+42
-2
@@ -47,11 +47,13 @@ function renderEntry(e) {
|
||||
const when = new Date(e.created_at * 1000).toLocaleString("de-DE");
|
||||
const replyLabel =
|
||||
e.reply_count === 1 ? "1 Antwort" : `${e.reply_count} Antworten`;
|
||||
const repostLabel =
|
||||
e.bump_count === 1 ? "1 Repost" : `${e.bump_count} Reposts`;
|
||||
|
||||
// Soft-gelöschter Beitrag: nur Platzhalter, damit der Thread navigierbar bleibt.
|
||||
if (e.deleted) {
|
||||
el.innerHTML = `
|
||||
<div class="muted byline"><span>[deleted] · ${when} · <a href="/e/${e.pid}">${replyLabel}</a></span></div>
|
||||
<div class="muted byline"><span>[deleted] · ${when} · <a href="/e/${e.pid}">${replyLabel}</a> · ${repostLabel}</span></div>
|
||||
<div class="threadline-wrapper">
|
||||
<div class="threadline"></div>
|
||||
<div class="body">
|
||||
@@ -74,7 +76,7 @@ function renderEntry(e) {
|
||||
el.innerHTML = `
|
||||
<div class="muted byline">
|
||||
<a href="/u/${user}"><img class="avatar avatar-sm" src="${avatarUrl(e.avatar)}" alt="" loading="lazy"></a>
|
||||
<span><a href="/u/${user}">${escapeHtml(e.username)}</a> · ${when} · <a href="/e/${e.pid}">${replyLabel}</a></span>
|
||||
<span><a href="/u/${user}">${escapeHtml(e.username)}</a> · ${when} · <a href="/e/${e.pid}">${replyLabel}</a> · ${repostLabel}</span>
|
||||
</div>
|
||||
<div class="threadline-wrapper">
|
||||
<div class="threadline"></div>
|
||||
@@ -87,11 +89,13 @@ function renderEntry(e) {
|
||||
<span class="muted">– / –</span>
|
||||
<button class="ghost" data-mode="right">Rechts</button>
|
||||
</div>
|
||||
<button class="bump" type="button">Repost</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
setupVoting(el, e.pid);
|
||||
setupBump(el, e);
|
||||
el.addEventListener("click", (ev) => {
|
||||
if (!ev.target.closest("a, button")) location.href = `/e/${e.pid}`;
|
||||
});
|
||||
@@ -126,6 +130,42 @@ function setupVoting(el, pid) {
|
||||
api.get(`/entry/${pid}/votes`).then(render);
|
||||
}
|
||||
|
||||
// setupBump verdrahtet den Bump-Button. Der Cooldown wächst pro Beitrag linear
|
||||
// (Backend, siehe handleBump); hier wird nur angezeigt und auf 429 reagiert. Eine
|
||||
// Live-Aktualisierung gibt es bewusst nicht: der Zustand stammt aus den
|
||||
// Beitragsdaten beim Laden, plus die Antwort des eigenen Klicks.
|
||||
function setupBump(el, e) {
|
||||
const btn = el.querySelector(".bump");
|
||||
if (!btn) return;
|
||||
|
||||
// sekunden bis zum nächsten erlaubten Bump aus den geladenen Daten.
|
||||
const nowSec = () => Math.floor(Date.now() / 1000);
|
||||
|
||||
// lock sperrt den Button und zeigt die grobe Restzeit (auf Tage aufgerundet,
|
||||
// mindestens 1 -> nie "0 Tage", solange noch Restzeit übrig ist).
|
||||
function lock(retryAfter) {
|
||||
btn.disabled = true;
|
||||
const days = Math.max(1, Math.ceil(retryAfter / 86400));
|
||||
btn.textContent = `Repost (${days} ${days === 1 ? "Tag" : "Tage"})`;
|
||||
}
|
||||
|
||||
const remaining = e.last_bump + e.bump_count * 86400 - nowSec();
|
||||
if (e.bump_count > 0 && remaining > 0) lock(remaining);
|
||||
|
||||
btn.addEventListener("click", async () => {
|
||||
const res = await api.post(`/entry/${e.pid}/bump`, new FormData());
|
||||
if (res.ok) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = "repostet";
|
||||
} else if (res.status === 429) {
|
||||
lock(res.data.retry_after || 86400);
|
||||
} else {
|
||||
// 401 (nicht angemeldet) o. Ä. -> Fehlertext zeigen, Button frei lassen.
|
||||
btn.textContent = res.data.error || "Fehler";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// createFeed hängt Infinite-Scroll an feedEl/sentinelEl und lädt Seiten über
|
||||
// urlFor(page). Gibt { reset } zurück, um den Feed neu zu laden.
|
||||
function createFeed(feedEl, sentinelEl, urlFor) {
|
||||
|
||||
Reference in New Issue
Block a user