buhapp-telegram/public/js/views/chat.js
root bfaa3af1af Debug: loadMessages/appendMessage логи
Grok рекомендовал: console.log чтобы понять:
1. r.messages это массив
2. appendMessage не падает с exception
3. #messages элемент существует
2026-08-21 12:38:01 +00:00

224 lines
9.2 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.

// Single chat view
const ChatView = {
chatId: null,
otherId: null,
otherName: null,
ws: null,
async render(root, params) {
this.chatId = params.chatId;
this.otherId = params.otherId;
this.otherName = params.otherName;
root.innerHTML = `
<div class="view" style="display:flex;flex-direction:column;height:calc(100vh - 100px);padding:0;">
<!-- header -->
<div style="padding:12px 16px;border-bottom:1px solid var(--border);background:var(--bg-elev);display:flex;align-items:center;gap:8px;">
<button class="btn-b ghost tiny" id="chat-back" style="padding:6px 10px;">←</button>
<div class="avatar-b sm" style="${API.Store.user?.other_photo_for_other ? '' : ''}">${escapeHtml(((this.otherName || '?')[0] || '?').toUpperCase())}</div>
<div style="flex:1;font-weight:600;font-size:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(this.otherName || '')}</div>
<button class="btn-b ghost tiny" id="chat-profile" style="padding:6px 10px;">👤</button>
<button class="btn-b ghost tiny" id="chat-review" style="padding:6px 10px;">⭐</button>
</div>
<!-- messages -->
<div id="messages" style="flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:6px;"></div>
<!-- edit bar -->
<div id="edit-bar" style="display:none;padding:8px 16px;background:var(--bg-elev);border-top:1px solid var(--border);font-size:13px;color:var(--text-dim);">
<span>✎ Редактирование</span>
<button id="edit-cancel" style="float:right;background:none;border:none;color:var(--danger);font-weight:600;cursor:pointer;">Отмена</button>
</div>
<!-- composer -->
<div class="composer-b">
<button class="attach-b" id="msg-attach" title="Прикрепить фото">📎</button>
<input id="msg-input" placeholder="Сообщение..." autocomplete="off" enterkeyhint="send">
<button class="send-b" id="msg-send" title="Отправить">➤</button>
</div>
</div>
`;
document.getElementById('chat-back').onclick = () => App.showView('chats');
document.getElementById('chat-profile').onclick = () => App.openUserProfile(this.otherId, this.otherName);
document.getElementById('chat-review').onclick = () => App.openReview(this.chatId, this.otherId, this.otherName);
document.getElementById('msg-attach').onclick = () => {
const inp = document.createElement('input');
inp.type = 'file'; inp.accept = 'image/*';
inp.onchange = () => this.sendPhoto(inp.files[0]);
inp.click();
};
document.getElementById('msg-input').addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.send();
}
});
document.getElementById('msg-send').onclick = () => this.send();
this.loadMessages();
this.setupWS();
this.markRead();
},
async loadMessages() {
console.log('[Chat] loadMessages start, chatId:', this.chatId);
try {
const r = await API.ChatApi.listMessages(this.chatId);
console.log('[Chat] listMessages response:', JSON.stringify(r).slice(0, 500), 'messages isArray:', Array.isArray(r.messages));
const box = document.getElementById('messages');
console.log('[Chat] messages box:', box, 'tag:', box?.tagName);
if (!box) { console.error('[Chat] no #messages element'); return; }
box.innerHTML = '';
const msgs = Array.isArray(r.messages) ? r.messages : (Array.isArray(r) ? r : []);
console.log('[Chat] will append', msgs.length, 'messages');
msgs.forEach((m) => {
try { this.appendMessage(m); } catch (e) { console.error('[Chat] appendMessage failed for', m, e); }
});
box.scrollTop = box.scrollHeight;
} catch (e) {
console.error('[Chat] loadMessages error:', e);
try {
fetch('/api/v1/debug/log', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ kind: 'ChatLoadError', msg: String(e && e.message), stack: String(e && e.stack || '').slice(0, 400) }) });
} catch {}
}
},
appendMessage(m) {
console.log('[Chat] appendMessage called, m:', JSON.stringify(m).slice(0, 200), 'me check:', API.Store.user && m.sender_id === (API.Store.user.id || API.Store.user.user_id));
const box = document.getElementById('messages');
if (!box) { console.error('[Chat] appendMessage: no box'); return; }
const me = API.Store.user && m.sender_id === (API.Store.user.id || API.Store.user.user_id);
const div = document.createElement('div');
div.className = 'bubble-b ' + (me ? 'out' : 'in');
div.dataset.id = m.id;
if (m.deleted) {
div.innerHTML = '<em style="color:var(--text-mute);">Удалено</em>';
} else {
const readMark = me
? (m.read_at ? '<span class="ticks read" title="прочитано">✓✓</span>' : '<span class="ticks" title="доставлено">✓</span>')
: '';
div.innerHTML = `
<div>${escapeHtml(m.body || '')}</div>
<div class="meta">
<span>${formatTime(m.created_at)}</span>
${m.edited ? '<span style="font-size:10px;margin:0 4px;">ред.</span>' : ''}
${readMark}
</div>
`;
if (me) {
div.onclick = () => this.onMessageClick(m);
}
}
box.appendChild(div);
console.log('[Chat] appended bubble, children:', box.children.length);
box.scrollTop = box.scrollHeight;
},
onMessageClick(m) {
TG_APP.showConfirm('Удалить сообщение?').then((ok) => {
if (ok) this.deleteMessage(m);
});
},
async deleteMessage(m) {
try {
await API.ChatApi.deleteMessage(m.id);
const el = document.querySelector(`.bubble[data-id="${m.id}"]`);
if (el) el.innerHTML = '<em>Удалено</em>';
} catch (e) {
TG_APP.showAlert('Ошибка: ' + e.message);
}
},
setupWS() {
if (!API.Store.access) return;
const url = (window.API_BASE || 'https://api.buhapp.mygoodservice.ru').replace(/^http/, 'ws') + '/ws?token=' + API.Store.access;
try {
this.ws = new WebSocket(url);
this.ws.onopen = () => console.log('WS connected');
this.ws.onmessage = (ev) => {
try {
const d = JSON.parse(ev.data);
if (d.type === 'message' && d.payload.chat_id === this.chatId) {
this.appendMessage(d.payload);
} else if (d.type === 'message_edited' && d.payload.chat_id === this.chatId) {
this.updateMessage(d.payload);
} else if (d.type === 'message_read' && d.payload.chat_id === this.chatId) {
this.markMessagesRead();
}
} catch {}
};
this.ws.onclose = () => setTimeout(() => this.setupWS(), 3000);
this.ws.onerror = (e) => console.warn('WS error', e);
} catch (e) {
console.warn(e);
}
},
async markMessagesRead() {
try {
const r = await API.ChatApi.listMessages(this.chatId);
r.messages.forEach((m) => {
if (m.read_at) {
const el = document.querySelector(`.bubble[data-id="${m.id}"] .read-tick`);
if (el) { el.classList.add('read'); el.textContent = '✓✓'; }
}
});
} catch {}
},
updateMessage(p) {
const el = document.querySelector(`.bubble[data-id="${p.id}"]`);
if (el) {
el.querySelector('div:first-child') && (el.querySelector('div:first-child').textContent = p.body);
const meta = el.querySelector('.meta');
if (meta && !meta.querySelector('.edited')) {
const ed = document.createElement('span');
ed.className = 'edited';
ed.textContent = 'ред.';
meta.appendChild(ed);
}
}
},
async send() {
const inp = document.getElementById('msg-input');
const btn = document.getElementById('msg-send');
const body = inp.value.trim();
if (!body) { App._toast('Введите сообщение', 'err'); btn.classList.add('err'); setTimeout(() => btn.classList.remove('err'), 600); return; }
btn.classList.add('load');
try {
await API.ChatApi.sendMessage(this.chatId, body);
inp.value = '';
btn.classList.remove('load');
} catch (e) {
btn.classList.remove('load');
App._toast('⚠️ ' + (e.message || 'Не отправлено'), 'err');
}
},
async sendPhoto(file) {
if (!file) return;
if (file.size > 8 * 1024 * 1024) { App._toast('Файл больше 8 МБ', 'err'); return; }
App._toast('⏳ Загружаю фото…', 'ok');
const fd = new FormData();
fd.append('photo', file);
try {
const r = await fetch(API_BASE + '/api/v1/chats/' + this.chatId + '/photo', { method: 'POST', body: fd, credentials: 'include' });
if (!r.ok) throw new Error((await r.json()).error || 'HTTP ' + r.status);
App._toast('✓ Фото отправлено', 'ok');
this.loadMessages();
} catch (e) {
App._toast('⚠️ ' + (e.message || 'Ошибка'), 'err');
}
},
async markRead() {
try { await API.ChatApi.markRead(this.chatId); } catch {}
},
cleanup() {
if (this.ws) { this.ws.close(); this.ws = null; }
},
};
window.ChatView = ChatView;