lorem_ipsum/js/messages.js

971 lines
31 KiB
JavaScript

(function () {
const MESSAGE_TYPE_CHALLENGE = "challenge";
const MESSAGE_TYPE_CHALLENGE_RESULT = "challenge-result";
const MESSAGE_POLL_INTERVAL_MS = 30000;
const ACTIVE_CHALLENGE_STORAGE_KEY = "loremIpsumActiveChallenge";
const CHALLENGE_DATA_PREFIX = "[[loremIpsumChallenge:";
const CHALLENGE_TEXT_PARTS = {
subjects: [
"Der flinke Entwickler",
"Die neugierige Studentin",
"Ein mueder Professor",
"Das kleine Frontend",
"Der mutige Browser",
"Eine schlaue Funktion",
"Der vergessliche Server",
"Die kreative Gruppe",
],
actions: [
"sortiert leise",
"debuggt geduldig",
"vergleicht heimlich",
"speichert vorsichtig",
"rendert ploetzlich",
"zaehlt konzentriert",
"testet neugierig",
"kompiliert langsam",
],
objects: [
"sieben blaue Buttons",
"drei lange Variablen",
"neun goldene Woerter",
"vier kaputte Formulare",
"acht schnelle Requests",
"zwei leuchtende Karten",
"fuenf stille Fehlermeldungen",
"sechs winzige Icons",
],
places: [
"im hellen Dashboard",
"unter dem dunklen Navbar",
"neben dem alten Footer",
"zwischen Login und Leaderboard",
"vor dem ersten Kaffee",
"waehrend der Lernphase",
"hinter dem lokalen Server",
"mitten im Semesterprojekt",
],
endings: [
"Danach lacht der Code, weil alles endlich funktioniert.",
"Am Ende merkt sich niemand die Semikolons, aber alle die Punkte.",
"Kurz darauf blinkt die Konsole und behauptet, sie sei unschuldig.",
"Spaeter landet der Score im Ranking und wartet auf Applaus.",
"Dabei bleibt die Seite ruhig, obwohl der Timer dramatisch tickt.",
"Zum Schluss gewinnt, wer die Woerter sauber in Reihenfolge bringt.",
],
};
let currentMessages = [];
let currentUsers = [];
let messagePollingInterval = null;
function getRandomChallengeTextPart(items) {
return items[Math.floor(Math.random() * items.length)];
}
function generateChallengeText() {
const firstSentence =
[
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.subjects),
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.actions),
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.objects),
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.places),
].join(" ") + ".";
const secondSentence =
[
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.subjects),
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.actions),
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.objects),
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.places),
].join(" ") + ".";
return firstSentence +
" " +
secondSentence +
" " +
getRandomChallengeTextPart(CHALLENGE_TEXT_PARTS.endings);
}
function buildEmbeddedChallengeText(challengeData, displayText) {
return CHALLENGE_DATA_PREFIX +
JSON.stringify(challengeData) +
"]]" +
"\n" +
displayText;
}
function getAuth() {
if (!window.AppAuth || typeof window.AppAuth.getAuth !== "function") {
return null;
}
const auth = window.AppAuth.getAuth();
if (!auth || !auth.username || !auth.password) {
return null;
}
return auth;
}
function normalizeUsername(username) {
return String(username || "").trim().toLowerCase();
}
function getMessageService() {
if (!window.config || !window.MessageService) {
return null;
}
return new window.MessageService(window.config);
}
function getUserService() {
if (!window.config || !window.UserService) {
return null;
}
return new window.UserService(window.config);
}
function getChallengeService() {
if (!window.config || !window.ChallengeService) {
return null;
}
return new window.ChallengeService(window.config);
}
function normalizeMessage(message) {
// Das Backend und unser Nachrichten-Fallback liefern Challenge-Daten leicht unterschiedlich.
// Hier werden beide Formen in ein einheitliches Message-Objekt gebracht.
const textCandidates = [
message.text,
message.content,
message.message,
message.challenge?.text,
message.result?.text,
];
const rawText = textCandidates.find((value) => String(value ?? "").includes(CHALLENGE_DATA_PREFIX))
?? textCandidates.find((value) => value !== null && value !== undefined)
?? "";
const embeddedChallenge = extractEmbeddedChallenge(rawText);
const type = message.type ?? MESSAGE_TYPE_CHALLENGE;
const backendChallenge = message.challenge
?? message.result
?? (type === MESSAGE_TYPE_CHALLENGE || type === MESSAGE_TYPE_CHALLENGE_RESULT ? message : null);
const challenge = embeddedChallenge.challenge
? { ...(backendChallenge ?? {}), ...embeddedChallenge.challenge }
: backendChallenge;
return {
id: message.id,
sender: message.sender ?? message.from ?? "",
recipient: message.recipient ?? message.to ?? "",
type: type,
text: embeddedChallenge.text,
read: Boolean(message.read),
createdAt: message.createdAt ?? message.time ?? message.date ?? "",
challenge: challenge,
};
}
function extractEmbeddedChallenge(text) {
// Fallback fuer Challenge-Daten, die im Nachrichtentext mitgesendet werden.
// So koennen wir bestehende Message-Endpunkte nutzen, ohne neue Backend-Felder zu verlangen.
const rawText = String(text ?? "");
const startIndex = rawText.indexOf(CHALLENGE_DATA_PREFIX);
if (startIndex === -1) {
return {
text: rawText,
challenge: null,
};
}
const endIndex = rawText.indexOf("]]", startIndex);
if (endIndex === -1) {
return {
text: rawText,
challenge: null,
};
}
const json = rawText.slice(startIndex + CHALLENGE_DATA_PREFIX.length, endIndex);
const displayText = (
rawText.slice(0, startIndex) +
rawText.slice(endIndex + 2)
).trim();
try {
return {
text: displayText,
challenge: JSON.parse(json),
};
} catch {
return {
text: rawText,
challenge: null,
};
}
}
function getChallengeId(challenge) {
return challenge?.challengeId ?? challenge?.challenge_id ?? challenge?.id ?? null;
}
function getChallengeChallenger(challenge, fallbackName) {
return challenge?.challenger
?? challenge?.challengerName
?? challenge?.sender
?? challenge?.from
?? fallbackName
?? "";
}
function getChallengeOpponent(challenge, fallbackName) {
return challenge?.opponent
?? challenge?.opponentName
?? challenge?.challengedUser
?? challenge?.recipient
?? challenge?.to
?? fallbackName
?? "";
}
function getOpponentScore(challenge) {
return challenge?.opponentScore
?? challenge?.challengedScore
?? challenge?.challengedUserScore
?? null;
}
function getChallengeText(challenge) {
return challenge?.challengeText
?? challenge?.roundText
?? challenge?.textToRemember
?? null;
}
function hasScore(value) {
return value !== null && value !== undefined && value !== "";
}
function getChallengeRole(message) {
const auth = getAuth();
const challenge = message.challenge;
if (!auth || !challenge || getChallengeId(challenge) === null) {
return null;
}
const ownName = normalizeUsername(auth.username);
const challenger = normalizeUsername(getChallengeChallenger(challenge, message.sender));
const opponent = normalizeUsername(getChallengeOpponent(challenge, message.recipient));
if (ownName === opponent) {
return "opponent";
}
if (ownName === challenger) {
return "challenger";
}
return null;
}
function canOpponentAcceptChallenge(message) {
if (message.type !== MESSAGE_TYPE_CHALLENGE || !message.challenge) {
return false;
}
const role = getChallengeRole(message);
const challenge = message.challenge;
const opponentScore = getOpponentScore(challenge);
return role === "opponent" && !hasScore(opponentScore) && !hasScore(challenge.challengerScore);
}
function canChallengerPlayChallenge(message) {
const role = getChallengeRole(message);
const challenge = message.challenge;
return role === "challenger"
&& hasScore(getOpponentScore(challenge))
&& !hasScore(challenge.challengerScore);
}
function getChallengeButtonState(message) {
// Bestimmt den sichtbaren Status einer Challenge aus Sicht des eingeloggten Users.
// Dadurch gibt es pro Challenge genau einen aktiven oder deaktivierten Button.
const challenge = message.challenge;
const role = getChallengeRole(message);
if (!challenge || !role) {
return null;
}
const opponentScore = getOpponentScore(challenge);
const challengerHasScore = hasScore(challenge.challengerScore);
const opponentHasScore = hasScore(opponentScore);
if (challengerHasScore && opponentHasScore) {
return {
disabled: true,
label: "Challenge erledigt",
};
}
if (role === "opponent") {
if (!opponentHasScore && !challengerHasScore) {
return {
disabled: false,
label: "Challenge annehmen",
role: "opponent",
};
}
return {
disabled: true,
label: "Schon gespielt",
};
}
if (role === "challenger") {
if (opponentHasScore && !challengerHasScore) {
return {
disabled: false,
label: "Challenge spielen",
role: "challenger",
};
}
return {
disabled: true,
label: "Warte auf Gegner",
};
}
return null;
}
function startChallenge(message, role) {
// Der aktive Challenge-Kontext wird nur fuer die naechste Spielrunde im Session Storage abgelegt.
// play.js liest diesen Zustand aus und weiss dadurch, ob es die erste oder finale Runde ist.
const challenge = message.challenge;
const challenger = getChallengeChallenger(challenge, message.sender);
const opponent = getChallengeOpponent(challenge, message.recipient);
const auth = getAuth();
const otherUser = role === "opponent" ? challenger : opponent;
sessionStorage.setItem(
ACTIVE_CHALLENGE_STORAGE_KEY,
JSON.stringify({
id: getChallengeId(challenge),
role: role,
challenger: challenger,
opponent: otherUser,
opponentScore: role === "challenger" ? getOpponentScore(challenge) : null,
challengeText: getChallengeText(challenge),
ownUsername: auth?.username ?? "",
}),
);
if (typeof window.loadPage === "function") {
window.loadPage("play", "nav-play");
}
}
function normalizeUser(user) {
if (typeof user === "string") {
return user;
}
return user?.name ?? user?.username ?? "";
}
function formatMessageTime(value) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return "";
}
return date.toLocaleString("de-CH", {
day: "2-digit",
month: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
}
function setFeedback(message, type) {
const feedback = document.getElementById("messages-feedback");
if (!feedback) {
return;
}
feedback.className = "alert alert-" + type + " mt-3 mb-0";
feedback.textContent = message;
feedback.classList.remove("d-none");
}
function setFormEnabled(enabled) {
const formElements = document.querySelectorAll(
"#challenge-form button, #challenge-form select, #challenge-form textarea, #mark-read-button, #refresh-messages-button",
);
formElements.forEach((element) => {
element.disabled = !enabled;
});
}
function updateMessagesNavState(messages = currentMessages) {
const navLink = document.getElementById("navbar-messages");
if (!navLink) {
return;
}
const auth = getAuth();
const ownName = normalizeUsername(auth?.username);
const hasUnreadMessages = messages.some((message) => {
const isIncoming = normalizeUsername(message.recipient) === ownName
|| normalizeUsername(message.sender) !== ownName;
return isIncoming && !message.read;
});
navLink.classList.toggle("has-unread-messages", hasUnreadMessages);
}
async function markMessageReadOnClick(message, item) {
const auth = getAuth();
const messageService = getMessageService();
if (!auth || !messageService || !message.id || message.read) {
return;
}
const isIncoming = normalizeUsername(message.recipient) === normalizeUsername(auth.username)
|| normalizeUsername(message.sender) !== normalizeUsername(auth.username);
if (!isIncoming) {
return;
}
message.read = true;
item.classList.remove("message-item-unread");
updateMessagesNavState(currentMessages);
const result = await messageService.markMessageAsRead(
auth.username,
auth.password,
message.id,
);
if (!result.ok) {
message.read = false;
item.classList.add("message-item-unread");
updateMessagesNavState(currentMessages);
setFeedback("Nachricht konnte nicht als gelesen markiert werden.", "warning");
}
}
async function markMessageGroupReadOnClick(messages, item) {
// Eine gruppierte Challenge-Karte kann mehrere einzelne Nachrichten enthalten.
// Beim Anklicken werden deshalb alle ungelesenen eingehenden Nachrichten der Gruppe markiert.
const auth = getAuth();
const messageService = getMessageService();
if (!auth || !messageService) {
return;
}
const unreadIncomingMessages = messages.filter((message) => {
const isIncoming = normalizeUsername(message.recipient) === normalizeUsername(auth.username)
|| normalizeUsername(message.sender) !== normalizeUsername(auth.username);
return isIncoming && !message.read && message.id;
});
if (unreadIncomingMessages.length === 0) {
return;
}
unreadIncomingMessages.forEach((message) => {
message.read = true;
});
item.classList.remove("message-item-unread");
updateMessagesNavState(currentMessages);
const results = await Promise.all(
unreadIncomingMessages.map((message) =>
messageService.markMessageAsRead(auth.username, auth.password, message.id),
),
);
if (results.some((result) => !result.ok)) {
unreadIncomingMessages.forEach((message) => {
message.read = false;
});
item.classList.add("message-item-unread");
updateMessagesNavState(currentMessages);
setFeedback("Challenge-Nachrichten konnten nicht als gelesen markiert werden.", "warning");
}
}
function getMessageTimeValue(message) {
const time = new Date(message.createdAt).getTime();
return Number.isNaN(time) ? 0 : time;
}
function mergeChallengeData(messages) {
// Fuer die Challenge-Karte werden Teildaten aus mehreren Nachrichten zusammengefuehrt
// (z. B. zuerst Challenge-ID, spaeter Gegner-Score, am Schluss Gewinner).
return messages
.slice()
.sort((a, b) => getMessageTimeValue(a) - getMessageTimeValue(b))
.reduce((merged, message) => {
if (!message.challenge) {
return merged;
}
Object.keys(message.challenge).forEach((key) => {
const value = message.challenge[key];
if (value !== null && value !== undefined && value !== "") {
merged[key] = value;
}
});
return merged;
}, {});
}
function groupMessagesByChallenge(messages) {
// Nachrichten mit gleicher Challenge-ID werden zu einer Karte gruppiert,
// damit alte Zwischen-Nachrichten nicht einzeln die Inbox ueberladen.
const groupsByChallenge = new Map();
const standaloneGroups = [];
messages.forEach((message) => {
const challengeId = getChallengeId(message.challenge);
if (challengeId === null || challengeId === undefined) {
standaloneGroups.push({
kind: "message",
latest: message,
messages: [message],
});
return;
}
const key = String(challengeId);
if (!groupsByChallenge.has(key)) {
groupsByChallenge.set(key, []);
}
groupsByChallenge.get(key).push(message);
});
const challengeGroups = Array.from(groupsByChallenge.values()).map((groupMessages) => {
const sortedMessages = groupMessages
.slice()
.sort((a, b) => getMessageTimeValue(b) - getMessageTimeValue(a));
const latest = { ...sortedMessages[0] };
latest.challenge = mergeChallengeData(sortedMessages);
return {
kind: "challenge",
latest: latest,
messages: sortedMessages,
};
});
return standaloneGroups
.concat(challengeGroups)
.sort((a, b) => getMessageTimeValue(b.latest) - getMessageTimeValue(a.latest));
}
function renderUserList(users) {
const userList = document.getElementById("messages-user-list");
const recipientSelect = document.getElementById("challenge-recipient");
if (!userList || !recipientSelect) {
return;
}
const auth = getAuth();
const ownName = normalizeUsername(auth?.username);
const uniqueUsers = Array.from(new Set(users.map(normalizeUser)))
.filter(Boolean)
.filter((username) => normalizeUsername(username) !== ownName)
.sort((a, b) => a.localeCompare(b, "de-CH"));
currentUsers = uniqueUsers;
userList.innerHTML = "";
recipientSelect.innerHTML = "";
if (uniqueUsers.length === 0) {
const emptyMessage = document.createElement("p");
emptyMessage.className = "messages-empty";
emptyMessage.textContent = "Keine anderen User gefunden.";
userList.appendChild(emptyMessage);
return;
}
uniqueUsers.forEach((username) => {
const userButton = document.createElement("button");
userButton.type = "button";
userButton.className = "messages-user-button";
userButton.textContent = username;
userButton.addEventListener("click", () => {
recipientSelect.value = username;
document.getElementById("challenge-text")?.focus();
});
userList.appendChild(userButton);
const option = document.createElement("option");
option.value = username;
option.textContent = username;
recipientSelect.appendChild(option);
});
}
function renderMessages(messages = currentMessages) {
// Rendert normale Nachrichten einzeln und Challenge-Nachrichten als gruppierte Karten.
const messageList = document.getElementById("message-list");
if (!messageList) {
return;
}
const auth = getAuth();
const ownName = normalizeUsername(auth?.username);
messageList.innerHTML = "";
if (messages.length === 0) {
const emptyMessage = document.createElement("p");
emptyMessage.className = "messages-empty";
emptyMessage.textContent = "Noch keine Nachrichten vorhanden.";
messageList.appendChild(emptyMessage);
return;
}
groupMessagesByChallenge(messages)
.forEach((messageGroup) => {
const message = messageGroup.latest;
const item = document.createElement("article");
const isOutgoing = normalizeUsername(message.sender) === ownName;
const hasUnreadIncoming = messageGroup.messages.some((groupMessage) => {
const isIncoming = normalizeUsername(groupMessage.recipient) === ownName
|| normalizeUsername(groupMessage.sender) !== ownName;
return isIncoming && !groupMessage.read;
});
item.className = "message-item";
if (messageGroup.kind === "challenge") {
item.classList.add("challenge-message-group");
}
if (hasUnreadIncoming) {
item.classList.add("message-item-unread");
item.addEventListener("click", () => markMessageGroupReadOnClick(messageGroup.messages, item));
}
const fromToText = messageGroup.kind === "challenge"
? "Challenge"
: isOutgoing
? "An " + message.recipient
: "Von " + message.sender;
const meta = document.createElement("div");
meta.className = "message-meta";
const sender = document.createElement("strong");
sender.textContent = fromToText;
const time = document.createElement("span");
time.textContent = formatMessageTime(message.createdAt);
const text = document.createElement("p");
text.textContent = message.text;
meta.append(sender, time);
item.append(meta, text);
if (messageGroup.kind === "challenge" && messageGroup.messages.length > 1) {
const toggleButton = document.createElement("button");
toggleButton.type = "button";
toggleButton.className = "challenge-thread-toggle";
toggleButton.textContent = "Verlauf anzeigen";
const thread = document.createElement("div");
thread.className = "challenge-thread d-none";
messageGroup.messages.forEach((threadMessage) => {
const threadItem = document.createElement("div");
threadItem.className = "challenge-thread-item";
const threadMeta = document.createElement("span");
threadMeta.textContent = (normalizeUsername(threadMessage.sender) === ownName
? "Du"
: threadMessage.sender || "System") + " · " + formatMessageTime(threadMessage.createdAt);
const threadText = document.createElement("p");
threadText.textContent = threadMessage.text;
threadItem.append(threadMeta, threadText);
thread.appendChild(threadItem);
});
toggleButton.addEventListener("click", (event) => {
event.stopPropagation();
const isCollapsed = thread.classList.toggle("d-none");
toggleButton.textContent = isCollapsed ? "Verlauf anzeigen" : "Verlauf ausblenden";
});
item.appendChild(toggleButton);
item.appendChild(thread);
}
if (message.challenge && hasScore(message.challenge.challengerScore) && hasScore(message.challenge.opponentScore)) {
item.appendChild(createChallengeResultGraphic(message.challenge));
}
const challengeButtonState = getChallengeButtonState(message);
if (challengeButtonState) {
const challengeButton = document.createElement("button");
challengeButton.type = "button";
challengeButton.className = "btn btn-sm mt-3 challenge-status-button";
challengeButton.textContent = challengeButtonState.label;
challengeButton.disabled = challengeButtonState.disabled;
if (!challengeButtonState.disabled) {
challengeButton.addEventListener("click", () => startChallenge(message, challengeButtonState.role));
}
item.appendChild(challengeButton);
}
messageList.appendChild(item);
});
}
function createChallengeResultGraphic(result) {
// Baut die kompakte Ergebnisgrafik mit passendem Bild aus Sicht des aktuellen Users.
const graphic = document.createElement("div");
graphic.className = "challenge-result-graphic";
const winnerName = result.winner ?? "Unentschieden";
const isDraw = result.winner === null || result.winner === "draw";
const auth = getAuth();
const isOwnWin = !isDraw && normalizeUsername(winnerName) === normalizeUsername(auth?.username);
const outcome = isDraw ? "draw" : isOwnWin ? "win" : "loss";
const outcomeText = outcome === "draw"
? "Unentschieden"
: outcome === "win"
? "Gewonnen"
: "Verloren";
const outcomeImage = outcome === "draw"
? "image/unentschieden.png"
: outcome === "win"
? "image/sieg.png"
: "image/verloren.png";
const outcomeHeader = document.createElement("div");
outcomeHeader.className = "challenge-outcome challenge-outcome-" + outcome;
const outcomeImg = document.createElement("img");
outcomeImg.src = outcomeImage;
outcomeImg.alt = outcomeText;
const outcomeLabel = document.createElement("strong");
outcomeLabel.textContent = outcomeText;
outcomeHeader.append(outcomeImg, outcomeLabel);
const headline = document.createElement("div");
headline.className = "challenge-result-headline " + (isDraw ? "challenge-result-draw" : "challenge-result-win");
headline.textContent = isDraw ? "Unentschieden" : "Sieger: " + winnerName;
const scores = document.createElement("div");
scores.className = "challenge-result-scores";
const challenger = document.createElement("div");
challenger.className = "challenge-result-score";
challenger.innerHTML = "<strong></strong><span></span>";
challenger.querySelector("strong").textContent = result.challenger ?? "Herausforderer";
challenger.querySelector("span").textContent = String(result.challengerScore ?? "-") + " Punkte";
const opponent = document.createElement("div");
opponent.className = "challenge-result-score";
opponent.innerHTML = "<strong></strong><span></span>";
opponent.querySelector("strong").textContent = result.opponent ?? "Gegner";
opponent.querySelector("span").textContent = String(result.opponentScore ?? "-") + " Punkte";
scores.append(challenger, opponent);
graphic.append(outcomeHeader, headline, scores);
return graphic;
}
async function loadUsers() {
const auth = getAuth();
const userService = getUserService();
if (!auth || !userService || typeof userService.getUsers !== "function") {
renderUserList([]);
return;
}
const result = await userService.getUsers(auth.username, auth.password);
if (!result.ok || !Array.isArray(result.body)) {
renderUserList([]);
setFeedback("User konnten nicht geladen werden. Backend-Endpunkt GET /users fehlt eventuell noch.", "warning");
return;
}
renderUserList(result.body);
}
async function loadMessages(options = {}) {
// Laedt Nachrichten vom Backend und blendet die Seite bei fehlendem Login in einen Hinweiszustand.
const auth = getAuth();
const messageService = getMessageService();
const loggedInDiv = document.getElementById("messages-content");
const loggedOutDiv = document.getElementById("messages-login-placeholder");
if (!auth) {
if (loggedInDiv) loggedInDiv.classList.add("d-none");
if (loggedOutDiv) loggedOutDiv.classList.remove("d-none");
currentMessages = [];
renderMessages();
updateMessagesNavState();
setFormEnabled(false);
return;
}
if (loggedInDiv) loggedInDiv.classList.remove("d-none");
if (loggedOutDiv) loggedOutDiv.classList.add("d-none");
if (!messageService) {
setFormEnabled(false);
setFeedback("Message-Service konnte nicht geladen werden.", "danger");
return;
}
const result = await messageService.getMessages(auth.username, auth.password);
if (!result.ok || !Array.isArray(result.body)) {
currentMessages = [];
renderMessages();
updateMessagesNavState();
setFormEnabled(false);
setFeedback("Nachrichten konnten nicht geladen werden. Backend-Endpunkt GET /messages fehlt eventuell noch.", "warning");
return;
}
currentMessages = result.body.map(normalizeMessage);
renderMessages();
updateMessagesNavState();
setFormEnabled(true);
if (options.showFeedback) {
setFeedback("Nachrichten wurden aktualisiert.", "success");
}
}
async function handleChallengeSubmit(event) {
// Erstellt eine Challenge. Der Gegner spielt zuerst; der weitere Ablauf wird ueber Nachrichten gesteuert.
event.preventDefault();
const auth = getAuth();
const challengeService = getChallengeService();
const recipientSelect = document.getElementById("challenge-recipient");
const textInput = document.getElementById("challenge-text");
if (!auth || !challengeService || !recipientSelect || !textInput) {
setFeedback("Bitte zuerst einloggen, um Challenges zu senden.", "warning");
return;
}
const recipient = recipientSelect.value;
const text = textInput.value.trim();
if (!recipient || !text) {
setFeedback("Bitte Empfaenger und Nachricht eingeben.", "warning");
return;
}
const challengeText = generateChallengeText();
const challengeMessage = buildEmbeddedChallengeText(
{ challengeText: challengeText },
text,
);
const result = await challengeService.postChallenge(
auth.username,
auth.password,
recipient,
challengeMessage,
);
if (!result.ok) {
setFeedback("Challenge konnte nicht gesendet werden (Status " + result.status + ").", "danger");
return;
}
sessionStorage.removeItem(ACTIVE_CHALLENGE_STORAGE_KEY);
setFeedback(
"Challenge an " + recipient + " wurde gesendet. Der Gegner spielt zuerst; danach bekommst du sein Resultat.",
"success",
);
await loadMessages({ showFeedback: false });
}
async function handleMarkRead() {
const auth = getAuth();
const messageService = getMessageService();
if (!auth || !messageService) {
setFeedback("Bitte zuerst einloggen, um Nachrichten zu markieren.", "warning");
return;
}
const result = await messageService.markAllMessagesAsRead(auth.username, auth.password);
if (!result.ok) {
const unreadMessages = currentMessages.filter((message) => !message.read);
const readResults = await Promise.all(
unreadMessages.map((message) =>
messageService.markMessageAsRead(auth.username, auth.password, message.id),
),
);
if (readResults.some((readResult) => !readResult.ok)) {
setFeedback("Nachrichten konnten nicht als gelesen markiert werden.", "danger");
return;
}
}
setFeedback("Alle Nachrichten wurden als gelesen markiert.", "info");
await loadMessages({ showFeedback: false });
}
async function initMessagesPage() {
const challengeForm = document.getElementById("challenge-form");
const markReadButton = document.getElementById("mark-read-button");
const refreshMessagesButton = document.getElementById("refresh-messages-button");
if (challengeForm) {
challengeForm.addEventListener("submit", handleChallengeSubmit);
}
if (markReadButton) {
markReadButton.addEventListener("click", handleMarkRead);
}
if (refreshMessagesButton) {
refreshMessagesButton.addEventListener("click", () => loadMessages({ showFeedback: true }));
}
setFormEnabled(Boolean(getAuth()));
await loadUsers();
await loadMessages({ showFeedback: false });
}
window.initMessagesPage = initMessagesPage;
window.updateMessagesNavState = function () {
loadMessages({ showFeedback: false }).catch((error) => {
console.error("Nachrichtenstatus konnte nicht geladen werden:", error);
updateMessagesNavState([]);
});
};
document.addEventListener("DOMContentLoaded", () => {
window.updateMessagesNavState();
if (!messagePollingInterval) {
messagePollingInterval = window.setInterval(
window.updateMessagesNavState,
MESSAGE_POLL_INTERVAL_MS,
);
}
});
})();