modularize frontend and implement new css and themes
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
var ws = null;
|
||||
var userTypingActive = 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 scrollMessages() {
|
||||
var el = document.getElementById('messages');
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
|
||||
function showSupporterTyping() {
|
||||
var indicator = document.getElementById('peer-typing');
|
||||
if (!indicator) return;
|
||||
indicator.classList.add('visible');
|
||||
scrollMessages();
|
||||
}
|
||||
|
||||
function hideSupporterTyping() {
|
||||
var indicator = document.getElementById('peer-typing');
|
||||
if (indicator) indicator.classList.remove('visible');
|
||||
}
|
||||
|
||||
function sendUserTypingState(state) {
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN || !window.__sessionId) return;
|
||||
ws.send('typing:user:' + state + ':' + window.__sessionId);
|
||||
}
|
||||
|
||||
function startUserTyping() {
|
||||
if (userTypingActive) return;
|
||||
userTypingActive = true;
|
||||
sendUserTypingState('start');
|
||||
}
|
||||
|
||||
function stopUserTyping() {
|
||||
if (!userTypingActive) return;
|
||||
sendUserTypingState('stop');
|
||||
userTypingActive = false;
|
||||
}
|
||||
|
||||
function handleUserTypingInput() {
|
||||
var input = document.getElementById('chat-input');
|
||||
if (input && input.value.trim()) startUserTyping();
|
||||
else stopUserTyping();
|
||||
}
|
||||
|
||||
document.addEventListener('htmx:afterSwap', function (e) {
|
||||
if (e.detail.target.id === 'messages') scrollMessages();
|
||||
});
|
||||
|
||||
document.addEventListener('htmx:beforeRequest', function (e) {
|
||||
if (e.detail.elt && e.detail.elt.id === 'chat-form') stopUserTyping();
|
||||
});
|
||||
|
||||
function resetChat() {
|
||||
stopUserTyping();
|
||||
var oldWs = ws;
|
||||
ws = null;
|
||||
window.__sessionId = null;
|
||||
userTypingActive = false;
|
||||
if (oldWs) oldWs.close();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function initChat(sessionId) {
|
||||
window.__sessionId = sessionId;
|
||||
document.getElementById('chat-session-id').textContent = 'Session #' + sessionId.slice(0, 8);
|
||||
|
||||
var wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
ws = new WebSocket(wsProtocol + '://' + location.host + '/ws/user/' + sessionId);
|
||||
|
||||
ws.onmessage = function (message) {
|
||||
if (message.data === '__reset__') {
|
||||
resetChat();
|
||||
return;
|
||||
}
|
||||
if (message.data.startsWith('typing:supporter:start:')) {
|
||||
showSupporterTyping();
|
||||
return;
|
||||
}
|
||||
if (message.data.startsWith('typing:supporter:stop:')) {
|
||||
hideSupporterTyping();
|
||||
return;
|
||||
}
|
||||
if (message.data.startsWith('typing:user:')) return;
|
||||
hideSupporterTyping();
|
||||
htmx.trigger('#messages', 'refresh');
|
||||
};
|
||||
|
||||
ws.onopen = function () {
|
||||
htmx.trigger('#messages', 'refresh');
|
||||
};
|
||||
|
||||
ws.onerror = function () {
|
||||
console.warn('WebSocket error; polling will continue.');
|
||||
};
|
||||
|
||||
var chatInput = document.getElementById('chat-input');
|
||||
if (chatInput) {
|
||||
chatInput.addEventListener('focus', function () { setTimeout(setAppHeight, 80); });
|
||||
chatInput.addEventListener('blur', function () { setTimeout(setAppHeight, 80); });
|
||||
}
|
||||
setAppHeight();
|
||||
}
|
||||
Reference in New Issue
Block a user