feat: Nachrichten als gelöscht markieren

- DB: deleted_on TEXT Spalte in messages (Migration)
- Backend: ToggleMessageDelete(), includeDeleted-Parameter in allen History-Queries
- Neuer Endpoint: PUT /api/messages/{id}/delete
- Frontend: Trash-Button zwischen Reply- und Mark-Button
- Gelöschte Nachrichten: durchgestrichen + roter Rand (oder versteckt je nach Einstellung)
- Einstellungen: Toggle-Switch 'Gelöschte Nachrichten anzeigen'
- KI-Payload: gelöschte Nachrichten werden nicht mitgeschickt
This commit is contained in:
Meleki committed 2026-03-03 11:45:52 +01:00
1 parent 493dfcda1c
commit a14a991a0f
11 files changed
+248 -39

No files matched your search

+56 -6
View File
@@ -115,6 +115,46 @@ function truncate(s, max) {
return s && s.length > max ? s.substring(0, max) + '...' : (s || '');
}
// ── Delete Button ──
function _addDeleteButton(div, messageId, deletedOn) {
if (!messageId) return;
const isDeleted = !!deletedOn;
const btn = document.createElement('button');
btn.className = 'msg-delete-btn icon-btn' + (isDeleted ? ' deleted' : '');
btn.innerHTML = '<i class="fa fa-trash-o"></i>';
btn.title = isDeleted ? 'Löschung aufheben' : 'Nachricht löschen';
btn.addEventListener('click', async (e) => {
e.stopPropagation();
try {
const result = await apiToggleMessageDelete(messageId);
const nowDeleted = !!result.deletedOn;
btn.classList.toggle('deleted', nowDeleted);
btn.title = nowDeleted ? 'Löschung aufheben' : 'Nachricht löschen';
if (nowDeleted && !isDeletedVisible()) {
// Nachricht ausblenden
div.classList.add('msg-deleted-hidden');
} else {
div.classList.remove('msg-deleted-hidden');
div.classList.toggle('msg-deleted', nowDeleted);
}
} catch (err) {
console.error('Delete toggle failed:', err);
}
});
// Nach dem Reply-Button einfügen
const replyBtn = div.querySelector('.msg-reply-btn');
if (replyBtn) {
replyBtn.after(btn);
} else {
div.prepend(btn);
}
}
function isDeletedVisible() {
return loadSettings().showDeletedMessages === true;
}
// ── Mark Button ──
function _addMarkButton(div, messageId, isMarked) {
@@ -134,11 +174,17 @@ function _addMarkButton(div, messageId, isMarked) {
console.error('Mark toggle failed:', err);
}
});
const replyBtn = div.querySelector('.msg-reply-btn');
if (replyBtn) {
replyBtn.after(btn);
// Nach dem Delete-Button einfügen (oder nach Reply-Button, oder am Anfang)
const deleteBtn = div.querySelector('.msg-delete-btn');
if (deleteBtn) {
deleteBtn.after(btn);
} else {
div.prepend(btn);
const replyBtn = div.querySelector('.msg-reply-btn');
if (replyBtn) {
replyBtn.after(btn);
} else {
div.prepend(btn);
}
}
}
@@ -238,23 +284,26 @@ function _renderUserTextContent(container, raw) {
parseFluentEmojis(wrap);
}
function addUserBubble(text, streamed = false, timestamp = '', messageId = null, isMarked = false) {
function addUserBubble(text, streamed = false, timestamp = '', messageId = null, isMarked = false, deletedOn = null) {
const div = document.createElement('div');
div.className = 'msg user markdown' + (isMarked ? ' marked' : '');
if (deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
div.dataset.role = 'user';
div.dataset.timestamp = timestamp || new Date().toISOString();
if (messageId) div.dataset.messageId = messageId;
if (streamed) div.classList.add('streamed');
renderUserContent(div, text);
_addReplyButton(div);
_addDeleteButton(div, messageId, deletedOn);
_addMarkButton(div, messageId, isMarked);
chatEl().appendChild(div);
scrollDown();
}
function addAssistantBubble(text, timestamp = '', messageId = null, isMarked = false) {
function addAssistantBubble(text, timestamp = '', messageId = null, isMarked = false, deletedOn = null) {
const div = document.createElement('div');
div.className = 'msg assistant markdown' + (isMarked ? ' marked' : '');
if (deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
div.dataset.role = 'assistant';
div.dataset.timestamp = timestamp || new Date().toISOString();
if (messageId) div.dataset.messageId = messageId;
@@ -262,6 +311,7 @@ function addAssistantBubble(text, timestamp = '', messageId = null, isMarked = f
parseFluentEmojis(div);
wrapKreaGalleries(div);
_addReplyButton(div);
_addDeleteButton(div, messageId, deletedOn);
_addMarkButton(div, messageId, isMarked);
chatEl().appendChild(div);
scrollDown();