yet-another-changes #10

Merged
killingdruid merged 4 commits from yet-another-changes into main 2026-01-27 22:43:13 +00:00
5 changed files with 118 additions and 7 deletions
Showing only changes of commit ac684da820 - Show all commits

View File

@@ -517,4 +517,24 @@ impl App {
pub fn get_selected_search_result_id(&self) -> Option<i64> { pub fn get_selected_search_result_id(&self) -> Option<i64> {
self.get_selected_search_result().map(|m| m.id) self.get_selected_search_result().map(|m| m.id)
} }
// === Draft Management ===
/// Получить черновик для текущего чата
pub fn get_current_draft(&self) -> Option<String> {
self.selected_chat_id.and_then(|chat_id| {
self.chats
.iter()
.find(|c| c.id == chat_id)
.and_then(|c| c.draft_text.clone())
})
}
/// Загрузить черновик в message_input (вызывается при открытии чата)
pub fn load_draft(&mut self) {
if let Some(draft) = self.get_current_draft() {
self.message_input = draft;
self.cursor_position = self.message_input.chars().count();
}
}
} }

View File

@@ -257,6 +257,8 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
let _ = timeout(Duration::from_secs(5), app.td_client.fetch_missing_reply_info()).await; let _ = timeout(Duration::from_secs(5), app.td_client.fetch_missing_reply_info()).await;
// Загружаем последнее закреплённое сообщение // Загружаем последнее закреплённое сообщение
let _ = timeout(Duration::from_secs(2), app.td_client.load_current_pinned_message(chat_id)).await; let _ = timeout(Duration::from_secs(2), app.td_client.load_current_pinned_message(chat_id)).await;
// Загружаем черновик
app.load_draft();
app.status_message = None; app.status_message = None;
} }
Ok(Err(e)) => { Ok(Err(e)) => {
@@ -386,6 +388,8 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
let _ = timeout(Duration::from_secs(5), app.td_client.fetch_missing_reply_info()).await; let _ = timeout(Duration::from_secs(5), app.td_client.fetch_missing_reply_info()).await;
// Загружаем последнее закреплённое сообщение // Загружаем последнее закреплённое сообщение
let _ = timeout(Duration::from_secs(2), app.td_client.load_current_pinned_message(chat_id)).await; let _ = timeout(Duration::from_secs(2), app.td_client.load_current_pinned_message(chat_id)).await;
// Загружаем черновик
app.load_draft();
app.status_message = None; app.status_message = None;
} }
Ok(Err(e)) => { Ok(Err(e)) => {
@@ -415,6 +419,16 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
// Отменить режим ответа // Отменить режим ответа
app.cancel_reply(); app.cancel_reply();
} else if app.selected_chat_id.is_some() { } else if app.selected_chat_id.is_some() {
// Сохраняем черновик если есть текст в инпуте
if let Some(chat_id) = app.selected_chat_id {
if !app.message_input.is_empty() && !app.is_editing() && !app.is_replying() {
let draft_text = app.message_input.clone();
let _ = app.td_client.set_draft_message(chat_id, draft_text).await;
} else if app.message_input.is_empty() {
// Очищаем черновик если инпут пустой
let _ = app.td_client.set_draft_message(chat_id, String::new()).await;
}
}
app.close_chat(); app.close_chat();
} }
return; return;

View File

@@ -112,6 +112,8 @@ pub struct ChatInfo {
pub folder_ids: Vec<i32>, pub folder_ids: Vec<i32>,
/// Чат замьючен (уведомления отключены) /// Чат замьючен (уведомления отключены)
pub is_muted: bool, pub is_muted: bool,
/// Черновик сообщения
pub draft_text: Option<String>,
} }
/// Информация о сообщении, на которое отвечают /// Информация о сообщении, на которое отвечают
@@ -592,6 +594,19 @@ impl TdClient {
} }
} }
} }
Update::ChatDraftMessage(update) => {
// Обновляем черновик в списке чатов
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) {
chat.draft_text = update.draft_message.as_ref().and_then(|draft| {
// Извлекаем текст из InputMessageText
if let tdlib_rs::enums::InputMessageContent::InputMessageText(text_msg) = &draft.input_message_text {
Some(text_msg.text.text.clone())
} else {
None
}
});
}
}
_ => {} _ => {}
} }
} }
@@ -678,6 +693,7 @@ impl TdClient {
last_read_outbox_message_id: td_chat.last_read_outbox_message_id, last_read_outbox_message_id: td_chat.last_read_outbox_message_id,
folder_ids, folder_ids,
is_muted, is_muted,
draft_text: None,
}; };
if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) { if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) {
@@ -1348,6 +1364,57 @@ impl TdClient {
} }
/// Редактирование текстового сообщения с поддержкой Markdown /// Редактирование текстового сообщения с поддержкой Markdown
/// Устанавливает черновик для чата через TDLib API
pub async fn set_draft_message(&self, chat_id: i64, text: String) -> Result<(), String> {
use tdlib_rs::types::{FormattedText, InputMessageText, DraftMessage};
use tdlib_rs::enums::InputMessageContent;
if text.is_empty() {
// Очищаем черновик
let result = functions::set_chat_draft_message(
chat_id,
0, // message_thread_id
None, // draft_message (None = очистить)
self.client_id,
).await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка очистки черновика: {:?}", e)),
}
} else {
// Создаём черновик
let formatted_text = FormattedText {
text: text.clone(),
entities: vec![],
};
let input_message = InputMessageContent::InputMessageText(InputMessageText {
text: formatted_text,
link_preview_options: None,
clear_draft: false,
});
let draft = DraftMessage {
reply_to: None,
date: 0, // TDLib установит текущее время
input_message_text: input_message,
};
let result = functions::set_chat_draft_message(
chat_id,
0, // message_thread_id
Some(draft),
self.client_id,
).await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка установки черновика: {:?}", e)),
}
}
}
pub async fn edit_message(&self, chat_id: i64, message_id: i64, text: String) -> Result<MessageInfo, String> { pub async fn edit_message(&self, chat_id: i64, message_id: i64, text: String) -> Result<MessageInfo, String> {
use tdlib_rs::types::{FormattedText, InputMessageText, TextParseModeMarkdown}; use tdlib_rs::types::{FormattedText, InputMessageText, TextParseModeMarkdown};
use tdlib_rs::enums::{InputMessageContent, TextParseMode}; use tdlib_rs::enums::{InputMessageContent, TextParseMode};

View File

@@ -65,13 +65,20 @@ pub fn render(f: &mut Frame, area: Rect, app: &mut App) {
String::new() String::new()
}; };
// Индикатор черновика ✎
let draft_badge = if chat.draft_text.is_some() {
"".to_string()
} else {
String::new()
};
let unread_badge = if chat.unread_count > 0 { let unread_badge = if chat.unread_count > 0 {
format!(" ({})", chat.unread_count) format!(" ({})", chat.unread_count)
} else { } else {
String::new() String::new()
}; };
let content = format!("{}{}{}{}{}{}{}{}", prefix, status_icon, pin_icon, mute_icon, chat.title, username_text, mention_badge, unread_badge); let content = format!("{}{}{}{}{}{}{}{}{}", prefix, status_icon, pin_icon, mute_icon, chat.title, username_text, mention_badge, draft_badge, unread_badge);
// Цвет: онлайн — зелёные, остальные — белые // Цвет: онлайн — зелёные, остальные — белые
let style = match app.td_client.get_user_status_by_chat_id(chat.id) { let style = match app.td_client.get_user_status_by_chat_id(chat.id) {

View File

@@ -148,15 +148,18 @@ fn render_input_with_cursor(prefix: &str, text: &str, cursor_pos: usize, color:
let chars: Vec<char> = text.chars().collect(); let chars: Vec<char> = text.chars().collect();
let mut spans: Vec<Span> = vec![Span::raw(prefix.to_string())]; let mut spans: Vec<Span> = vec![Span::raw(prefix.to_string())];
// Ограничиваем cursor_pos границами текста
let safe_cursor_pos = cursor_pos.min(chars.len());
// Текст до курсора // Текст до курсора
if cursor_pos > 0 { if safe_cursor_pos > 0 {
let before: String = chars[..cursor_pos].iter().collect(); let before: String = chars[..safe_cursor_pos].iter().collect();
spans.push(Span::styled(before, Style::default().fg(color))); spans.push(Span::styled(before, Style::default().fg(color)));
} }
// Символ под курсором (или █ если курсор в конце) // Символ под курсором (или █ если курсор в конце)
if cursor_pos < chars.len() { if safe_cursor_pos < chars.len() {
let cursor_char = chars[cursor_pos].to_string(); let cursor_char = chars[safe_cursor_pos].to_string();
spans.push(Span::styled(cursor_char, Style::default().fg(Color::Black).bg(color))); spans.push(Span::styled(cursor_char, Style::default().fg(Color::Black).bg(color)));
} else { } else {
// Курсор в конце - показываем блок // Курсор в конце - показываем блок
@@ -164,8 +167,8 @@ fn render_input_with_cursor(prefix: &str, text: &str, cursor_pos: usize, color:
} }
// Текст после курсора // Текст после курсора
if cursor_pos + 1 < chars.len() { if safe_cursor_pos + 1 < chars.len() {
let after: String = chars[cursor_pos + 1..].iter().collect(); let after: String = chars[safe_cursor_pos + 1..].iter().collect();
spans.push(Span::styled(after, Style::default().fg(color))); spans.push(Span::styled(after, Style::default().fg(color)));
} }