use crate::discord::AppCommand;
use super::composer::{
MentionCompletion, build_mention_candidates, expand_mention_completions, is_mention_query_char,
move_mention_selection, should_start_mention_query,
};
use super::{DashboardState, FocusPane, MentionPickerEntry};
impl DashboardState {
pub fn is_composing(&self) -> bool {
self.composer_active
}
pub(super) fn start_reply_composer(&mut self) {
let Some(message_id) = self.selected_message_state().map(|message| message.id) else {
return;
};
if !self.can_send_in_selected_channel() {
return;
}
self.composer_input.clear();
self.reply_target_message_id = Some(message_id);
self.edit_target_message = None;
self.composer_active = true;
self.focus = FocusPane::Messages;
}
pub(super) fn start_edit_composer(&mut self) {
let Some(message) = self.selected_message_state() else {
return;
};
if Some(message.author_id) != self.current_user_id || !message.message_kind.is_regular() {
return;
}
let Some(content) = message.content.clone() else {
return;
};
let channel_id = message.channel_id;
let message_id = message.id;
self.composer_input = content;
self.reply_target_message_id = None;
self.edit_target_message = Some((channel_id, message_id));
self.composer_active = true;
self.focus = FocusPane::Messages;
}
pub fn composer_input(&self) -> &str {
&self.composer_input
}
pub fn can_send_in_selected_channel(&self) -> bool {
match self.selected_channel_state() {
Some(channel) if channel.is_forum() => false,
Some(channel) => self.discord.can_send_in_channel(channel),
None => true,
}
}
pub fn can_attach_in_selected_channel(&self) -> bool {
match self.selected_channel_state() {
Some(channel) if channel.is_forum() => false,
Some(channel) => self.discord.can_attach_in_channel(channel),
None => true,
}
}
pub fn start_composer(&mut self) {
if self.selected_channel_id().is_none() {
return;
}
if !self.can_send_in_selected_channel() {
return;
}
self.reply_target_message_id = None;
self.edit_target_message = None;
self.composer_active = true;
self.focus = FocusPane::Messages;
}
pub fn cancel_composer(&mut self) {
self.composer_active = false;
self.composer_input.clear();
self.reply_target_message_id = None;
self.edit_target_message = None;
self.reset_mention_picker_state();
}
pub fn push_composer_char(&mut self, value: char) {
if value == '@' {
let triggers_picker = should_start_mention_query(&self.composer_input);
self.composer_input.push('@');
if triggers_picker {
self.composer_mention_query = Some(String::new());
} else {
self.composer_mention_query = None;
}
self.composer_mention_selected = 0;
return;
}
if let Some(query) = self.composer_mention_query.as_mut() {
if is_mention_query_char(value) {
query.push(value);
self.composer_input.push(value);
self.composer_mention_selected = 0;
return;
}
self.composer_mention_query = None;
self.composer_mention_selected = 0;
}
self.composer_input.push(value);
}
pub fn pop_composer_char(&mut self) {
if let Some(query) = self.composer_mention_query.as_mut() {
if query.pop().is_some() {
self.composer_input.pop();
self.composer_mention_selected = 0;
return;
}
self.composer_input.pop();
self.composer_mention_query = None;
self.composer_mention_selected = 0;
return;
}
self.composer_input.pop();
self.invalidate_dropped_mention_completions();
}
pub fn submit_composer(&mut self) -> Option<AppCommand> {
let expanded =
expand_mention_completions(&self.composer_input, &self.composer_mention_completions);
let content = expanded.trim().to_owned();
if content.is_empty() {
return None;
}
if let Some((channel_id, message_id)) = self.edit_target_message.take() {
self.composer_input.clear();
self.composer_active = false;
self.reply_target_message_id = None;
self.reset_mention_picker_state();
return Some(AppCommand::EditMessage {
channel_id,
message_id,
content,
});
}
let channel_id = self.selected_channel_id()?;
if !self.can_send_in_selected_channel() {
self.composer_input.clear();
self.composer_active = false;
self.reply_target_message_id = None;
self.edit_target_message = None;
self.reset_mention_picker_state();
return None;
}
self.composer_input.clear();
self.reset_mention_picker_state();
let reply_to = self.reply_target_message_id.take();
Some(AppCommand::SendMessage {
channel_id,
content,
reply_to,
})
}
pub fn composer_mention_query(&self) -> Option<&str> {
self.composer_mention_query.as_deref()
}
pub fn composer_mention_selected(&self) -> usize {
self.composer_mention_selected
}
pub fn composer_mention_candidates(&self) -> Vec<MentionPickerEntry> {
let Some(query) = self.composer_mention_query.as_deref() else {
return Vec::new();
};
build_mention_candidates(query, self.flattened_members())
}
pub fn move_composer_mention_selection(&mut self, delta: isize) {
if self.composer_mention_query.is_none() {
return;
}
let len = self.composer_mention_candidates().len();
self.composer_mention_selected =
move_mention_selection(self.composer_mention_selected, len, delta);
}
pub fn confirm_composer_mention(&mut self) -> bool {
let Some(query) = self.composer_mention_query.clone() else {
return false;
};
let candidates = self.composer_mention_candidates();
let Some(entry) = candidates.get(self.composer_mention_selected) else {
return false;
};
let entry = entry.clone();
let suffix_byte_count = '@'.len_utf8() + query.len();
let new_len = self.composer_input.len().saturating_sub(suffix_byte_count);
self.composer_input.truncate(new_len);
let start = self.composer_input.len();
self.composer_input.push('@');
self.composer_input.push_str(&entry.display_name);
let end = self.composer_input.len();
self.composer_input.push(' ');
self.composer_mention_completions.push(MentionCompletion {
byte_start: start,
byte_end: end,
user_id: entry.user_id,
});
self.composer_mention_query = None;
self.composer_mention_selected = 0;
true
}
pub fn cancel_composer_mention(&mut self) {
self.composer_mention_query = None;
self.composer_mention_selected = 0;
}
fn reset_mention_picker_state(&mut self) {
self.composer_mention_query = None;
self.composer_mention_selected = 0;
self.composer_mention_completions.clear();
}
fn invalidate_dropped_mention_completions(&mut self) {
let len = self.composer_input.len();
self.composer_mention_completions
.retain(|completion| completion.byte_end <= len);
}
}