Properly sort messages and add fetch previous messages ability

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-04-27 14:12:08 +02:00
parent d59bb75dce
commit d617dd77c1
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
4 changed files with 78 additions and 42 deletions

View File

@ -63,7 +63,7 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
case 'send_message': case 'send_message':
await self.receive_message(content) await self.receive_message(content)
case 'fetch_messages': case 'fetch_messages':
await self.fetch_messages(content['channel_id']) await self.fetch_messages(**content)
case unknown: case unknown:
print("Unknown message type:", unknown) print("Unknown message type:", unknown)
@ -109,17 +109,19 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
'content': message.content, 'content': message.content,
}) })
async def fetch_messages(self, channel_id: int, offset: int = 0, limit: int = 50) -> None: async def fetch_messages(self, channel_id: int, offset: int = 0, limit: int = 50, **_kwargs) -> None:
channel = await Channel.objects.aget(id=channel_id) channel = await Channel.objects.aget(id=channel_id)
read_channels = await Channel.get_accessible_channels(self.scope['user'], 'read') read_channels = await Channel.get_accessible_channels(self.scope['user'], 'read')
if not await read_channels.acontains(channel): if not await read_channels.acontains(channel):
return return
messages = Message.objects.filter(channel=channel).order_by('created_at')[offset:offset + limit].all() limit = min(limit, 200) # Fetch only maximum 200 messages at the time
messages = Message.objects.filter(channel=channel).order_by('-created_at')[offset:offset + limit].all()
await self.send_json({ await self.send_json({
'type': 'fetch_messages', 'type': 'fetch_messages',
'channel_id': channel_id, 'channel_id': channel_id,
'messages': [ 'messages': list(reversed([
{ {
'id': message.id, 'id': message.id,
'timestamp': message.created_at.isoformat(), 'timestamp': message.created_at.isoformat(),
@ -127,7 +129,7 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
'content': message.content, 'content': message.content,
} }
async for message in messages async for message in messages
] ]))
}) })
async def chat_send_message(self, message) -> None: async def chat_send_message(self, message) -> None:

View File

@ -4,6 +4,8 @@
await Notification.requestPermission() await Notification.requestPermission()
})() })()
const MAX_MESSAGES = 50
let channels = {} let channels = {}
let messages = {} let messages = {}
let selected_channel_id = null let selected_channel_id = null
@ -61,12 +63,9 @@ function setChannels(new_channels) {
for (let channel of new_channels) { for (let channel of new_channels) {
channels[channel['id']] = channel channels[channel['id']] = channel
if (!messages[channel['id']]) if (!messages[channel['id']])
messages[channel['id']] = [] messages[channel['id']] = new Map()
socket.send(JSON.stringify({ fetchMessages(channel['id'])
'type': 'fetch_messages',
'channel_id': channel['id'],
}))
} }
if (new_channels && (!selected_channel_id || !channels[selected_channel_id])) if (new_channels && (!selected_channel_id || !channels[selected_channel_id]))
@ -78,16 +77,35 @@ function receiveMessage(message) {
redrawMessages() redrawMessages()
} }
function fetchMessages(data) { function fetchMessages(channel_id, offset = 0, limit = MAX_MESSAGES) {
socket.send(JSON.stringify({
'type': 'fetch_messages',
'channel_id': channel_id,
'offset': offset,
'limit': limit,
}))
}
function fetchPreviousMessages() {
let channel_id = selected_channel_id
let offset = messages[channel_id].size
fetchMessages(channel_id, offset, MAX_MESSAGES)
}
function receiveFetchedMessages(data) {
let channel_id = data['channel_id'] let channel_id = data['channel_id']
let new_messages = data['messages'] let new_messages = data['messages']
if (!messages[channel_id]) if (!messages[channel_id])
messages[channel_id] = [] messages[channel_id] = new Map()
for (let message of new_messages) { for (let message of new_messages)
messages[channel_id].push(message) messages[channel_id].set(message['id'], message)
}
// Sort messages by timestamp
messages[channel_id] = new Map([...messages[channel_id].values()]
.sort((a, b) => new Date(a['timestamp']) - new Date(b['timestamp']))
.map(message => [message['id'], message]))
redrawMessages() redrawMessages()
} }
@ -99,7 +117,7 @@ function redrawMessages() {
let lastMessage = null let lastMessage = null
let lastContentDiv = null let lastContentDiv = null
for (let message of messages[selected_channel_id]) { for (let message of messages[selected_channel_id].values()) {
if (lastMessage && lastMessage['author'] === message['author']) { if (lastMessage && lastMessage['author'] === message['author']) {
let lastTimestamp = new Date(lastMessage['timestamp']) let lastTimestamp = new Date(lastMessage['timestamp'])
let newTimestamp = new Date(message['timestamp']) let newTimestamp = new Date(message['timestamp'])
@ -138,6 +156,12 @@ function redrawMessages() {
lastMessage = message lastMessage = message
lastContentDiv = contentDiv lastContentDiv = contentDiv
} }
let fetchMoreButton = document.getElementById('fetch-previous-messages')
if (!messages[selected_channel_id] || messages[selected_channel_id].size % MAX_MESSAGES !== 0)
fetchMoreButton.classList.add('d-none')
else
fetchMoreButton.classList.remove('d-none')
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
@ -154,7 +178,7 @@ document.addEventListener('DOMContentLoaded', () => {
receiveMessage(data) receiveMessage(data)
break break
case 'fetch_messages': case 'fetch_messages':
fetchMessages(data) receiveFetchedMessages(data)
break break
default: default:
console.log(data) console.log(data)

View File

@ -35,6 +35,12 @@
</h3> </h3>
</div> </div>
<div class="card-body overflow-y-scroll mw-100 h-100 flex-grow-0" id="chat-messages"> <div class="card-body overflow-y-scroll mw-100 h-100 flex-grow-0" id="chat-messages">
<div class="text-center d-none" id="fetch-previous-messages">
<a href="#" class="nav-link" onclick="event.preventDefault(); fetchPreviousMessages()">
{% trans "Fetch previous messages…" %}
</a>
<hr>
</div>
<ul class="list-group list-group-flush" id="message-list"></ul> <ul class="list-group list-group-flush" id="message-list"></ul>
</div> </div>
<div class="card-footer mt-auto"> <div class="card-footer mt-auto">

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TFJM\n" "Project-Id-Version: TFJM\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-27 11:02+0200\n" "POT-Creation-Date: 2024-04-27 14:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n" "Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -21,20 +21,20 @@ msgstr ""
msgid "API" msgid "API"
msgstr "API" msgstr "API"
#: chat/models.py:17 participation/models.py:35 participation/models.py:263 #: chat/models.py:18 participation/models.py:35 participation/models.py:263
#: participation/tables.py:18 participation/tables.py:34 #: participation/tables.py:18 participation/tables.py:34
msgid "name" msgid "name"
msgstr "nom" msgstr "nom"
#: chat/models.py:22 #: chat/models.py:23
msgid "read permission" msgid "read permission"
msgstr "permission de lecture" msgstr "permission de lecture"
#: chat/models.py:28 #: chat/models.py:29
msgid "write permission" msgid "write permission"
msgstr "permission d'écriture" msgstr "permission d'écriture"
#: chat/models.py:38 draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 #: chat/models.py:39 draw/admin.py:53 draw/admin.py:71 draw/admin.py:88
#: draw/models.py:26 participation/admin.py:79 participation/admin.py:140 #: draw/models.py:26 participation/admin.py:79 participation/admin.py:140
#: participation/admin.py:171 participation/models.py:693 #: participation/admin.py:171 participation/models.py:693
#: participation/models.py:717 participation/models.py:935 #: participation/models.py:717 participation/models.py:935
@ -43,7 +43,7 @@ msgstr "permission d'écriture"
msgid "tournament" msgid "tournament"
msgstr "tournoi" msgstr "tournoi"
#: chat/models.py:40 #: chat/models.py:41
msgid "" msgid ""
"For a permission that concerns a tournament, indicates what is the concerned " "For a permission that concerns a tournament, indicates what is the concerned "
"tournament." "tournament."
@ -51,21 +51,21 @@ msgstr ""
"Pour une permission qui concerne un tournoi, indique quel est le tournoi " "Pour une permission qui concerne un tournoi, indique quel est le tournoi "
"concerné." "concerné."
#: chat/models.py:49 draw/models.py:429 draw/models.py:456 #: chat/models.py:50 draw/models.py:429 draw/models.py:456
#: participation/admin.py:136 participation/admin.py:155 #: participation/admin.py:136 participation/admin.py:155
#: participation/models.py:1434 participation/models.py:1443 #: participation/models.py:1434 participation/models.py:1443
#: participation/tables.py:84 #: participation/tables.py:84
msgid "pool" msgid "pool"
msgstr "poule" msgstr "poule"
#: chat/models.py:51 #: chat/models.py:52
msgid "" msgid ""
"For a permission that concerns a pool, indicates what is the concerned pool." "For a permission that concerns a pool, indicates what is the concerned pool."
msgstr "" msgstr ""
"Pour une permission qui concerne une poule, indique quelle est la poule " "Pour une permission qui concerne une poule, indique quelle est la poule "
"concernée." "concernée."
#: chat/models.py:60 draw/templates/draw/tournament_content.html:277 #: chat/models.py:61 draw/templates/draw/tournament_content.html:277
#: participation/admin.py:167 participation/models.py:252 #: participation/admin.py:167 participation/models.py:252
#: participation/models.py:708 #: participation/models.py:708
#: participation/templates/participation/tournament_harmonize.html:15 #: participation/templates/participation/tournament_harmonize.html:15
@ -75,18 +75,18 @@ msgstr ""
msgid "team" msgid "team"
msgstr "équipe" msgstr "équipe"
#: chat/models.py:62 #: chat/models.py:63
msgid "" msgid ""
"For a permission that concerns a team, indicates what is the concerned team." "For a permission that concerns a team, indicates what is the concerned team."
msgstr "" msgstr ""
"Pour une permission qui concerne une équipe, indique quelle est l'équipe " "Pour une permission qui concerne une équipe, indique quelle est l'équipe "
"concernée." "concernée."
#: chat/models.py:66 #: chat/models.py:67
msgid "private" msgid "private"
msgstr "privé" msgstr "privé"
#: chat/models.py:68 #: chat/models.py:69
msgid "" msgid ""
"If checked, only users who have been explicitly added to the channel will be " "If checked, only users who have been explicitly added to the channel will be "
"able to access it." "able to access it."
@ -94,11 +94,11 @@ msgstr ""
"Si sélectionné, seul⋅es les utilisateur⋅rices qui ont été explicitement " "Si sélectionné, seul⋅es les utilisateur⋅rices qui ont été explicitement "
"ajouté⋅es au canal pourront y accéder." "ajouté⋅es au canal pourront y accéder."
#: chat/models.py:73 #: chat/models.py:74
msgid "invited users" msgid "invited users"
msgstr "Utilisateur⋅rices invité" msgstr "Utilisateur⋅rices invité"
#: chat/models.py:76 #: chat/models.py:77
msgid "" msgid ""
"Extra users who have been invited to the channel, in addition to the " "Extra users who have been invited to the channel, in addition to the "
"permitted group of the channel." "permitted group of the channel."
@ -106,52 +106,56 @@ msgstr ""
"Utilisateur⋅rices supplémentaires qui ont été invité⋅es au canal, en plus du " "Utilisateur⋅rices supplémentaires qui ont été invité⋅es au canal, en plus du "
"groupe autorisé du canal." "groupe autorisé du canal."
#: chat/models.py:81 #: chat/models.py:82
#, python-brace-format #, python-brace-format
msgid "Channel {name}" msgid "Channel {name}"
msgstr "Canal {name}" msgstr "Canal {name}"
#: chat/models.py:150 chat/models.py:159 #: chat/models.py:148 chat/models.py:157
msgid "channel" msgid "channel"
msgstr "canal" msgstr "canal"
#: chat/models.py:151 #: chat/models.py:149
msgid "channels" msgid "channels"
msgstr "canaux" msgstr "canaux"
#: chat/models.py:165 #: chat/models.py:163
msgid "author" msgid "author"
msgstr "auteur⋅rice" msgstr "auteur⋅rice"
#: chat/models.py:172 #: chat/models.py:170
msgid "created at" msgid "created at"
msgstr "créé le" msgstr "créé le"
#: chat/models.py:177 #: chat/models.py:175
msgid "updated at" msgid "updated at"
msgstr "modifié le" msgstr "modifié le"
#: chat/models.py:182 #: chat/models.py:180
msgid "content" msgid "content"
msgstr "contenu" msgstr "contenu"
#: chat/models.py:186 #: chat/models.py:243
msgid "message" msgid "message"
msgstr "message" msgstr "message"
#: chat/models.py:187 #: chat/models.py:244
msgid "messages" msgid "messages"
msgstr "messages" msgstr "messages"
#: chat/templates/chat/chat.html:8 #: chat/templates/chat/chat.html:8
msgid "JavaScript must be enabled on your browser to access chat." msgid "JavaScript must be enabled on your browser to access chat."
msgstr "" msgstr "JavaScript doit être activé sur votre navigateur pour accéder au chat."
#: chat/templates/chat/chat.html:12 #: chat/templates/chat/chat.html:12
msgid "Chat channels" msgid "Chat channels"
msgstr "Canaux de chat" msgstr "Canaux de chat"
#: chat/templates/chat/chat.html:43 #: chat/templates/chat/chat.html:40
msgid "Fetch previous messages…"
msgstr "Récupérer les messages précédents…"
#: chat/templates/chat/chat.html:52
msgid "Send message…" msgid "Send message…"
msgstr "Envoyer un message…" msgstr "Envoyer un message…"