yet-another-changes #10
@@ -517,4 +517,24 @@ impl App {
|
||||
pub fn get_selected_search_result_id(&self) -> Option<i64> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(2), app.td_client.load_current_pinned_message(chat_id)).await;
|
||||
// Загружаем черновик
|
||||
app.load_draft();
|
||||
app.status_message = None;
|
||||
}
|
||||
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(2), app.td_client.load_current_pinned_message(chat_id)).await;
|
||||
// Загружаем черновик
|
||||
app.load_draft();
|
||||
app.status_message = None;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
@@ -415,6 +419,16 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
// Отменить режим ответа
|
||||
app.cancel_reply();
|
||||
} 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();
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -112,6 +112,8 @@ pub struct ChatInfo {
|
||||
pub folder_ids: Vec<i32>,
|
||||
/// Чат замьючен (уведомления отключены)
|
||||
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,
|
||||
folder_ids,
|
||||
is_muted,
|
||||
draft_text: None,
|
||||
};
|
||||
|
||||
if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) {
|
||||
@@ -1348,6 +1364,57 @@ impl TdClient {
|
||||
}
|
||||
|
||||
/// Редактирование текстового сообщения с поддержкой 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> {
|
||||
use tdlib_rs::types::{FormattedText, InputMessageText, TextParseModeMarkdown};
|
||||
use tdlib_rs::enums::{InputMessageContent, TextParseMode};
|
||||
|
||||
@@ -65,13 +65,20 @@ pub fn render(f: &mut Frame, area: Rect, app: &mut App) {
|
||||
String::new()
|
||||
};
|
||||
|
||||
// Индикатор черновика ✎
|
||||
let draft_badge = if chat.draft_text.is_some() {
|
||||
" ✎".to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let unread_badge = if chat.unread_count > 0 {
|
||||
format!(" ({})", chat.unread_count)
|
||||
} else {
|
||||
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) {
|
||||
|
||||
@@ -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 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 {
|
||||
let before: String = chars[..cursor_pos].iter().collect();
|
||||
if safe_cursor_pos > 0 {
|
||||
let before: String = chars[..safe_cursor_pos].iter().collect();
|
||||
spans.push(Span::styled(before, Style::default().fg(color)));
|
||||
}
|
||||
|
||||
// Символ под курсором (или █ если курсор в конце)
|
||||
if cursor_pos < chars.len() {
|
||||
let cursor_char = chars[cursor_pos].to_string();
|
||||
if safe_cursor_pos < chars.len() {
|
||||
let cursor_char = chars[safe_cursor_pos].to_string();
|
||||
spans.push(Span::styled(cursor_char, Style::default().fg(Color::Black).bg(color)));
|
||||
} else {
|
||||
// Курсор в конце - показываем блок
|
||||
@@ -164,8 +167,8 @@ fn render_input_with_cursor(prefix: &str, text: &str, cursor_pos: usize, color:
|
||||
}
|
||||
|
||||
// Текст после курсора
|
||||
if cursor_pos + 1 < chars.len() {
|
||||
let after: String = chars[cursor_pos + 1..].iter().collect();
|
||||
if safe_cursor_pos + 1 < chars.len() {
|
||||
let after: String = chars[safe_cursor_pos + 1..].iter().collect();
|
||||
spans.push(Span::styled(after, Style::default().fg(color)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user