diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index af25ffd..a224077 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -66,6 +66,11 @@ cargo run --- +### 4. Работа с git + +НИКОГДА НЕ КОММИТЬ ИЗМЕНЕНИЯ ПОКА ТЕБЯ НЕ ПОПРОСЯТ!!! + + ## Чеклист перед началом работы - [ ] Прочитал CONTEXT.md diff --git a/src/input/main_input.rs b/src/input/main_input.rs index bf4c4b7..4bdae92 100644 --- a/src/input/main_input.rs +++ b/src/input/main_input.rs @@ -1,4 +1,8 @@ use crate::app::App; +use crate::input::handlers::{ + copy_to_clipboard, format_message_for_clipboard, get_available_actions_count, + handle_global_commands, +}; use crate::tdlib::ChatAction; use crate::types::{ChatId, MessageId}; use crate::utils::{with_timeout, with_timeout_msg}; @@ -6,67 +10,13 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use std::time::{Duration, Instant}; pub async fn handle(app: &mut App, key: KeyEvent) { - let has_ctrl = key.modifiers.contains(KeyModifiers::CONTROL); - // Глобальные команды (работают всегда) - match key.code { - KeyCode::Char('r') if has_ctrl => { - app.status_message = Some("Обновление чатов...".to_string()); - let _ = with_timeout(Duration::from_secs(5), app.td_client.load_chats(50)).await; - app.status_message = None; - return; - } - KeyCode::Char('s') if has_ctrl => { - // Ctrl+S - начать поиск (только если чат не открыт) - if app.selected_chat_id.is_none() { - app.start_search(); - } - return; - } - KeyCode::Char('p') if has_ctrl => { - // Ctrl+P - режим просмотра закреплённых сообщений - if app.selected_chat_id.is_some() && !app.is_pinned_mode() { - if let Some(chat_id) = app.get_selected_chat_id() { - app.status_message = Some("Загрузка закреплённых...".to_string()); - match with_timeout_msg( - Duration::from_secs(5), - app.td_client.get_pinned_messages(ChatId::new(chat_id)), - "Таймаут загрузки", - ) - .await - { - Ok(messages) => { - let messages: Vec = messages; - if messages.is_empty() { - app.status_message = Some("Нет закреплённых сообщений".to_string()); - } else { - app.enter_pinned_mode(messages); - app.status_message = None; - } - } - Err(e) => { - app.error_message = Some(e); - app.status_message = None; - } - } - } - } - return; - } - KeyCode::Char('f') if has_ctrl => { - // Ctrl+F - поиск по сообщениям в открытом чате - if app.selected_chat_id.is_some() - && !app.is_pinned_mode() - && !app.is_message_search_mode() - { - app.enter_message_search_mode(); - } - return; - } - - _ => {} + if handle_global_commands(app, key).await { + return; } + let has_ctrl = key.modifiers.contains(KeyModifiers::CONTROL); + // Режим профиля if app.is_profile_mode() { // Обработка подтверждения выхода из группы @@ -1023,117 +973,3 @@ pub async fn handle(app: &mut App, key: KeyEvent) { } } -/// Подсчёт количества доступных действий в профиле -fn get_available_actions_count(profile: &crate::tdlib::ProfileInfo) -> usize { - let mut count = 0; - - if profile.username.is_some() { - count += 1; // Открыть в браузере - } - - count += 1; // Скопировать ID - - if profile.is_group { - count += 1; // Покинуть группу - } - - count -} - -/// Копирует текст в системный буфер обмена -#[cfg(feature = "clipboard")] -fn copy_to_clipboard(text: &str) -> Result<(), String> { - use arboard::Clipboard; - - let mut clipboard = - Clipboard::new().map_err(|e| format!("Не удалось инициализировать буфер обмена: {}", e))?; - clipboard - .set_text(text) - .map_err(|e| format!("Не удалось скопировать: {}", e))?; - - Ok(()) -} - -/// Заглушка для copy_to_clipboard когда feature "clipboard" выключена -#[cfg(not(feature = "clipboard"))] -fn copy_to_clipboard(_text: &str) -> Result<(), String> { - Err("Копирование в буфер обмена недоступно (требуется feature 'clipboard')".to_string()) -} - -/// Форматирует сообщение для копирования с контекстом -fn format_message_for_clipboard(msg: &crate::tdlib::MessageInfo) -> String { - let mut result = String::new(); - - // Добавляем forward контекст если есть - if let Some(forward) = msg.forward_from() { - result.push_str(&format!("↪ Переслано от {}\n", forward.sender_name)); - } - - // Добавляем reply контекст если есть - if let Some(reply) = msg.reply_to() { - result.push_str(&format!("┌ {}: {}\n", reply.sender_name, reply.text)); - } - - // Добавляем основной текст с markdown форматированием - result.push_str(&convert_entities_to_markdown(msg.text(), msg.entities())); - - result -} - -/// Конвертирует текст с entities в markdown -fn convert_entities_to_markdown(text: &str, entities: &[tdlib_rs::types::TextEntity]) -> String { - use tdlib_rs::enums::TextEntityType; - - if entities.is_empty() { - return text.to_string(); - } - - // Создаём вектор символов для работы с unicode - let chars: Vec = text.chars().collect(); - let mut result = String::new(); - let mut i = 0; - - while i < chars.len() { - // Ищем entity, который начинается в текущей позиции - let mut entity_found = false; - - for entity in entities { - if entity.offset as usize == i { - entity_found = true; - let end = (entity.offset + entity.length) as usize; - let entity_text: String = chars[i..end.min(chars.len())].iter().collect(); - - // Применяем форматирование в зависимости от типа - let formatted = match &entity.r#type { - TextEntityType::Bold => format!("**{}**", entity_text), - TextEntityType::Italic => format!("*{}*", entity_text), - TextEntityType::Underline => format!("__{}__", entity_text), - TextEntityType::Strikethrough => format!("~~{}~~", entity_text), - TextEntityType::Code | TextEntityType::Pre | TextEntityType::PreCode(_) => { - format!("`{}`", entity_text) - } - TextEntityType::TextUrl(url_info) => { - format!("[{}]({})", entity_text, url_info.url) - } - TextEntityType::Url => format!("<{}>", entity_text), - TextEntityType::Mention | TextEntityType::MentionName(_) => { - format!("@{}", entity_text.trim_start_matches('@')) - } - TextEntityType::Spoiler => format!("||{}||", entity_text), - _ => entity_text, - }; - - result.push_str(&formatted); - i = end; - break; - } - } - - if !entity_found { - result.push(chars[i]); - i += 1; - } - } - - result -}