This commit is contained in:
2026-08-08 14:57:04 +02:00
parent 0d8c86af16
commit d1f9234457
5 changed files with 56 additions and 38 deletions
+41 -2
View File
@@ -17,7 +17,7 @@ const userStore = useUserStore();
const {renderMarkdown} = useMarkdown() const {renderMarkdown} = useMarkdown()
const messageContainer = ref<HTMLElement | null>(null); const messageContainer = ref<HTMLElement | null>(null);
const {messages, loading, loadingBefore, loadingAfter} = storeToRefs(messageStore); const {messages, loading, loadingBefore, loadingAfter, hasMoreAfter, isAtBottom} = storeToRefs(messageStore);
const newMessage = ref(''); const newMessage = ref('');
const SCROLL_LOAD_THRESHOLD = 120; const SCROLL_LOAD_THRESHOLD = 120;
@@ -26,6 +26,10 @@ const paginationLock = ref<'before' | 'after' | null>(null);
const paginationLockScrollTop = ref(0); const paginationLockScrollTop = ref(0);
const lastScrollTop = ref(0); const lastScrollTop = ref(0);
const showRecentMessagesButton = computed(() =>
!loading.value && (hasMoreAfter.value || !isAtBottom.value),
);
interface ScrollAnchor { interface ScrollAnchor {
id: string; id: string;
top: number; top: number;
@@ -36,6 +40,8 @@ const scrollToBottom = async () => {
if (messageContainer.value) { if (messageContainer.value) {
messageContainer.value.scrollTop = messageContainer.value.scrollHeight; messageContainer.value.scrollTop = messageContainer.value.scrollHeight;
messageStore.setAtBottom(true); messageStore.setAtBottom(true);
paginationLock.value = null;
lastScrollTop.value = messageContainer.value.scrollTop;
} }
}; };
@@ -153,6 +159,12 @@ const sendMessage = async () => {
} }
}; };
const returnToRecentMessages = async () => {
paginationLock.value = null;
await messageStore.fetchMessages(channelId.value);
await scrollToBottom();
};
// Only explicit initial loads and realtime messages received while at the // Only explicit initial loads and realtime messages received while at the
// bottom request an automatic scroll. Pagination restores its own anchor. // bottom request an automatic scroll. Pagination restores its own anchor.
watch(messages, async () => { watch(messages, async () => {
@@ -169,7 +181,7 @@ watch(channelId, async (newChannelId) => {
</script> </script>
<template> <template>
<v-container class="pa-0 fill-height d-flex flex-column" fluid> <v-container class="pa-0 fill-height d-flex flex-column channel-layout" fluid>
<div <div
ref="messageContainer" ref="messageContainer"
class="flex-grow-1 overflow-y-auto w-100 message-container" class="flex-grow-1 overflow-y-auto w-100 message-container"
@@ -211,6 +223,22 @@ watch(channelId, async (newChannelId) => {
<v-progress-linear v-if="loadingAfter" color="primary" indeterminate /> <v-progress-linear v-if="loadingAfter" color="primary" indeterminate />
</div> </div>
<div v-if="showRecentMessagesButton" class="recent-messages-button">
<v-tooltip location="top" text="Revenir aux messages récents">
<template #activator="{ props: tooltipProps }">
<v-btn
v-bind="tooltipProps"
aria-label="Revenir aux messages récents"
color="primary"
elevation="4"
icon="mdi-arrow-down-bold"
:loading="loading"
@click="returnToRecentMessages"
/>
</template>
</v-tooltip>
</div>
<v-sheet class="pa-4 flex-shrink-0" width="100%"> <v-sheet class="pa-4 flex-shrink-0" width="100%">
<v-textarea <v-textarea
v-model="newMessage" v-model="newMessage"
@@ -244,6 +272,17 @@ watch(channelId, async (newChannelId) => {
height: 0; height: 0;
} }
.channel-layout {
position: relative;
}
.recent-messages-button {
position: absolute;
right: 16px;
bottom: 92px;
z-index: 2;
}
.markdown-content :deep(p) { .markdown-content :deep(p) {
margin-bottom: 0; margin-bottom: 0;
} }
+1 -5
View File
@@ -1,16 +1,12 @@
pub use sea_orm_migration::prelude::*; pub use sea_orm_migration::prelude::*;
mod m20220101_000001_create_table; mod m20220101_000001_create_table;
mod m20260808_000002_add_message_channel_id_id_index;
pub struct Migrator; pub struct Migrator;
#[async_trait::async_trait] #[async_trait::async_trait]
impl MigratorTrait for Migrator { impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> { fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![ vec![Box::new(m20220101_000001_create_table::Migration)]
Box::new(m20220101_000001_create_table::Migration),
Box::new(m20260808_000002_add_message_channel_id_id_index::Migration),
]
} }
} }
@@ -505,6 +505,12 @@ impl MigrationTrait for Migration {
.to(Alias::new("message"), Alias::new("id")) .to(Alias::new("message"), Alias::new("id"))
.on_delete(ForeignKeyAction::SetNull), .on_delete(ForeignKeyAction::SetNull),
) )
.index(
Index::create()
.name("idx_message_channel_id_id")
.col(Alias::new("channel_id"))
.col(Alias::new("id")),
)
.to_owned(), .to_owned(),
) )
.await?; .await?;
@@ -1,31 +0,0 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_index(
Index::create()
.name("idx_message_channel_id_id")
.table(Alias::new("message"))
.col(Alias::new("channel_id"))
.col(Alias::new("id"))
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_index(
Index::drop()
.name("idx_message_channel_id_id")
.table(Alias::new("message"))
.to_owned(),
)
.await
}
}
+8
View File
@@ -1,6 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Generate test messages directly in the project's SQLite database.""" """Generate test messages directly in the project's SQLite database."""
# python3 scripts/generate_messages.py \
# --db oxspeak.db \
# --channel-id 672e7757-b7df-401b-8e47-8c62e1fb9d7d \
# --user-id d327a80b-83d4-4a53-9c0b-140f60cc0caa \
# --count 1000 \
# --min-words 10 \
# --max-words 500
from __future__ import annotations from __future__ import annotations
import argparse import argparse