This commit is contained in:
Mikhail Kilin
2026-02-02 02:32:02 +03:00
parent dd4981d216
commit 5c92c059c9
2 changed files with 13 additions and 172 deletions

View File

@@ -66,6 +66,11 @@ cargo run
---
### 4. Работа с git
НИКОГДА НЕ КОММИТЬ ИЗМЕНЕНИЯ ПОКА ТЕБЯ НЕ ПОПРОСЯТ!!!
## Чеклист перед началом работы
- [ ] Прочитал CONTEXT.md

View File

@@ -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<crate::tdlib::MessageInfo> = 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<char> = 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
}