This commit is contained in:
Mikhail Kilin
2026-01-24 18:53:35 +03:00
parent 22c4e17377
commit fa749d24c5
7 changed files with 576 additions and 29 deletions

View File

@@ -144,7 +144,7 @@ fn styles_equal(a: &CharStyle, b: &CharStyle) -> bool {
}
/// Рендерит текст инпута с блочным курсором
fn render_input_with_cursor<'a>(prefix: &'a str, text: &str, cursor_pos: usize, color: Color) -> Line<'a> {
fn render_input_with_cursor(prefix: &str, text: &str, cursor_pos: usize, color: Color) -> Line<'static> {
let chars: Vec<char> = text.chars().collect();
let mut spans: Vec<Span> = vec![Span::raw(prefix.to_string())];
@@ -435,6 +435,48 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let selection_marker = if is_selected { "" } else { "" };
let marker_len = selection_marker.chars().count();
// Отображаем forward если есть
if let Some(forward) = &msg.forward_from {
let forward_line = format!("↪ Переслано от {}", forward.sender_name);
let forward_len = forward_line.chars().count();
if msg.is_outgoing {
// Forward справа для исходящих
let padding = content_width.saturating_sub(forward_len + 1);
lines.push(Line::from(vec![
Span::raw(" ".repeat(padding)),
Span::styled(forward_line, Style::default().fg(Color::Magenta)),
]));
} else {
// Forward слева для входящих
lines.push(Line::from(vec![
Span::styled(forward_line, Style::default().fg(Color::Magenta)),
]));
}
}
// Отображаем reply если есть
if let Some(reply) = &msg.reply_to {
let reply_text: String = reply.text.chars().take(40).collect();
let ellipsis = if reply.text.chars().count() > 40 { "..." } else { "" };
let reply_line = format!("{}: {}{}", reply.sender_name, reply_text, ellipsis);
let reply_len = reply_line.chars().count();
if msg.is_outgoing {
// Reply справа для исходящих
let padding = content_width.saturating_sub(reply_len + 1);
lines.push(Line::from(vec![
Span::raw(" ".repeat(padding)),
Span::styled(reply_line, Style::default().fg(Color::Cyan)),
]));
} else {
// Reply слева для входящих
lines.push(Line::from(vec![
Span::styled(reply_line, Style::default().fg(Color::Cyan)),
]));
}
}
if msg.is_outgoing {
// Исходящие: справа, формат "текст (HH:MM ✎ ✓✓)"
let read_mark = if msg.is_read { "✓✓" } else { "" };
@@ -562,17 +604,29 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
f.render_widget(messages_widget, message_chunks[1]);
// Input box с wrap для длинного текста и блочным курсором
let (input_line, input_title) = if app.is_selecting_message() {
let (input_line, input_title) = if app.is_forwarding() {
// Режим пересылки - показываем превью сообщения
let forward_preview = app.get_forwarding_message()
.map(|m| {
let text_preview: String = m.content.chars().take(40).collect();
let ellipsis = if m.content.chars().count() > 40 { "..." } else { "" };
format!("{}{}", text_preview, ellipsis)
})
.unwrap_or_else(|| "↪ ...".to_string());
let line = Line::from(Span::styled(forward_preview, Style::default().fg(Color::Cyan)));
(line, " Выберите чат ← ")
} else if app.is_selecting_message() {
// Режим выбора сообщения - подсказка зависит от возможностей
let selected_msg = app.get_selected_message();
let can_edit = selected_msg.map(|m| m.can_be_edited && m.is_outgoing).unwrap_or(false);
let can_delete = selected_msg.map(|m| m.can_be_deleted_only_for_self || m.can_be_deleted_for_all_users).unwrap_or(false);
let hint = match (can_edit, can_delete) {
(true, true) => "↑↓ выбрать · Enter редакт. · d удалить · Esc отмена",
(true, false) => "↑↓ выбрать · Enter редакт. · Esc отмена",
(false, true) => "↑↓ выбрать · d удалить · Esc отмена",
(false, false) => "↑↓ выбрать · Esc отмена",
(true, true) => "↑↓ · Enter ред. · r ответ · f перслть · d удал. · Esc",
(true, false) => "↑↓ · Enter ред. · r ответ · f переслть · Esc",
(false, true) => "↑↓ · r ответ · f переслать · d удалить · Esc",
(false, false) => "↑↓ · r ответить · f переслать · Esc",
};
(Line::from(Span::styled(hint, Style::default().fg(Color::Cyan))), " Выбор сообщения ")
} else if app.is_editing() {
@@ -590,6 +644,31 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let line = render_input_with_cursor("", &app.message_input, app.cursor_position, Color::Magenta);
(line, " Редактирование (Esc отмена) ")
}
} else if app.is_replying() {
// Режим ответа на сообщение
let reply_preview = app.get_replying_to_message()
.map(|m| {
let sender = if m.is_outgoing { "Вы" } else { &m.sender_name };
let text_preview: String = m.content.chars().take(30).collect();
let ellipsis = if m.content.chars().count() > 30 { "..." } else { "" };
format!("{}: {}{}", sender, text_preview, ellipsis)
})
.unwrap_or_else(|| "...".to_string());
if app.message_input.is_empty() {
let line = Line::from(vec![
Span::styled("", Style::default().fg(Color::Cyan)),
Span::styled(reply_preview, Style::default().fg(Color::Gray)),
Span::raw(" "),
Span::styled("", Style::default().fg(Color::Yellow)),
]);
(line, " Ответ (Esc отмена) ")
} else {
let short_preview: String = reply_preview.chars().take(15).collect();
let prefix = format!("{} > ", short_preview);
let line = render_input_with_cursor(&prefix, &app.message_input, app.cursor_position, Color::Yellow);
(line, " Ответ (Esc отмена) ")
}
} else {
// Обычный режим
if app.message_input.is_empty() {
@@ -610,10 +689,15 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let input_block = if input_title.is_empty() {
Block::default().borders(Borders::ALL)
} else {
let title_color = if app.is_replying() || app.is_forwarding() {
Color::Cyan
} else {
Color::Magenta
};
Block::default()
.borders(Borders::ALL)
.title(input_title)
.title_style(Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD))
.title_style(Style::default().fg(title_color).add_modifier(Modifier::BOLD))
};
let input = Paragraph::new(input_line)