- 40+ Utility-Klassen in form.css (clickable, text-danger, flex-row, loading-state, etc.) - share-menu position/z-index in chat.css statt inline - index.html: 0 Inline-Styles (hidden-Attribut, CSS-Klassen) - 12 JS-Dateien bereinigt: style.cssText/style.* durch classList ersetzt - Bugfix: var(--danger) → var(--dangerous) (nicht existierende Variable) - display:none Toggles → hidden-Attribut wo passend - Hardcoded font-sizes in chat.css → var(--font-size) 15 Dateien, nur Frontend (CSS/JS/HTML), kein Backend
195 lines
6.0 KiB
JavaScript
195 lines
6.0 KiB
JavaScript
// ── Timestamp ──
|
|
// formatTimestamp() → defined in ui-helpers.js
|
|
|
|
function _addTimestamp(div, timestamp) {
|
|
if (!timestamp) return;
|
|
var formatted = formatTimestamp(timestamp);
|
|
if (!formatted) return;
|
|
var span = document.createElement('span');
|
|
span.className = 'msg-timestamp';
|
|
span.textContent = formatted;
|
|
div.prepend(span);
|
|
}
|
|
|
|
// ── 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) {
|
|
if (!messageId) return; // kein DB-Eintrag → kein Button
|
|
const btn = document.createElement('button');
|
|
btn.className = 'msg-mark-btn icon-btn' + (isMarked ? ' marked' : '');
|
|
btn.innerHTML = isMarked ? '<i class="fa fa-star"></i>' : '<i class="fa fa-star-o"></i>';
|
|
btn.title = 'Als wichtig markieren';
|
|
btn.addEventListener('click', async (e) => {
|
|
e.stopPropagation();
|
|
try {
|
|
const result = await apiToggleMessageMark(messageId);
|
|
btn.classList.toggle('marked', result.isMarked);
|
|
btn.innerHTML = result.isMarked ? '<i class="fa fa-star"></i>' : '<i class="fa fa-star-o"></i>';
|
|
div.classList.toggle('marked', result.isMarked);
|
|
} catch (err) {
|
|
console.error('Mark toggle failed:', err);
|
|
}
|
|
});
|
|
// 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 {
|
|
const replyBtn = div.querySelector('.msg-reply-btn');
|
|
if (replyBtn) {
|
|
replyBtn.after(btn);
|
|
} else {
|
|
div.prepend(btn);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Share Button ──
|
|
|
|
function _addShareButton(div, messageId) {
|
|
if (!messageId) return;
|
|
const btn = document.createElement('button');
|
|
btn.className = 'msg-share-btn icon-btn';
|
|
btn.innerHTML = '<i class="fa fa-share-alt"></i>';
|
|
btn.title = 'Nachricht teilen';
|
|
btn.addEventListener('click', async (e) => {
|
|
e.stopPropagation();
|
|
try {
|
|
// Kontextmenü: Einzelnachricht oder mit Kontext?
|
|
const mode = await _showShareMenu(btn);
|
|
if (!mode) return; // abgebrochen
|
|
|
|
const result = await apiShareMessage(messageId, mode === 'context');
|
|
const url = location.origin + '/#shared/' + result.token;
|
|
await navigator.clipboard.writeText(url);
|
|
showToast('Link kopiert!', 'success');
|
|
} catch (err) {
|
|
showToast('Fehler: ' + err.message, 'error');
|
|
}
|
|
});
|
|
// Nach dem Mark-Button einfügen
|
|
const markBtn = div.querySelector('.msg-mark-btn');
|
|
if (markBtn) {
|
|
markBtn.after(btn);
|
|
} else {
|
|
const deleteBtn = div.querySelector('.msg-delete-btn');
|
|
if (deleteBtn) {
|
|
deleteBtn.after(btn);
|
|
} else {
|
|
div.prepend(btn);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _showShareMenu(anchorBtn) {
|
|
return new Promise((resolve) => {
|
|
// Wenn schon ein Menü offen, schließen
|
|
const existing = document.querySelector('.share-menu');
|
|
if (existing) { existing.remove(); resolve(null); return; }
|
|
|
|
const menu = document.createElement('div');
|
|
menu.className = 'share-menu';
|
|
menu.innerHTML = `
|
|
<button class="share-menu-item" data-mode="single">
|
|
<i class="fa fa-comment-o"></i> Einzelne Nachricht
|
|
</button>
|
|
<button class="share-menu-item" data-mode="context">
|
|
<i class="fa fa-comments-o"></i> Mit Kontext (Thread)
|
|
</button>
|
|
`;
|
|
|
|
// Positionierung
|
|
const rect = anchorBtn.getBoundingClientRect();
|
|
menu.style.top = (rect.bottom + 4) + 'px';
|
|
menu.style.left = rect.left + 'px';
|
|
|
|
document.body.appendChild(menu);
|
|
|
|
function cleanup() {
|
|
menu.remove();
|
|
document.removeEventListener('click', outsideClick);
|
|
}
|
|
|
|
function outsideClick(e) {
|
|
if (!menu.contains(e.target)) {
|
|
cleanup();
|
|
resolve(null);
|
|
}
|
|
}
|
|
|
|
menu.querySelectorAll('.share-menu-item').forEach(item => {
|
|
item.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const mode = item.dataset.mode;
|
|
cleanup();
|
|
resolve(mode);
|
|
});
|
|
});
|
|
|
|
// Beim nächsten Klick außerhalb schließen
|
|
setTimeout(() => document.addEventListener('click', outsideClick), 10);
|
|
});
|
|
}
|
|
|
|
// ── Reply Button ──
|
|
|
|
function _addReplyButton(div) {
|
|
const btn = document.createElement('button');
|
|
btn.className = 'msg-reply-btn icon-btn';
|
|
btn.innerHTML = '<i class="fa fa-reply"></i>';
|
|
btn.title = 'Antworten';
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
// Nur den eigentlichen Nachrichten-Text holen (ohne Reply-Button-Text)
|
|
const clone = div.cloneNode(true);
|
|
clone.querySelectorAll('.msg-reply-btn, .attachment-indicator, .reply-quote').forEach(el => el.remove());
|
|
const text = clone.textContent.trim();
|
|
setReply({
|
|
role: div.dataset.role,
|
|
timestamp: div.dataset.timestamp || '',
|
|
text
|
|
});
|
|
});
|
|
div.prepend(btn);
|
|
}
|