This commit is contained in:
Mikhail Kilin
2026-01-27 23:29:00 +03:00
parent 356d2d3064
commit f291191577
8 changed files with 923 additions and 43 deletions

View File

@@ -251,6 +251,73 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
return;
}
// Обработка ввода в режиме выбора реакции
if app.is_reaction_picker_mode() {
match key.code {
KeyCode::Left => {
app.select_previous_reaction();
app.needs_redraw = true;
}
KeyCode::Right => {
app.select_next_reaction();
app.needs_redraw = true;
}
KeyCode::Up => {
// Переход на ряд выше (8 эмодзи в ряду)
if app.selected_reaction_index >= 8 {
app.selected_reaction_index = app.selected_reaction_index.saturating_sub(8);
app.needs_redraw = true;
}
}
KeyCode::Down => {
// Переход на ряд ниже (8 эмодзи в ряду)
let new_index = app.selected_reaction_index + 8;
if new_index < app.available_reactions.len() {
app.selected_reaction_index = new_index;
app.needs_redraw = true;
}
}
KeyCode::Enter => {
// Добавить/убрать реакцию
if let Some(emoji) = app.get_selected_reaction().cloned() {
if let Some(message_id) = app.get_selected_message_for_reaction() {
if let Some(chat_id) = app.selected_chat_id {
app.status_message = Some("Отправка реакции...".to_string());
app.needs_redraw = true;
match timeout(
Duration::from_secs(5),
app.td_client.toggle_reaction(chat_id, message_id, emoji.clone())
).await {
Ok(Ok(_)) => {
app.status_message = Some(format!("Реакция {} добавлена", emoji));
app.exit_reaction_picker_mode();
app.needs_redraw = true;
}
Ok(Err(e)) => {
app.error_message = Some(format!("Ошибка: {}", e));
app.status_message = None;
app.needs_redraw = true;
}
Err(_) => {
app.error_message = Some("Таймаут отправки реакции".to_string());
app.status_message = None;
app.needs_redraw = true;
}
}
}
}
}
}
KeyCode::Esc => {
app.exit_reaction_picker_mode();
app.needs_redraw = true;
}
_ => {}
}
return;
}
// Модалка подтверждения удаления
if app.is_confirm_delete_shown() {
match key.code {
@@ -563,6 +630,58 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
// Начать режим пересылки
app.start_forward_selected();
}
KeyCode::Char('y') | KeyCode::Char('н') => {
// Копировать сообщение
if let Some(msg) = app.get_selected_message() {
let text = format_message_for_clipboard(msg);
match copy_to_clipboard(&text) {
Ok(_) => {
app.status_message = Some("Сообщение скопировано".to_string());
}
Err(e) => {
app.error_message = Some(format!("Ошибка копирования: {}", e));
}
}
}
}
KeyCode::Char('e') | KeyCode::Char('у') => {
// Открыть emoji picker для добавления реакции
if let Some(msg) = app.get_selected_message() {
let chat_id = app.selected_chat_id.unwrap();
let message_id = msg.id;
app.status_message = Some("Загрузка реакций...".to_string());
app.needs_redraw = true;
// Запрашиваем доступные реакции
match timeout(
Duration::from_secs(5),
app.td_client.get_message_available_reactions(chat_id, message_id)
).await {
Ok(Ok(reactions)) => {
if reactions.is_empty() {
app.error_message = Some("Реакции недоступны для этого сообщения".to_string());
app.status_message = None;
app.needs_redraw = true;
} else {
app.enter_reaction_picker_mode(message_id, reactions);
app.status_message = None;
app.needs_redraw = true;
}
}
Ok(Err(e)) => {
app.error_message = Some(format!("Ошибка загрузки реакций: {}", e));
app.status_message = None;
app.needs_redraw = true;
}
Err(_) => {
app.error_message = Some("Таймаут загрузки реакций".to_string());
app.status_message = None;
app.needs_redraw = true;
}
}
}
}
_ => {}
}
return;
@@ -756,3 +875,91 @@ fn get_available_actions_count(profile: &crate::tdlib::ProfileInfo) -> usize {
count
}
/// Копирует текст в системный буфер обмена
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(())
}
/// Форматирует сообщение для копирования с контекстом
fn format_message_for_clipboard(msg: &crate::tdlib::client::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.content, &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
}