Files
Kontrollverlust/web/js/app.js
T
irrlicht a47f41bbfa Frontend: Registrierung angebunden (POST /auth/newuser)
- Umschalter Login <-> Registrieren im Auth-Bereich
- Fehlerlisten-Anzeige ({"errors": [...]}) fuer newuser
- nach erfolgreicher Registrierung automatischer Login
- Logout setzt Ansicht auf Login zurueck

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:47:00 +02:00

212 lines
5.6 KiB
JavaScript
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.
"use strict";
const api = {
async get(url) {
const r = await fetch(url, { credentials: "same-origin" });
return r.json();
},
async post(url, formData) {
const r = await fetch(url, {
method: "POST",
body: formData,
credentials: "same-origin",
});
return { ok: r.ok, status: r.status, data: await r.json().catch(() => ({})) };
},
};
// --- Headerbar (eingeloggt?) ---
let loggedIn = false;
async function refreshHeader() {
const h = await api.get("/auth/headerbar");
loggedIn = !!h.loggedin;
const bar = document.getElementById("headerbar");
document.getElementById("auth-box").hidden = loggedIn;
document.getElementById("compose").hidden = !loggedIn;
if (loggedIn) {
bar.innerHTML = `<button id="logout-btn">Logout</button>`;
document.getElementById("logout-btn").addEventListener("click", async () => {
await api.get("/auth/logout");
await refreshHeader();
});
} else {
bar.innerHTML = "";
showAuth("login");
}
}
// --- Login / Registrierung ---
const loginForm = document.getElementById("login-form");
const registerForm = document.getElementById("register-form");
const authMsg = document.getElementById("auth-msg");
function showAuth(which) {
loginForm.hidden = which !== "login";
registerForm.hidden = which !== "register";
authMsg.textContent = "";
}
document.getElementById("show-register").addEventListener("click", (e) => {
e.preventDefault();
showAuth("register");
});
document.getElementById("show-login").addEventListener("click", (e) => {
e.preventDefault();
showAuth("login");
});
loginForm.addEventListener("submit", async (e) => {
e.preventDefault();
const res = await api.post("/auth/login", new FormData(e.target));
if (res.ok) {
await refreshHeader();
} else {
authMsg.textContent = res.data.error || "Login fehlgeschlagen.";
}
});
registerForm.addEventListener("submit", async (e) => {
e.preventDefault();
const data = new FormData(e.target);
const res = await api.post("/auth/newuser", data);
if (!res.ok) {
// newuser liefert eine Fehlerliste ({"errors": [...]}).
const errors = res.data.errors || [res.data.error || "Registrierung fehlgeschlagen."];
authMsg.textContent = errors.join(" ");
return;
}
// Direkt mit denselben Daten einloggen, damit der Nutzer gleich drin ist.
const login = new FormData();
login.append("user", data.get("user"));
login.append("pass", data.get("pass1"));
login.append("timeout", "86400");
await api.post("/auth/login", login);
await refreshHeader();
});
// --- Neuer Beitrag ---
document.getElementById("entry-form").addEventListener("submit", async (e) => {
e.preventDefault();
const res = await api.post("/entry/create", new FormData(e.target));
const msg = document.getElementById("entry-msg");
if (res.ok) {
msg.textContent = "";
e.target.reset();
resetFeed();
} else {
msg.textContent = res.data.error || "Beitrag fehlgeschlagen.";
}
});
// --- Feed mit Infinite Scroll ---
let page = 0;
let loading = false;
let done = false;
function renderEntry(e) {
const el = document.createElement("article");
el.className = "entry";
const when = new Date(e.created_at * 1000).toLocaleString("de-DE");
let media = "";
if (e.filepath) {
media = `<img src="/${e.filepath}" alt="" loading="lazy">`;
}
el.innerHTML = `
<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 () => {
const fd = new FormData();
fd.append("mode", b.dataset.mode);
const res = await api.post(`/entry/${pid}/vote`, fd);
render(res.data);
})
);
// Anfangszustand laden (read-only).
api.get(`/entry/${pid}/votes`).then(render);
}
async function loadMore() {
if (loading || done) return;
loading = true;
const entries = await api.get(`/entry/feed/${page}`);
const feed = document.getElementById("feed");
if (!entries.length) {
done = true;
const end = document.createElement("p");
end.className = "end";
end.textContent = "YOU REACHED THE END!";
feed.appendChild(end);
} else {
entries.forEach((e) => feed.appendChild(renderEntry(e)));
page++;
}
loading = false;
}
function resetFeed() {
page = 0;
done = false;
document.getElementById("feed").innerHTML = "";
loadMore();
}
const observer = new IntersectionObserver((items) => {
if (items.some((i) => i.isIntersecting)) loadMore();
});
observer.observe(document.getElementById("sentinel"));
// --- Helpers ---
function escapeHtml(s) {
const d = document.createElement("div");
d.textContent = s ?? "";
return d.innerHTML;
}
function linkify(s) {
const escaped = escapeHtml(s);
const url = /https?:\/\/[^\s<]+/g;
return escaped
.replace(url, (m) => `<a href="${m}" target="_blank" rel="noopener">${m}</a>`)
.replace(/\n/g, "<br>");
}
// --- Start ---
refreshHeader();
loadMore();