Open NeuralNet chats before history loads
mindflux-ci / build (push) Successful in 24s

This commit is contained in:
AliceDarkest
2026-07-14 16:19:52 +02:00
parent fb487be037
commit f9e6516124
4 changed files with 20 additions and 8 deletions
+3
View File
@@ -7,6 +7,9 @@ Versionierung [semantisch](https://semver.org/lang/de/).
## [Unreleased] – 2026-07-14 · SysnaptiK Neural Rebrand ## [Unreleased] – 2026-07-14 · SysnaptiK Neural Rebrand
### Marke & Plattform ### Marke & Plattform
- **NeuralNet-Chat öffnet sofort:** Der Klick auf eine Verbindung schaltet das
Gespräch samt Eingabefeld nun unabhängig vom anschließenden Verlaufsabruf frei;
Chat-Aktionen sind explizit als Buttons markiert.
- **NeuralNet-Chat repariert:** Leere, erfolgreiche API-Antworten werden nicht mehr - **NeuralNet-Chat repariert:** Leere, erfolgreiche API-Antworten werden nicht mehr
fälschlich als JSON gelesen. Kontaktaktionen aktualisieren die Oberfläche wieder fälschlich als JSON gelesen. Kontaktaktionen aktualisieren die Oberfläche wieder
zuverlässig und bestätigte Verbindungen lassen sich direkt im Chat öffnen. zuverlässig und bestätigte Verbindungen lassen sich direkt im Chat öffnen.
+1
View File
@@ -489,6 +489,7 @@ Der Verdacht „Profilbutton fehlt / Einstellungen nicht im Profil / irgendwas i
> ✅ **MVP-Stand 2026-07-14:** Opt-in, Kontaktanfragen (annehmen, ablehnen, > ✅ **MVP-Stand 2026-07-14:** Opt-in, Kontaktanfragen (annehmen, ablehnen,
> zurückziehen) und private Nachrichten nach bestätigter Verbindung sind umgesetzt. > zurückziehen) und private Nachrichten nach bestätigter Verbindung sind umgesetzt.
> Leere Erfolgsantworten des API-Backends werden im Client robust verarbeitet. > Leere Erfolgsantworten des API-Backends werden im Client robust verarbeitet.
> Der Gesprächsbereich öffnet unmittelbar beim Auswählen einer bestätigten Verbindung.
> Blockieren, Melden/Moderation, Duelle und Lernlobby bleiben die nächsten Ausbauschritte. > Blockieren, Melden/Moderation, Duelle und Lernlobby bleiben die nächsten Ausbauschritte.
> Ersetzt den `/soziales`-Platzhalter („Straßen? Wo wir hingehen, brauchen wir keine Straßen!"). Ziel: User können sich befreunden, intern miteinander schreiben, gemeinsam lernen und Achievements vergleichen – **vollständig selbst gehostet, ohne einen einzigen externen Dienst**, und DSGVO-konform von Grund auf. > Ersetzt den `/soziales`-Platzhalter („Straßen? Wo wir hingehen, brauchen wir keine Straßen!"). Ziel: User können sich befreunden, intern miteinander schreiben, gemeinsam lernen und Achievements vergleichen – **vollständig selbst gehostet, ohne einen einzigen externen Dienst**, und DSGVO-konform von Grund auf.
+15 -7
View File
@@ -28,13 +28,13 @@
} }
function render(data) { function render(data) {
q('[data-nn-incoming]').innerHTML = data.incoming.length ? data.incoming.map(function (name) { q('[data-nn-incoming]').innerHTML = data.incoming.length ? data.incoming.map(function (name) {
return item(name, '<button data-action="accept" data-user="' + esc(name) + '">Annehmen</button><button data-action="decline" data-user="' + esc(name) + '">Ablehnen</button>'); return item(name, '<button type="button" data-action="accept" data-user="' + esc(name) + '">Annehmen</button><button type="button" data-action="decline" data-user="' + esc(name) + '">Ablehnen</button>');
}).join('') : '<span class="mf-small-hint">Keine offenen Anfragen.</span>'; }).join('') : '<span class="mf-small-hint">Keine offenen Anfragen.</span>';
q('[data-nn-outgoing]').innerHTML = data.outgoing.length ? data.outgoing.map(function (name) { q('[data-nn-outgoing]').innerHTML = data.outgoing.length ? data.outgoing.map(function (name) {
return item(name, '<button data-action="cancel" data-user="' + esc(name) + '">Zurückziehen</button>'); return item(name, '<button type="button" data-action="cancel" data-user="' + esc(name) + '">Zurückziehen</button>');
}).join('') : '<span class="mf-small-hint">Keine gesendeten Anfragen.</span>'; }).join('') : '<span class="mf-small-hint">Keine gesendeten Anfragen.</span>';
q('[data-nn-connections]').innerHTML = data.connections.length ? data.connections.map(function (name) { q('[data-nn-connections]').innerHTML = data.connections.length ? data.connections.map(function (name) {
return item(name, '<button data-action="chat" data-user="' + esc(name) + '" class="' + (selected === name ? 'active' : '') + '">Chat</button>'); return item(name, '<button type="button" data-action="chat" data-user="' + esc(name) + '" class="' + (selected === name ? 'active' : '') + '">Chat</button>');
}).join('') : '<span class="mf-small-hint">Noch keine Verbindungen.</span>'; }).join('') : '<span class="mf-small-hint">Noch keine Verbindungen.</span>';
} }
async function refresh() { async function refresh() {
@@ -47,15 +47,19 @@
async function loadChat() { async function loadChat() {
if (!selected) return; if (!selected) return;
var rows = await api('/messages/' + encodeURIComponent(selected)); var rows = await api('/messages/' + encodeURIComponent(selected));
q('[data-nn-empty]').hidden = true;
q('[data-nn-conversation]').hidden = false;
q('[data-nn-partner]').textContent = selected;
q('[data-nn-messages]').innerHTML = rows.map(function (message) { q('[data-nn-messages]').innerHTML = rows.map(function (message) {
return '<article class="nn-message ' + (message.sender === me ? 'mine' : '') + '">' + esc(message.body) + '<small>' + esc(message.sender) + ' · ' + new Date(message.sentAt).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' }) + '</small></article>'; return '<article class="nn-message ' + (message.sender === me ? 'mine' : '') + '">' + esc(message.body) + '<small>' + esc(message.sender) + ' · ' + new Date(message.sentAt).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' }) + '</small></article>';
}).join(''); }).join('');
var box = q('[data-nn-messages]'); var box = q('[data-nn-messages]');
box.scrollTop = box.scrollHeight; box.scrollTop = box.scrollHeight;
} }
function openChat(user) {
selected = user;
q('[data-nn-empty]').hidden = true;
q('[data-nn-conversation]').hidden = false;
q('[data-nn-partner]').textContent = user;
q('[data-nn-messages]').innerHTML = '<p class="mf-small-hint">Nachrichten werden geladen …</p>';
}
function message(text) { function message(text) {
var hint = q('.nn-head p'); var hint = q('.nn-head p');
hint.textContent = text; hint.textContent = text;
@@ -77,7 +81,11 @@
if (!button) return; if (!button) return;
var user = button.dataset.user, action = button.dataset.action; var user = button.dataset.user, action = button.dataset.action;
try { try {
if (action === 'chat') { selected = user; await refresh(); await loadChat(); return; } if (action === 'chat') {
openChat(user);
try { await loadChat(); } catch (error) { message(error.message); }
return;
}
if (action === 'accept') await api('/requests/' + encodeURIComponent(user) + '/accept', 'POST'); if (action === 'accept') await api('/requests/' + encodeURIComponent(user) + '/accept', 'POST');
if (action === 'decline') await api('/requests/' + encodeURIComponent(user) + '/decline', 'POST'); if (action === 'decline') await api('/requests/' + encodeURIComponent(user) + '/decline', 'POST');
if (action === 'cancel') await api('/requests/' + encodeURIComponent(user), 'DELETE'); if (action === 'cancel') await api('/requests/' + encodeURIComponent(user), 'DELETE');
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NeuralNet – SysnaptiK Neural</title> <title>NeuralNet – SysnaptiK Neural</title>
<link rel="stylesheet" href="/css/theme.css?v=3"><link rel="stylesheet" href="/css/neuralnet.css?v=1"> <link rel="stylesheet" href="/css/theme.css?v=3"><link rel="stylesheet" href="/css/neuralnet.css?v=1">
<script src="/js/theme.js?v=3"></script><script src="/js/neuralnet.js?v=2" defer></script> <script src="/js/theme.js?v=3"></script><script src="/js/neuralnet.js?v=3" defer></script>
</head> </head>
<body> <body>
<div class="mf-theme-float"><button class="mf-theme"></button></div> <div class="mf-theme-float"><button class="mf-theme"></button></div>