feat: add MessageList and UserList web components with load balancer and sync module

Implement MessageList custom element with real-time message text updates and typing glow animation, add UserList component with relative time formatting, introduce LoadBalancer class for backend process management, create DatasetWebSocketView for key-value sync over WebSocket, override save method in ChannelMessageService to render HTML templates, and return channel_message object from ChatService instead of boolean.
This commit is contained in:
2025-05-15 11:18:53 +00:00
parent 6250a7a09c
commit 10b0c3448a
12 changed files with 665 additions and 52 deletions
+93 -38
View File
@@ -9,19 +9,19 @@
<section class="chat-area">
<div class="chat-messages">
{% for message in messages %}
{% autoescape false %}
{{ message.html }}
{% endautoescape %}
{% endfor %}
</div>
<message-list class="chat-messages">
{% for message in messages %}
{% autoescape false %}
{{ message.html }}
{% endautoescape %}
{% endfor %}
</message-list>
<div class="chat-input">
<textarea placeholder="Type a message..." rows="2"></textarea>
<textarea list="chat-input-autocomplete-items" placeholder="Type a message..." rows="2" autocomplete="on"></textarea>
<upload-button channel="{{ channel.uid.value }}"></upload-button>
</div>
</section>
{% include "online.html" %}
<script type="module">
import { app } from "/app.js";
import { Schedule } from "/schedule.js";
@@ -30,18 +30,95 @@
function getInputField(){
return document.querySelector("textarea")
}
getInputField().autoComplete = {
"/online": () =>{
showOnlineUsers();
},
"/clear": () => {
document.querySelector(".chat-messages").innerHTML = '';
},
"/live": () =>{
getInputField().liveType = !getInputField().liveType
}
}
function initInputField(textBox) {
textBox.addEventListener('keydown', (e) => {
if(textBox.liveType == undefined){
textBox.liveType = false
}
textBox.addEventListener('keydown',async (e) => {
if(e.key === "ArrowUp"){
const value = findDivAboveText(e.target.value).querySelector('.text')
e.target.value = value.textContent
console.info("HIERR")
return
}
if (e.key === "Tab") {
const message = e.target.value.trim();
if (!message) {
return
}
let autoCompleteHandler = null;
Object.keys(e.target.autoComplete).forEach((key)=>{
if(key.startsWith(message)){
if(autoCompleteHandler){
return
}
autoCompleteHandler = key
}
})
if(autoCompleteHandler){
e.preventDefault();
e.target.value = autoCompleteHandler;
return
}
}
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
const message = e.target.value.trim();
if (message) {
app.rpc.sendMessage(channelUid, message);
e.target.value = '';
if (!message) {
return
}
let autoCompleteHandler = e.target.autoComplete[message]
if(autoCompleteHandler){
const value = message;
e.target.value = '';
autoCompleteHandler(value)
return
}
e.target.value = '';
if(textBox.liveType && textBox.lastMessageUid && textBox.lastMessageUid != '?'){
app.rpc.updateMessageText(textBox.lastMessageUid, message)
textBox.lastMessageUid = null
return
}
const messageResponse = await app.rpc.sendMessage(channelUid, message);
}else{
app.rpc.set_typing(channelUid)
if(textBox.liveType){
if(e.target.value[0] == "/"){
return
}
if(!textBox.lastMessageUid){
textBox.lastMessageUid = '?'
app.rpc.sendMessage(channelUid, e.target.value).then((messageResponse)=>{
textBox.lastMessageUid = messageResponse
})
}
if(textBox.lastMessageUid == '?'){
return;
}
app.rpc.updateMessageText(textBox.lastMessageUid, e.target.value)
}
app.rpc.set_typing(channelUid)
}
});
document.querySelector("upload-button").addEventListener("upload",function(e){
@@ -81,29 +158,7 @@
}
});
function triggerGlow(uid) {
document.querySelectorAll(".avatar").forEach((el)=>{
const div = el.closest('a');
if(el.href.indexOf(uid)!=-1){
el.classList.add('glow')
let originalColor = el.style.backgroundColor
//console.error(originalColor)
//el.style.backgroundColor = 'black'
setTimeout(()=>{
// el.style.backgroundColor = originalColor
// console.error(el.style.backgroundColor)
el.classList.remove('glow')
},1200)
}
})
}
app.ws.addEventListener("set_typing",(data)=>{
triggerGlow(data.data.user_uid)
})
const chatInput = document.querySelector(".chat-area")
chatInput.addEventListener("drop", async (e) => {