Implement left/right voting on entries
GET /entry/{pid}/interactions/{mode} now reads the vote tally and, for
mode=left/right (auth required), toggles the user's vote: new vote, repeat
same mode removes it, different mode switches. Returns {left, right,
selected}. A UNIQUE(uid, pid) constraint on the vote table prevents
duplicate votes. The JS frontend renders Links/Rechts buttons with live
counts under each entry.
This completes the voting feature that was left commented-out and broken
in the Flask version.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -73,3 +73,23 @@ button:hover { opacity: 0.9; }
|
||||
|
||||
.msg { color: #ff6b6b; font-size: 0.9rem; min-height: 1.2em; }
|
||||
.end { text-align: center; color: var(--muted); padding: 2rem 0; }
|
||||
|
||||
.interactions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.interactions .vote {
|
||||
background: transparent;
|
||||
border: 1px solid #2a2e35;
|
||||
color: var(--fg);
|
||||
padding: 0.3rem 0.7rem;
|
||||
}
|
||||
.interactions .vote:hover { border-color: var(--accent); }
|
||||
.interactions .vote.selected {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
.interactions .counts { color: var(--muted); font-size: 0.9rem; }
|
||||
|
||||
@@ -83,10 +83,42 @@ function renderEntry(e) {
|
||||
<div class="meta"><strong>${escapeHtml(e.username)}</strong> · ${when}</div>
|
||||
<div class="content">${linkify(e.content)}</div>
|
||||
${media}
|
||||
<div class="interactions">
|
||||
<button class="vote" data-mode="left">Links</button>
|
||||
<span class="counts">– / –</span>
|
||||
<button class="vote" data-mode="right">Rechts</button>
|
||||
</div>
|
||||
`;
|
||||
setupVoting(el, e.pid);
|
||||
return el;
|
||||
}
|
||||
|
||||
function setupVoting(el, pid) {
|
||||
const box = el.querySelector(".interactions");
|
||||
const counts = box.querySelector(".counts");
|
||||
const buttons = box.querySelectorAll(".vote");
|
||||
|
||||
function render(state) {
|
||||
if (state.error) {
|
||||
counts.textContent = state.error;
|
||||
return;
|
||||
}
|
||||
counts.textContent = `${state.left} / ${state.right}`;
|
||||
buttons.forEach((b) =>
|
||||
b.classList.toggle("selected", b.dataset.mode === state.selected)
|
||||
);
|
||||
}
|
||||
|
||||
buttons.forEach((b) =>
|
||||
b.addEventListener("click", async () => {
|
||||
render(await api.get(`/entry/${pid}/interactions/${b.dataset.mode}`));
|
||||
})
|
||||
);
|
||||
|
||||
// Anfangszustand laden, ohne zu werten.
|
||||
api.get(`/entry/${pid}/interactions/none`).then(render);
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (loading || done) return;
|
||||
loading = true;
|
||||
|
||||
Reference in New Issue
Block a user