modularize frontend and implement new css and themes
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
var activeSessionId = null;
|
||||
var supporterWs = null;
|
||||
var lastTypingSessionId = null;
|
||||
var supporterTypingActive = false;
|
||||
|
||||
function setAppHeight() {
|
||||
var height = window.visualViewport ? window.visualViewport.height : window.innerHeight;
|
||||
document.documentElement.style.setProperty('--app-height', height + 'px');
|
||||
}
|
||||
|
||||
setAppHeight();
|
||||
window.addEventListener('resize', setAppHeight);
|
||||
if (window.visualViewport) {
|
||||
window.visualViewport.addEventListener('resize', setAppHeight);
|
||||
window.visualViewport.addEventListener('scroll', setAppHeight);
|
||||
}
|
||||
|
||||
function openSidebar() {
|
||||
document.getElementById('sidebar').classList.add('open');
|
||||
document.getElementById('drawer-backdrop').classList.add('visible');
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
document.getElementById('sidebar').classList.remove('open');
|
||||
document.getElementById('drawer-backdrop').classList.remove('visible');
|
||||
}
|
||||
|
||||
function selectSession(button) {
|
||||
document.querySelectorAll('.session-item').forEach(function (b) { b.classList.remove('selected'); });
|
||||
button.classList.add('selected');
|
||||
setActiveSession(button.dataset.sessionId, button.dataset.sessionName);
|
||||
closeSidebar();
|
||||
}
|
||||
|
||||
function setActiveSession(sessionId, name) {
|
||||
stopSupporterTyping(lastTypingSessionId);
|
||||
activeSessionId = sessionId;
|
||||
document.getElementById('active-session-name').textContent = name;
|
||||
document.getElementById('active-session-name').style.color = '';
|
||||
document.getElementById('active-session-meta').textContent = 'Session #' + sessionId.slice(0, 8);
|
||||
document.getElementById('reply-area').classList.add('visible');
|
||||
document.getElementById('reply-input').value = '';
|
||||
hideUserTyping();
|
||||
scrollSupporterMessages();
|
||||
}
|
||||
|
||||
function resetSupporterDashboard() {
|
||||
stopSupporterTyping(lastTypingSessionId);
|
||||
activeSessionId = null;
|
||||
document.getElementById('active-session-name').textContent = 'Select a conversation';
|
||||
document.getElementById('active-session-name').style.color = 'var(--ink-dim)';
|
||||
document.getElementById('active-session-meta').textContent = '';
|
||||
document.getElementById('reply-area').classList.remove('visible');
|
||||
hideUserTyping();
|
||||
closeSidebar();
|
||||
document.getElementById('supporter-messages').innerHTML =
|
||||
'<div class="empty-state"><p>Chat history was cleared.</p><p>New conversations will appear in the chat list.</p></div>';
|
||||
htmx.trigger('#session-list', 'refresh');
|
||||
}
|
||||
|
||||
function scrollSupporterMessages() {
|
||||
var el = document.getElementById('supporter-messages');
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
|
||||
function showUserTyping() {
|
||||
var indicator = document.getElementById('supporter-peer-typing');
|
||||
if (!indicator) return;
|
||||
indicator.classList.add('visible');
|
||||
scrollSupporterMessages();
|
||||
}
|
||||
|
||||
function hideUserTyping() {
|
||||
var indicator = document.getElementById('supporter-peer-typing');
|
||||
if (indicator) indicator.classList.remove('visible');
|
||||
}
|
||||
|
||||
function sendSupporterTypingState(state, sessionId) {
|
||||
sessionId = sessionId || activeSessionId;
|
||||
if (!sessionId || !supporterWs || supporterWs.readyState !== WebSocket.OPEN) return;
|
||||
supporterWs.send('typing:supporter:' + state + ':' + sessionId);
|
||||
}
|
||||
|
||||
function startSupporterTyping() {
|
||||
var input = document.getElementById('reply-input');
|
||||
if (!activeSessionId || !input || !input.value.trim()) {
|
||||
stopSupporterTyping(lastTypingSessionId || activeSessionId);
|
||||
return;
|
||||
}
|
||||
if (supporterTypingActive && lastTypingSessionId === activeSessionId) return;
|
||||
supporterTypingActive = true;
|
||||
lastTypingSessionId = activeSessionId;
|
||||
sendSupporterTypingState('start', activeSessionId);
|
||||
}
|
||||
|
||||
function stopSupporterTyping(sessionId) {
|
||||
sessionId = sessionId || activeSessionId;
|
||||
if (!supporterTypingActive && !sessionId) return;
|
||||
var targetSession = sessionId || lastTypingSessionId;
|
||||
if (targetSession) sendSupporterTypingState('stop', targetSession);
|
||||
supporterTypingActive = false;
|
||||
lastTypingSessionId = null;
|
||||
}
|
||||
|
||||
function handleReplyInput() {
|
||||
var input = document.getElementById('reply-input');
|
||||
if (input && input.value.trim()) startSupporterTyping();
|
||||
else stopSupporterTyping(lastTypingSessionId || activeSessionId);
|
||||
}
|
||||
|
||||
document.addEventListener('htmx:afterSwap', function (e) {
|
||||
if (e.detail.target.id === 'supporter-messages') scrollSupporterMessages();
|
||||
});
|
||||
|
||||
document.getElementById('reply-btn').addEventListener('click', function () {
|
||||
if (!activeSessionId) return;
|
||||
var input = document.getElementById('reply-input');
|
||||
var content = input.value.trim();
|
||||
if (!content) return;
|
||||
|
||||
stopSupporterTyping(activeSessionId);
|
||||
|
||||
fetch('/supporter/send/' + activeSessionId, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: 'content=' + encodeURIComponent(content)
|
||||
})
|
||||
.then(function (r) { return r.text(); })
|
||||
.then(function (html) {
|
||||
var msgs = document.getElementById('supporter-messages');
|
||||
msgs.insertAdjacentHTML('beforeend', html);
|
||||
input.value = '';
|
||||
scrollSupporterMessages();
|
||||
setAppHeight();
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('reply-input').addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') document.getElementById('reply-btn').click();
|
||||
});
|
||||
|
||||
document.getElementById('reply-input').addEventListener('input', handleReplyInput);
|
||||
document.getElementById('reply-input').addEventListener('focus', function () { setTimeout(setAppHeight, 80); });
|
||||
document.getElementById('reply-input').addEventListener('blur', function () { setTimeout(setAppHeight, 80); });
|
||||
|
||||
function connectSupporterWS() {
|
||||
var wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
supporterWs = new WebSocket(wsProtocol + '://' + location.host + '/ws/supporter/dashboard');
|
||||
|
||||
supporterWs.onmessage = function (e) {
|
||||
var data = e.data;
|
||||
if (data === '__reset__') {
|
||||
resetSupporterDashboard();
|
||||
return;
|
||||
}
|
||||
if (data.indexOf('typing:user:start:') === 0) {
|
||||
var sessionId = data.replace('typing:user:start:', '');
|
||||
if (sessionId === activeSessionId) showUserTyping();
|
||||
return;
|
||||
}
|
||||
if (data.indexOf('typing:user:stop:') === 0) {
|
||||
var sessionId2 = data.replace('typing:user:stop:', '');
|
||||
if (sessionId2 === activeSessionId) hideUserTyping();
|
||||
return;
|
||||
}
|
||||
if (data.indexOf('typing:supporter:') === 0) return;
|
||||
|
||||
htmx.trigger('#session-list', 'refresh');
|
||||
|
||||
if (data === activeSessionId) {
|
||||
hideUserTyping();
|
||||
fetch('/supporter/messages/' + activeSessionId)
|
||||
.then(function (r) { return r.text(); })
|
||||
.then(function (html) {
|
||||
document.getElementById('supporter-messages').innerHTML = html;
|
||||
scrollSupporterMessages();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
supporterWs.onopen = function () {
|
||||
if (supporterTypingActive && lastTypingSessionId) sendSupporterTypingState('start', lastTypingSessionId);
|
||||
};
|
||||
supporterWs.onclose = function () { setTimeout(connectSupporterWS, 2000); };
|
||||
}
|
||||
|
||||
connectSupporterWS();
|
||||
Reference in New Issue
Block a user