render markdown message in community

This commit is contained in:
2026-09-25 09:24:30 +07:00
parent 2315a6ffdf
commit 93d9360cb7
9 changed files with 573 additions and 520 deletions
+39 -21
View File
@@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use anyhow::Result;
@@ -19,6 +19,7 @@ use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
use ui::dock::{Panel, PanelEvent};
use ui::input::{InputEvent, Textarea, TextareaState};
use ui::markdown::RenderedText;
use ui::message::WelcomeMessage;
use ui::notification::Notification;
use ui::scroll::Scrollbar;
@@ -98,6 +99,8 @@ pub struct CommunityPanel {
channel: Option<ChannelId>,
/// The selected channel's timeline (oldest first)
rows: Vec<ChatMessage>,
/// Rendered markdown content, keyed by message id and dropped when a row changes
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
/// Whether the store holds rows older than `rows`
has_more: bool,
/// A round or a page read is in flight
@@ -163,34 +166,32 @@ impl CommunityPanel {
},
));
let panel = Self {
cx.defer_in(window, |this, window, cx| {
this.list_state.set_follow_mode(FollowMode::Tail);
this.list_state.set_scroll_handler(cx.listener(
|this, event: &ListScrollEvent, window, cx| {
if event.visible_range.start <= LOAD_OLDER_THRESHOLD {
this.load_older(window, cx);
}
},
));
this.load(window, cx);
});
Self {
id,
focus_handle: cx.focus_handle(),
community: community.downgrade(),
channel,
rows: Vec::new(),
rendered_texts_by_id: BTreeMap::new(),
has_more: false,
loading: false,
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.)),
input,
tasks: smallvec![],
_subscriptions: subscriptions,
};
panel.list_state.set_follow_mode(FollowMode::Tail);
panel.list_state.set_scroll_handler(cx.listener(
|this, event: &ListScrollEvent, window, cx| {
if event.visible_range.start <= LOAD_OLDER_THRESHOLD {
this.load_older(window, cx);
}
},
));
cx.defer_in(window, |this, window, cx| {
this.load(window, cx);
});
panel
}
}
/// The channel to show, following the community's selection.
@@ -204,6 +205,7 @@ impl CommunityPanel {
if channel != self.channel {
self.channel = channel;
self.rows.clear();
self.rendered_texts_by_id.clear();
self.has_more = false;
self.loading = false;
self.list_state.reset(2);
@@ -493,6 +495,7 @@ impl CommunityPanel {
if !connected {
self.rows = messages;
self.rendered_texts_by_id.clear();
self.has_more = has_more;
self.list_state.reset(self.item_count());
cx.notify();
@@ -527,7 +530,13 @@ impl CommunityPanel {
for message in messages {
match shown.get(&message.id).copied() {
Some(ix) => self.rows[ix] = message,
Some(ix) => {
// Drop the cached render when the text changes, so edits re-parse.
if self.rows[ix].content != message.content {
self.rendered_texts_by_id.remove(&message.id);
}
self.rows[ix] = message;
}
None => fresh.push(message),
}
}
@@ -690,7 +699,7 @@ impl CommunityPanel {
fn render_message(
&mut self,
ix: usize,
_window: &mut Window,
window: &mut Window,
cx: &mut Context<Self>,
) -> AnyElement {
if ix == 0 {
@@ -708,7 +717,16 @@ impl CommunityPanel {
let show_author = self.opens_run(ix - 2);
message::render(ix, message, show_author, cx)
let content = if message.deleted {
message::deleted(cx)
} else {
self.rendered_texts_by_id
.entry(message.id)
.or_insert_with(|| RenderedText::new(&message.content, &[], true))
.element(ix.into(), window, cx)
};
message::render(ix, message, content, show_author, cx)
}
fn render_composer(&self, cx: &mut Context<Self>) -> impl IntoElement {
+12 -11
View File
@@ -12,7 +12,13 @@ use ui::avatar::Avatar;
use ui::h_flex;
use ui::message::MessageRow;
pub(crate) fn render(ix: usize, message: &ChatMessage, show_author: bool, cx: &App) -> AnyElement {
pub(crate) fn render(
ix: usize,
message: &ChatMessage,
content: AnyElement,
show_author: bool,
cx: &App,
) -> AnyElement {
let persons = PersonRegistry::global(cx);
let author = persons.read(cx).get(&message.author, cx);
let hide_avatar = AppSettings::get_hide_avatar(cx);
@@ -30,23 +36,18 @@ pub(crate) fn render(ix: usize, message: &ChatMessage, show_author: bool, cx: &A
.when(message.edited_at.is_some(), |this| {
this.header_extra(div().child("(edited)"))
})
.child(content(message, cx))
.child(content)
.when(!message.reactions.is_empty(), |this| {
this.child(reactions(message, cx))
})
.into_any_element()
}
fn content(message: &ChatMessage, cx: &App) -> AnyElement {
if message.deleted {
return div()
.text_color(cx.theme().text_danger)
.child("Message deleted")
.into_any_element();
}
/// The placeholder shown in place of the body of a deleted message.
pub(crate) fn deleted(cx: &App) -> AnyElement {
div()
.child(SharedString::from(&message.content))
.text_color(cx.theme().text_danger)
.child("Message deleted")
.into_any_element()
}