Implement chat history and multiline messages
This commit is contained in:
parent
63b04208d0
commit
9006f53e4d
6 changed files with 361 additions and 79 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -477,6 +477,7 @@ dependencies = [
|
|||
name = "cri"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"color-eyre",
|
||||
"futures",
|
||||
"iced",
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.31"
|
||||
color-eyre = "0.6.2"
|
||||
futures = "0.3.29"
|
||||
iced = { version = "0.10.0", features = ["tokio"] }
|
||||
|
|
|
@ -8,7 +8,16 @@ use tokio::{
|
|||
|
||||
pub async fn connect() -> Result<irc::client::Client> {
|
||||
let client = irc::client::Client::new("config.toml").await?;
|
||||
client.send_cap_req(&[Capability::EchoMessage, Capability::ServerTime])?;
|
||||
client.send_cap_req(&[
|
||||
Capability::AccountTag,
|
||||
Capability::Batch,
|
||||
Capability::EchoMessage,
|
||||
Capability::ServerTime,
|
||||
Capability::Custom("message-tags"),
|
||||
Capability::Custom("draft/chathistory"),
|
||||
Capability::Custom("draft/event-playback"),
|
||||
Capability::Custom("draft/multiline"),
|
||||
])?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
pub enum IrcMessage {
|
||||
use chrono::{DateTime, Local};
|
||||
|
||||
pub struct IrcMessage {
|
||||
pub detail: MessageDetail,
|
||||
pub message_id: Option<String>,
|
||||
pub timestamp: DateTime<Local>,
|
||||
}
|
||||
|
||||
pub enum MessageDetail {
|
||||
Join {
|
||||
nickname: String,
|
||||
},
|
||||
|
|
101
src/main.rs
101
src/main.rs
|
@ -2,7 +2,7 @@ mod irc_handler;
|
|||
mod irc_message;
|
||||
mod message_log;
|
||||
|
||||
use crate::{irc_message::IrcMessage, message_log::MessageLog};
|
||||
use chrono::{DateTime, Local};
|
||||
use color_eyre::eyre::Result;
|
||||
use iced::{
|
||||
alignment::{Horizontal, Vertical},
|
||||
|
@ -11,9 +11,11 @@ use iced::{
|
|||
widget::{column, container, mouse_area, row, text, text_input},
|
||||
Application, Background, Color, Element, Length, Settings,
|
||||
};
|
||||
use irc::proto::{Command as IrcCommand, Response};
|
||||
use irc::proto::{message::Tag, Command as IrcCommand, Response};
|
||||
use irc_message::MessageDetail;
|
||||
use message_log::MessageLog;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::RefCell;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||
|
||||
static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
|
||||
|
@ -49,6 +51,7 @@ pub enum UiMessage {
|
|||
InputChanged(String),
|
||||
InputSubmitted,
|
||||
HandleChannelPress(Option<String>),
|
||||
None,
|
||||
}
|
||||
|
||||
struct Cri {
|
||||
|
@ -170,23 +173,80 @@ impl Application for Cri {
|
|||
.unwrap_or(&self.nickname)
|
||||
.to_string();
|
||||
|
||||
let tags_map = message
|
||||
.tags
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|Tag(k, v)| (k.clone(), v.clone()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let timestamp = tags_map
|
||||
.get(&"time".to_string())
|
||||
.cloned()
|
||||
.flatten()
|
||||
.and_then(|time| DateTime::parse_from_rfc3339(&time).ok())
|
||||
.map(DateTime::into)
|
||||
.unwrap_or_else(Local::now);
|
||||
|
||||
let message_id = tags_map.get(&"msgid".to_string()).cloned().flatten();
|
||||
let message_id = message_id.as_deref();
|
||||
|
||||
match &message.command {
|
||||
IrcCommand::JOIN(chanlist, _, _) => {
|
||||
self.message_log.on_join(chanlist, &source_nickname);
|
||||
let already_joined = self.message_log.has_channel(chanlist);
|
||||
self.message_log.on_join(
|
||||
chanlist,
|
||||
&source_nickname,
|
||||
×tamp,
|
||||
message_id,
|
||||
);
|
||||
|
||||
if !already_joined && source_nickname == self.nickname {
|
||||
self.input_tx
|
||||
.borrow()
|
||||
.send(
|
||||
IrcCommand::Raw(
|
||||
"CHATHISTORY".to_string(),
|
||||
vec![
|
||||
"LATEST".to_string(),
|
||||
chanlist.clone(),
|
||||
"*".to_string(),
|
||||
100.to_string(),
|
||||
],
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
IrcCommand::PART(chanlist, comment) => {
|
||||
self.message_log
|
||||
.on_part(chanlist, &source_nickname, comment.as_deref());
|
||||
self.message_log.on_part(
|
||||
chanlist,
|
||||
&source_nickname,
|
||||
comment.as_deref(),
|
||||
×tamp,
|
||||
message_id,
|
||||
);
|
||||
}
|
||||
|
||||
IrcCommand::NICK(new) => {
|
||||
self.message_log.on_nick(&source_nickname, new);
|
||||
self.message_log
|
||||
.on_nick(&source_nickname, new, ×tamp, message_id);
|
||||
}
|
||||
|
||||
IrcCommand::QUIT(comment) => {
|
||||
self.message_log
|
||||
.on_quit(&source_nickname, comment.as_deref());
|
||||
self.message_log.on_quit(
|
||||
&source_nickname,
|
||||
comment.as_deref(),
|
||||
×tamp,
|
||||
message_id,
|
||||
);
|
||||
}
|
||||
|
||||
IrcCommand::TOPIC(channel, topic) => {
|
||||
self.message_log.on_topic(channel, topic.clone());
|
||||
}
|
||||
|
||||
IrcCommand::PRIVMSG(msgtarget, content)
|
||||
|
@ -197,11 +257,22 @@ impl Application for Cri {
|
|||
&channel,
|
||||
&source_nickname,
|
||||
content,
|
||||
message_id,
|
||||
×tamp,
|
||||
);
|
||||
}
|
||||
IrcCommand::Response(Response::RPL_WELCOME, args) => {
|
||||
self.nickname = args[0].clone()
|
||||
}
|
||||
IrcCommand::BATCH(tag, subcommand, params) => {
|
||||
self.message_log.on_batch(
|
||||
&self.nickname,
|
||||
tag,
|
||||
subcommand.as_ref(),
|
||||
params.as_ref(),
|
||||
×tamp,
|
||||
);
|
||||
}
|
||||
_ => self.message_log.on_other(&message.to_string()),
|
||||
}
|
||||
}
|
||||
|
@ -218,6 +289,7 @@ impl Application for Cri {
|
|||
self.input_value.clear();
|
||||
}
|
||||
UiMessage::HandleChannelPress(channel) => self.message_log.set_active(channel),
|
||||
UiMessage::None => (),
|
||||
}
|
||||
iced::Command::none()
|
||||
}
|
||||
|
@ -227,8 +299,11 @@ impl Application for Cri {
|
|||
"irc message",
|
||||
self.message_rx.take(),
|
||||
move |mut receiver| async move {
|
||||
let message = receiver.as_mut().unwrap().recv().await.unwrap();
|
||||
if let Some(message) = receiver.as_mut().unwrap().recv().await {
|
||||
(UiMessage::IrcMessageReceived(Box::new(message)), receiver)
|
||||
} else {
|
||||
(UiMessage::None, receiver)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -267,8 +342,8 @@ impl Application for Cri {
|
|||
.iter()
|
||||
.rev()
|
||||
.find_map(|m| -> Option<Element<_, _>> {
|
||||
match m {
|
||||
IrcMessage::Privmsg { nickname, message } => Some(
|
||||
match &m.detail {
|
||||
MessageDetail::Privmsg { nickname, message } => Some(
|
||||
row![
|
||||
text(format!("{nickname}: "))
|
||||
.style(nickname_color)
|
||||
|
@ -277,7 +352,7 @@ impl Application for Cri {
|
|||
]
|
||||
.into(),
|
||||
),
|
||||
IrcMessage::Join { nickname } => Some(
|
||||
MessageDetail::Join { nickname } => Some(
|
||||
row![
|
||||
text(nickname).style(nickname_color).size(text_size),
|
||||
text(" joined").style(no_message_color).size(text_size)
|
||||
|
|
|
@ -1,24 +1,38 @@
|
|||
use crate::irc_message::IrcMessage;
|
||||
use crate::irc_message::{IrcMessage, MessageDetail};
|
||||
|
||||
use chrono::{DateTime, Local};
|
||||
use iced::{
|
||||
alignment::Horizontal,
|
||||
widget::{column, container, scrollable, text, Container},
|
||||
Background, Color, Length,
|
||||
};
|
||||
use irc::proto::BatchSubCommand;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Channel {
|
||||
pub messages: Vec<IrcMessage>,
|
||||
pub message_ids: HashSet<String>,
|
||||
|
||||
pub topic: Option<String>,
|
||||
pub unread_messages: i32,
|
||||
pub unread_highlights: i32,
|
||||
pub unread_events: i32,
|
||||
|
||||
pub is_multiline: bool,
|
||||
pub multiline_privmsgs: Option<Vec<String>>,
|
||||
pub multiline_timestamp: Option<chrono::DateTime<Local>>,
|
||||
pub multiline_nickname: Option<String>,
|
||||
pub multiline_message_id: Option<String>,
|
||||
}
|
||||
|
||||
pub struct MessageLog {
|
||||
channels: HashMap<Option<String>, Channel>,
|
||||
pub active_channel: Option<String>,
|
||||
|
||||
/// Maps multiline batch tags to channels
|
||||
pub batch_channels: HashMap<String, (BatchSubCommand, String)>,
|
||||
}
|
||||
|
||||
impl<'a> MessageLog {
|
||||
|
@ -28,9 +42,14 @@ impl<'a> MessageLog {
|
|||
Self {
|
||||
channels,
|
||||
active_channel: None,
|
||||
batch_channels: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_channel(&self, channel: &str) -> bool {
|
||||
self.channels.contains_key(&Some(channel.to_string()))
|
||||
}
|
||||
|
||||
pub fn get_all(&'a self) -> Vec<(&'a Option<String>, &'a Channel)> {
|
||||
let mut log: Vec<_> = self.channels.iter().collect();
|
||||
log.sort_unstable_by_key(|(name, _)| name.as_deref());
|
||||
|
@ -45,12 +64,26 @@ impl<'a> MessageLog {
|
|||
self.channels.entry(channel_name).or_default()
|
||||
}
|
||||
|
||||
pub fn on_join(&mut self, channel_name: &str, nickname: &str) {
|
||||
pub fn on_join(
|
||||
&mut self,
|
||||
channel_name: &str,
|
||||
nickname: &str,
|
||||
timestamp: &DateTime<Local>,
|
||||
message_id: Option<&str>,
|
||||
) {
|
||||
let is_active = self.active_channel.as_deref() != Some(channel_name);
|
||||
|
||||
let channel = self.get_mut(Some(channel_name.to_string()));
|
||||
channel.messages.push(IrcMessage::Join {
|
||||
|
||||
if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().to_owned()) {
|
||||
return;
|
||||
}
|
||||
|
||||
channel.messages.push(IrcMessage {
|
||||
detail: MessageDetail::Join {
|
||||
nickname: nickname.to_string(),
|
||||
},
|
||||
message_id: message_id.map(str::to_string),
|
||||
timestamp: *timestamp,
|
||||
});
|
||||
|
||||
if is_active {
|
||||
|
@ -58,13 +91,28 @@ impl<'a> MessageLog {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn on_part(&mut self, channel_name: &str, nickname: &str, comment: Option<&str>) {
|
||||
pub fn on_part(
|
||||
&mut self,
|
||||
channel_name: &str,
|
||||
nickname: &str,
|
||||
comment: Option<&str>,
|
||||
timestamp: &DateTime<Local>,
|
||||
message_id: Option<&str>,
|
||||
) {
|
||||
let is_active = self.active_channel.as_deref() != Some(channel_name);
|
||||
|
||||
let channel = self.get_mut(Some(channel_name.to_string()));
|
||||
channel.messages.push(IrcMessage::Part {
|
||||
|
||||
if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().to_owned()) {
|
||||
return;
|
||||
}
|
||||
|
||||
channel.messages.push(IrcMessage {
|
||||
detail: MessageDetail::Part {
|
||||
nickname: nickname.to_string(),
|
||||
reason: comment.map(str::to_string),
|
||||
},
|
||||
message_id: message_id.map(str::to_string),
|
||||
timestamp: *timestamp,
|
||||
});
|
||||
|
||||
if is_active {
|
||||
|
@ -72,42 +120,147 @@ impl<'a> MessageLog {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn on_nick(&mut self, old: &str, new: &str) {
|
||||
pub fn on_nick(
|
||||
&mut self,
|
||||
old: &str,
|
||||
new: &str,
|
||||
timestamp: &DateTime<Local>,
|
||||
message_id: Option<&str>,
|
||||
) {
|
||||
// TODO increment event counter for each relevant channel
|
||||
for log in self.channels.values_mut() {
|
||||
for channel in self.channels.values_mut() {
|
||||
if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().to_owned()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO only show in relevant channels
|
||||
log.messages.push(IrcMessage::Nick {
|
||||
channel.messages.push(IrcMessage {
|
||||
detail: MessageDetail::Nick {
|
||||
old: old.to_string(),
|
||||
new: new.to_string(),
|
||||
},
|
||||
message_id: message_id.map(str::to_string),
|
||||
timestamp: *timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_quit(&mut self, nickname: &str, reason: Option<&str>) {
|
||||
pub fn on_quit(
|
||||
&mut self,
|
||||
nickname: &str,
|
||||
reason: Option<&str>,
|
||||
timestamp: &DateTime<Local>,
|
||||
message_id: Option<&str>,
|
||||
) {
|
||||
// TODO increment event counter for each relevant channel
|
||||
for log in self.channels.values_mut() {
|
||||
for channel in self.channels.values_mut() {
|
||||
if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().to_owned()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO only show in relevant channels
|
||||
log.messages.push(IrcMessage::Quit {
|
||||
channel.messages.push(IrcMessage {
|
||||
detail: MessageDetail::Quit {
|
||||
nickname: nickname.to_string(),
|
||||
reason: reason.map(str::to_string),
|
||||
},
|
||||
message_id: message_id.map(str::to_string),
|
||||
timestamp: *timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_batch(
|
||||
&mut self,
|
||||
current_nickname: &str,
|
||||
tag: &str,
|
||||
subcommand: Option<&BatchSubCommand>,
|
||||
params: Option<&Vec<String>>,
|
||||
timestamp: &DateTime<Local>,
|
||||
) {
|
||||
if let Some(tag) = tag.strip_prefix('+') {
|
||||
if subcommand == Some(&BatchSubCommand::CUSTOM("DRAFT/MULTILINE".to_string())) {
|
||||
let channel_name = ¶ms.unwrap()[0];
|
||||
self.batch_channels.insert(
|
||||
tag.to_string(),
|
||||
(subcommand.unwrap().clone(), channel_name.clone()),
|
||||
);
|
||||
|
||||
let channel = self.get_mut(Some(channel_name.clone()));
|
||||
|
||||
channel.is_multiline = true;
|
||||
channel.multiline_privmsgs = Some(Vec::new());
|
||||
channel.multiline_timestamp = Some(*timestamp);
|
||||
} else if subcommand == Some(&BatchSubCommand::CUSTOM("CHATHISTORY".to_string())) {
|
||||
let channel_name = ¶ms.unwrap()[0];
|
||||
self.batch_channels.insert(
|
||||
tag.to_string(),
|
||||
(subcommand.unwrap().clone(), channel_name.clone()),
|
||||
);
|
||||
}
|
||||
} else if let Some(tag) = tag.strip_prefix('-') {
|
||||
if let Some((subcommand, channel_name)) = self.batch_channels.remove(tag) {
|
||||
let channel = self.get_mut(Some(channel_name.clone()));
|
||||
|
||||
if subcommand == BatchSubCommand::CUSTOM("DRAFT/MULTILINE".to_string()) {
|
||||
channel.is_multiline = false;
|
||||
|
||||
let nickname = channel.multiline_nickname.clone().unwrap();
|
||||
let message = channel.multiline_privmsgs.as_ref().unwrap().join("\n");
|
||||
let timestamp = channel.multiline_timestamp.unwrap();
|
||||
|
||||
self.on_privmsg(
|
||||
current_nickname,
|
||||
&channel_name,
|
||||
&nickname,
|
||||
&message,
|
||||
None,
|
||||
×tamp,
|
||||
);
|
||||
} else if subcommand == BatchSubCommand::CUSTOM("CHATHISTORY".to_string()) {
|
||||
channel.messages.sort_by_key(|m| m.timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_privmsg(
|
||||
&mut self,
|
||||
current_nickname: &str,
|
||||
channel_name: &str,
|
||||
nickname: &str,
|
||||
message: &str,
|
||||
message_id: Option<&str>,
|
||||
timestamp: &DateTime<Local>,
|
||||
) {
|
||||
let is_active = self.active_channel.as_deref() == Some(channel_name);
|
||||
|
||||
let channel = self.get_mut(Some(channel_name.to_string()));
|
||||
channel.messages.push(IrcMessage::Privmsg {
|
||||
|
||||
if channel.is_multiline {
|
||||
channel.multiline_nickname = Some(nickname.to_string());
|
||||
channel
|
||||
.multiline_privmsgs
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.push(message.to_string());
|
||||
|
||||
if let Some(message_id) = message_id {
|
||||
channel.multiline_message_id = Some(message_id.to_owned());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if message_id.is_none() || channel.message_ids.insert(message_id.unwrap().to_owned()) {
|
||||
channel.messages.push(IrcMessage {
|
||||
detail: MessageDetail::Privmsg {
|
||||
nickname: nickname.to_string(),
|
||||
message: message.to_string(),
|
||||
},
|
||||
message_id: message_id.map(str::to_string),
|
||||
timestamp: *timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
if !is_active {
|
||||
let highlight_regex =
|
||||
|
@ -121,8 +274,12 @@ impl<'a> MessageLog {
|
|||
}
|
||||
|
||||
pub fn on_other(&mut self, message: &str) {
|
||||
self.get_mut(None).messages.push(IrcMessage::Other {
|
||||
self.get_mut(None).messages.push(IrcMessage {
|
||||
detail: MessageDetail::Other {
|
||||
message: message.trim().to_string(),
|
||||
},
|
||||
message_id: None,
|
||||
timestamp: chrono::Local::now(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -159,16 +316,30 @@ impl<'a> MessageLog {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let messages = self
|
||||
.get(&self.active_channel)
|
||||
.unwrap()
|
||||
let channel = self.get(&self.active_channel).unwrap();
|
||||
|
||||
let header = container(column![
|
||||
text(
|
||||
self.active_channel
|
||||
.clone()
|
||||
.unwrap_or("Server messages".to_string())
|
||||
),
|
||||
text(channel.topic.clone().unwrap_or_default())
|
||||
]);
|
||||
|
||||
let messages = channel
|
||||
.messages
|
||||
.iter()
|
||||
.flat_map(|message| -> Option<iced::Element<_>> {
|
||||
match message {
|
||||
IrcMessage::Join { nickname } => Some(
|
||||
.flat_map(|irc_message| -> Option<iced::Element<_>> {
|
||||
let timestamp = irc_message.timestamp.format("%H:%M:%S");
|
||||
|
||||
match &irc_message.detail {
|
||||
MessageDetail::Join { nickname } => Some(
|
||||
container(
|
||||
container(text(format!("{nickname} joined the channel")))
|
||||
container(
|
||||
text(format!("{nickname} joined the channel"))
|
||||
.horizontal_alignment(Horizontal::Center),
|
||||
)
|
||||
.style(move |_: &_| event_appearance)
|
||||
.padding([3, 10]),
|
||||
)
|
||||
|
@ -177,7 +348,7 @@ impl<'a> MessageLog {
|
|||
.padding([3, 0])
|
||||
.into(),
|
||||
),
|
||||
IrcMessage::Part { nickname, reason } => {
|
||||
MessageDetail::Part { nickname, reason } => {
|
||||
let reason = match reason {
|
||||
Some(reason) => format!(" ({reason})"),
|
||||
None => String::new(),
|
||||
|
@ -194,7 +365,7 @@ impl<'a> MessageLog {
|
|||
.into(),
|
||||
)
|
||||
}
|
||||
IrcMessage::Nick { old, new } => Some(
|
||||
MessageDetail::Nick { old, new } => Some(
|
||||
container(
|
||||
container(text(format!("{old} changed their nickname to {new}")))
|
||||
.style(move |_: &_| event_appearance)
|
||||
|
@ -205,7 +376,7 @@ impl<'a> MessageLog {
|
|||
.padding([3, 0])
|
||||
.into(),
|
||||
),
|
||||
IrcMessage::Quit { nickname, reason } => {
|
||||
MessageDetail::Quit { nickname, reason } => {
|
||||
let reason = match reason {
|
||||
Some(reason) => format!(" ({reason})"),
|
||||
None => String::new(),
|
||||
|
@ -223,7 +394,7 @@ impl<'a> MessageLog {
|
|||
.into(),
|
||||
)
|
||||
}
|
||||
IrcMessage::Privmsg { nickname, message } => {
|
||||
MessageDetail::Privmsg { nickname, message } => {
|
||||
let is_self = nickname == current_nickname;
|
||||
|
||||
let mut elements = Vec::new();
|
||||
|
@ -231,34 +402,44 @@ impl<'a> MessageLog {
|
|||
elements.push(text(nickname).style(dark_red).into())
|
||||
}
|
||||
elements.push(text(message).into());
|
||||
elements.push(
|
||||
text(timestamp)
|
||||
.style(dark_grey)
|
||||
.horizontal_alignment(Horizontal::Right)
|
||||
.into(),
|
||||
);
|
||||
|
||||
let appearance = if is_self {
|
||||
own_message_appearance
|
||||
} else {
|
||||
message_appearance
|
||||
};
|
||||
|
||||
let alignment = if is_self {
|
||||
Horizontal::Right
|
||||
} else {
|
||||
Horizontal::Left
|
||||
};
|
||||
|
||||
Some(
|
||||
container(
|
||||
container(column(elements))
|
||||
.style(move |_: &_| {
|
||||
if is_self {
|
||||
own_message_appearance
|
||||
} else {
|
||||
message_appearance
|
||||
}
|
||||
})
|
||||
.style(move |_: &_| appearance)
|
||||
.padding([4, 10]),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.align_x(if is_self {
|
||||
Horizontal::Right
|
||||
} else {
|
||||
Horizontal::Left
|
||||
})
|
||||
.align_x(alignment)
|
||||
.padding([4, 8])
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
IrcMessage::Other { message } => Some(text(message).into()),
|
||||
MessageDetail::Other { message } => Some(text(message).into()),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
container(column![
|
||||
header,
|
||||
container(
|
||||
scrollable(column(messages))
|
||||
.height(Length::Fill)
|
||||
|
@ -270,5 +451,12 @@ impl<'a> MessageLog {
|
|||
background: Some(Background::Color(lighter_grey)),
|
||||
..Default::default()
|
||||
})
|
||||
])
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill)
|
||||
}
|
||||
|
||||
pub fn on_topic(&mut self, channel: &str, topic: Option<String>) {
|
||||
self.get_mut(Some(channel.to_string())).topic = topic;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue