@emaxe/tuigram 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -13,6 +13,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
13
13
 
14
14
  - English localization of the TUI and CLI interface strings.
15
15
 
16
+ ## [1.4.0] — 2026-09-01
17
+
18
+ ### Added
19
+
20
+ - Message selection in the chat feed: a left click marks a message with a colored `▌` bar
21
+ along all of its lines. `↑` / `↓` (and `k` / `j`) move the selection between messages and
22
+ scroll it into view; reaching the topmost message loads the previous page of history.
23
+ - The message action menu now opens with the right mouse button, and with `[Enter]` on the
24
+ selected message. Previously any click opened it, so a message could not simply be picked.
25
+ - The selected message became the target for actions: `[Ctrl+R]` replies to it, `[Ctrl+E]`
26
+ edits it (when it is yours), `[Ctrl+A]` and the status bar `[Ctrl+A]` button open the menu
27
+ for it. With nothing selected the previous behaviour applies — the last message in the chat.
28
+ - Full-screen image viewer opened by clicking an image preview: the thumbnail that arrived
29
+ with the message is shown instantly, then replaced by the full-size version. For videos and
30
+ documents the largest thumbnail is used and the file itself is never downloaded. Close with
31
+ `[Esc]`, `[Q]`, `[Enter]` or a click; the image is re-rendered when the terminal is resized.
32
+ - The `[Ctrl+A]` shortcut (action menu) now works from every panel, including the input box.
33
+ - `[F12]` temporarily hands the mouse back to the terminal so text can be selected and
34
+ copied, and restores the capture when pressed again.
35
+
36
+ ### Fixed
37
+
38
+ - A modal window was closed by the very click that opened it: blessed delivers a click to the
39
+ element first and to the screen immediately after. As a result the status bar buttons
40
+ `[F1]`, `[Ctrl+A]`, `[Ctrl+P]`, `[Ctrl+Q]`, the header logo and the chat title did not work
41
+ with the mouse at all, and the action menu only stayed open when the click happened to land
42
+ inside the future modal rectangle.
43
+ - Clicking a message hit the wrong message in a scrolled feed: the position was read from
44
+ `getScroll()`, which adds a blessed-internal offset to the actual content shift.
45
+ - The mouse wheel scrolled the feed twice per notch (a custom handler on top of the built-in
46
+ blessed one), and scrolling up from the bottom of the feed did nothing for the first notches.
47
+ - `PageUp` / `PageDown` scrolled the feed twice as far while it had focus.
48
+ - Clicks on the input hints were off by about 10 cells: clicking `[Ctrl+E] Правка` triggered
49
+ commands, and clicking the "Введите сообщение…" text switched to reply mode.
50
+ - A right click behaved like a left one: it opened chats in the list and pressed status bar
51
+ buttons and filter tabs.
52
+ - The feed layout map drifted by one line per message, so click coordinates diverged from the
53
+ actual text the longer the history grew.
54
+ - A full-screen image render could evict and replace the same image's thumbnail in the feed:
55
+ the render size is now part of the pseudo-graphics cache key.
56
+
57
+ ## [1.3.0] — 2026-09-01
58
+
59
+ ### Added
60
+
61
+ - Comprehensive mouse support across the entire TUI:
62
+ - Direct panel focus on click (left panel for dialog list, middle panel for message feed, bottom panel for input box).
63
+ - Single-click dialog selection and immediate opening.
64
+ - Smooth mouse wheel scrolling for dialog list and message feed with automatic loading of older history when scrolling up past the top.
65
+ - Clicking on any message opens its contextual action menu (reactions, replies, edits, downloads, deletion).
66
+ - Clickable filter tabs (`1:Все`, `2:ЛС`, `3:Группы`, `4:Каналы`, `5:Боты`, `6:Непроч`) and search input focusing.
67
+ - Interactive status bar buttons (`[Tab]`, `[1-6]`, `[/]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+P]`, `[Ctrl+Q]`).
68
+ - Interactive header elements: click logo for Help, click chat title for Chat Info, click status badge for network state.
69
+ - Interactive context bar in input box: click to cancel reply/edit mode or trigger shortcuts.
70
+ - Full mouse navigation inside modals (button clicks, checkbox toggles, file browsing) and dismissing any modal by clicking outside its boundary.
71
+ - Robust terminal mouse mode initialization (`\x1b[?1000h\x1b[?1002h\x1b[?1006h\x1b[?1015h`) supporting modern terminals (macOS Terminal.app, iTerm2, Alacritty, Kitty, Windows Terminal, xterm).
72
+ - Pure mouse coordinate calculation utilities (`src/utils/mouse.js`) with full test coverage in `test/unit.test.js`.
73
+
16
74
  ## [1.2.0] — 2026-09-01
17
75
 
18
76
  ### Added
@@ -118,7 +176,9 @@ First public release.
118
176
  - A WCAG contrast test (3:1 threshold) applied to every theme **after** conversion
119
177
  to xterm-256 — the colors are checked exactly as the user sees them.
120
178
 
121
- [Unreleased]: https://github.com/emaxe/tuigram/compare/v1.2.0...HEAD
179
+ [Unreleased]: https://github.com/emaxe/tuigram/compare/v1.4.0...HEAD
180
+ [1.4.0]: https://github.com/emaxe/tuigram/compare/v1.3.0...v1.4.0
181
+ [1.3.0]: https://github.com/emaxe/tuigram/compare/v1.2.0...v1.3.0
122
182
  [1.2.0]: https://github.com/emaxe/tuigram/compare/v1.1.0...v1.2.0
123
183
  [1.1.0]: https://github.com/emaxe/tuigram/compare/v1.0.1...v1.1.0
124
184
  [1.0.1]: https://github.com/emaxe/tuigram/compare/v1.0.0...v1.0.1
package/CHANGELOG.ru.md CHANGED
@@ -13,6 +13,68 @@
13
13
 
14
14
  - Английская локализация строк интерфейса TUI и CLI.
15
15
 
16
+ ## [1.4.0] — 2026-09-01
17
+
18
+ ### Добавлено
19
+
20
+ - Выделение сообщений в ленте чата: левый клик мышью помечает сообщение цветной полосой `▌`
21
+ слева от всех его строк. Стрелки `↑` / `↓` (и `k` / `j`) перемещают выделение между
22
+ сообщениями с автоподкруткой ленты; на самом верхнем сообщении подгружается предыдущая
23
+ страница истории.
24
+ - Меню действий над сообщением открывается правой кнопкой мыши, а также `[Enter]` над
25
+ выделенным сообщением. Прежде меню открывалось любым кликом, из-за чего сообщение нельзя
26
+ было просто выбрать.
27
+ - Выделенное сообщение стало целью действий: `[Ctrl+R]` отвечает именно на него, `[Ctrl+E]`
28
+ правит его (если оно ваше), `[Ctrl+A]` и кнопка `[Ctrl+A]` строки состояния открывают меню
29
+ для него. Без выделения действует прежнее поведение — последнее сообщение чата.
30
+ - Полноэкранный просмотр изображений по клику на превью в сообщении: сначала мгновенно
31
+ показывается пришедшая с сообщением миниатюра, затем она заменяется полноразмерной
32
+ версией. У видео и документов берётся самая крупная миниатюра, сам файл не качается.
33
+ Закрытие — `[Esc]`, `[Q]`, `[Enter]` или клик; при изменении размера терминала
34
+ изображение перерисовывается.
35
+ - Глобальное сочетание `[Ctrl+A]` (меню действий) теперь работает из любой панели, включая
36
+ поле ввода.
37
+ - Клавиша `[F12]` временно отдаёт мышь терминалу, чтобы можно было выделить и скопировать
38
+ текст, и возвращает захват повторным нажатием.
39
+
40
+ ### Исправлено
41
+
42
+ - Модальное окно закрывалось тем же кликом, которым его открыли: blessed рассылает клик
43
+ сначала элементу, а сразу за ним — экрану. Из-за этого мышью вовсе не работали кнопки
44
+ строки состояния `[F1]`, `[Ctrl+A]`, `[Ctrl+P]`, `[Ctrl+Q]`, логотип и заголовок чата в
45
+ шапке, а меню действий открывалось лишь при попадании клика в будущий прямоугольник окна.
46
+ - Клик по сообщению попадал не в то сообщение при прокрученной ленте: позиция бралась из
47
+ `getScroll()`, которая складывает сдвиг содержимого со служебным смещением blessed.
48
+ - Колесо мыши прокручивало ленту дважды за щелчок (собственный обработчик поверх
49
+ встроенного в blessed), а прокрутка вверх от нижнего края ленты первые несколько щелчков
50
+ не работала вовсе.
51
+ - `PageUp` / `PageDown` прокручивали ленту на удвоенное расстояние, когда она была в фокусе.
52
+ - Клики по подсказкам поля ввода были смещены примерно на 10 ячеек: клик по `[Ctrl+E] Правка`
53
+ вызывал команды, а клик по тексту «Введите сообщение…» включал режим ответа.
54
+ - Правый клик срабатывал как левый: открывал чат в списке, нажимал кнопки строки состояния
55
+ и вкладки фильтров.
56
+ - Разметка ленты «съезжала» на одну строку с каждым сообщением, из-за чего карта координат
57
+ расходилась с реальным текстом тем сильнее, чем длиннее история.
58
+ - Полноэкранный рендер картинки мог вытеснить и подменить её же миниатюру в ленте: размер
59
+ теперь входит в ключ кэша псевдографики.
60
+
61
+ ## [1.3.0] — 2026-09-01
62
+
63
+ ### Добавлено
64
+
65
+ - Полноценная поддержка управления мышью во всём TUI-интерфейсе:
66
+ - Фокусировка любой панели по клику (список чатов, лента сообщений, поле ввода).
67
+ - Открытие диалога в один клик по элементу списка.
68
+ - Плавная прокрутка списка чатов и ленты сообщений колесом мыши с автоматической подгрузкой старой истории при прокрутке вверх.
69
+ - Открытие меню действий над сообщением (реакции, ответ, редактирование, скачивание, удаление) по прямому клику на сообщение.
70
+ - Интерактивные вкладки фильтрации (`1:Все`, `2:ЛС`, `3:Группы`, `4:Каналы`, `5:Боты`, `6:Непроч`) и строка поиска в списке диалогов.
71
+ - Интерактивная нижняя строка состояния (`[Tab]`, `[1-6]`, `[/]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+P]`, `[Ctrl+Q]`).
72
+ - Интерактивный верхний заголовок: клик по логотипу открывает справку, по названию активного чата — сведения о чате, по статусу — информацию о сети.
73
+ - Контекстная строка поля ввода: клик сбрасывает режим ответа/редактирования либо запускает команды.
74
+ - Поддержка мыши во всех модальных окнах (клики по кнопкам, чекбоксам, файлам в проводнике) и закрытие модалок по клику мимо окна.
75
+ - Надёжная инициализация режимов мыши терминала (`\x1b[?1000h\x1b[?1002h\x1b[?1006h\x1b[?1015h`) с поддержкой современных эмуляторов (macOS Terminal.app, iTerm2, Alacritty, Kitty, Windows Terminal, xterm).
76
+ - Чистые утилиты расчёта координат мыши (`src/utils/mouse.js`) и покрытие юнит-тестами в `test/unit.test.js`.
77
+
16
78
  ## [1.2.0] — 2026-09-01
17
79
 
18
80
  ### Добавлено
@@ -120,7 +182,9 @@
120
182
  - Тест контраста по WCAG (порог 3:1) для каждой темы — **после** конверсии в xterm-256,
121
183
  то есть ровно в том виде, в каком цвет увидит пользователь.
122
184
 
123
- [Unreleased]: https://github.com/emaxe/tuigram/compare/v1.2.0...HEAD
185
+ [Unreleased]: https://github.com/emaxe/tuigram/compare/v1.4.0...HEAD
186
+ [1.4.0]: https://github.com/emaxe/tuigram/compare/v1.3.0...v1.4.0
187
+ [1.3.0]: https://github.com/emaxe/tuigram/compare/v1.2.0...v1.3.0
124
188
  [1.2.0]: https://github.com/emaxe/tuigram/compare/v1.1.0...v1.2.0
125
189
  [1.1.0]: https://github.com/emaxe/tuigram/compare/v1.0.1...v1.1.0
126
190
  [1.0.1]: https://github.com/emaxe/tuigram/compare/v1.0.0...v1.0.1
package/README.md CHANGED
@@ -52,6 +52,7 @@ TuiGram lets you use Telegram entirely from the terminal: browse your dialog lis
52
52
  - [Where files are stored](#-where-files-are-stored)
53
53
  - [Development](#-development)
54
54
  - [Keyboard shortcuts](#️-keyboard-shortcuts)
55
+ - [Mouse controls](#-mouse-controls)
55
56
  - [Slash commands](#-slash-commands-in-the-input-box)
56
57
  - [Sending files and images](#-sending-files-and-images)
57
58
  - [Themes](#-themes)
@@ -70,7 +71,8 @@ TuiGram lets you use Telegram entirely from the terminal: browse your dialog lis
70
71
 
71
72
  - **A complete interactive TUI**:
72
73
  - Two-pane adaptive layout (dialog list on the left, history and input box on the right);
73
- - Keyboard and mouse control (clicks, wheel scrolling);
74
+ - Keyboard and mouse control (left click selects a message, right click opens the action
75
+ menu, clicking an image opens it full screen, wheel scrolling);
74
76
  - Chat categories: All, Direct messages, Groups, Channels, Bots, Unread;
75
77
  - Instant search and filtering of chats by title and `@username` (`/`);
76
78
  - Live typing indicator ("… is typing");
@@ -432,26 +434,83 @@ node scripts/check-package.js # verifies that .env and the session do not leak
432
434
  |---|---|---|
433
435
  | `Tab` / `Shift+Tab` | Global | Cycle focus: dialog list → message feed → input box |
434
436
  | `↑` / `↓` | Dialog list | Select a chat |
437
+ | `↑` / `↓`, `k` / `j` | Message feed | Move the selection between messages |
435
438
  | `Enter` | Dialog list | Open the selected chat and load its history |
439
+ | `Enter` | Message feed | Action menu for the selected message |
436
440
  | `1` .. `6` | Dialog list | Switch category: `1:All`, `2:DM`, `3:Groups`, `4:Channels`, `5:Bots`, `6:Unread` |
437
441
  | `/` | Dialog list | Search / filter chats |
438
442
  | `Enter` | Input box | Send the typed message |
439
443
  | `Ctrl+J` | Input box | Insert a line break without sending |
440
- | `Ctrl+R` | Chat / Input | Reply to the last message |
441
- | `Ctrl+E` | Chat / Input | Edit your own last message |
442
- | `Ctrl+A` | Messages | Context action menu (reactions, delete, download, reply) |
444
+ | `Ctrl+R` | Chat / Input | Reply to the selected message, or the last one |
445
+ | `Ctrl+E` | Chat / Input | Edit the selected message of yours, or your last one |
446
+ | `Ctrl+A` | Global | Context action menu (reactions, delete, download, reply) |
443
447
  | `Ctrl+O` | Global | Send a file / photo / document |
444
448
  | `Ctrl+F` | Send dialog | File browser (navigate folders) |
445
449
  | `Ctrl+D` | Send dialog | Send uncompressed, as a document |
446
450
  | `Ctrl+P` | Global | Info about the current chat (ID, members, links) |
447
451
  | `PageUp` / `Ctrl+U` | History | Scroll up / load older history |
448
452
  | `PageDown` / `Ctrl+D` | History | Scroll down |
453
+ | `Home` / `End` | History | Jump to the top (loads history) / to the last message |
449
454
  | `Esc` | Modals | Close the modal / cancel reply or edit |
450
455
  | `F1` or `?` | Global | Help window with every shortcut |
456
+ | `F12` | Global | Hand the mouse to the terminal for text selection (press again to return) |
451
457
  | `Ctrl+Q` / `Ctrl+C` | Global | Safely exit the client |
452
458
 
453
459
  ---
454
460
 
461
+ ## 🖱 Mouse controls
462
+
463
+ TuiGram fully supports mouse interaction in terminals that support mouse reporting (macOS Terminal, iTerm2, Alacritty, Kitty, Windows Terminal, Linux virtual terminals):
464
+
465
+ | Action | Target | Result |
466
+ |---|---|---|
467
+ | Left click | Left / Middle / Bottom panel | Focus the clicked panel |
468
+ | Left click | Chat item in dialog list | Immediately select and open the chat |
469
+ | Left click | Filter tabs `1:Все` .. `6:Непроч` | Switch chat category filter |
470
+ | Left click | Search bar `[/]` | Focus search box and clear placeholder |
471
+ | Wheel scroll | Dialog list / Message feed | Smooth scroll (scrolling up to the top loads older messages) |
472
+ | **Left click** | **Message in the feed** | **Select the message — a `▌` bar marks it on the left** |
473
+ | **Right click** | **Message in the feed** | **Open the action menu (reactions, reply, edit, download, delete)** |
474
+ | **Left click** | **Image preview inside a message** | **Open the image full screen** |
475
+ | Left click | Empty area of the feed | Focus the message feed |
476
+ | Left click | Header logo 🚀 | Open Help window |
477
+ | Left click | Header active chat title | Open Chat Info window |
478
+ | Left click | Header connection badge | Show active network status |
479
+ | Left click | Input context bar | Cancel reply/edit mode, trigger `[Ctrl+R]` / `[Ctrl+E]` or insert `/` |
480
+ | Left click | Status bar items | Trigger corresponding action (`[Tab]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+Q]`, etc.) |
481
+ | Click | Outside any modal window | Dismiss / close modal |
482
+ | Click | Buttons in modals | Click buttons (`[ Отправить ]`, `[ Отмена ]`, `[ Закрыть ]`, toggle checkboxes) |
483
+
484
+ ### Message selection
485
+
486
+ A left click selects a message: a colored `▌` bar appears to the left of all its lines.
487
+ `↑` / `↓` (and `k` / `j`) move the selection between messages and scroll it into view;
488
+ reaching the topmost message loads the previous page of history. Plain scrolling stays on
489
+ `PageUp` / `PageDown`, the wheel and `Home` / `End`.
490
+
491
+ The selected message becomes the target for actions: `[Enter]` and right click open the
492
+ action menu, `[Ctrl+R]` replies to it, `[Ctrl+E]` edits it (when it is yours). With nothing
493
+ selected these actions apply to the last message in the chat, as before.
494
+
495
+ ### Full-screen image viewer
496
+
497
+ A left click on a preview opens the image across the whole terminal. The thumbnail that
498
+ already arrived with the message is shown instantly, then it is replaced by the full-size
499
+ version as soon as it downloads. For videos and documents the largest available thumbnail
500
+ is shown — the file itself is never downloaded. Close with `[Esc]`, `[Q]`, `[Enter]` or a
501
+ click anywhere.
502
+
503
+ ### Right click and terminal text selection
504
+
505
+ > **macOS Terminal.app** captures the right click for its own context menu and never passes
506
+ > it to the application. Use `[Enter]` or `[Ctrl+A]` on the selected message there. Right
507
+ > click works in iTerm2, Ghostty, Alacritty, Kitty and Windows Terminal.
508
+
509
+ While the app captures the mouse, the terminal cannot select text for copying. Press `[F12]`
510
+ to hand the mouse back to the terminal, and `[F12]` again to return control to the interface.
511
+
512
+ ---
513
+
455
514
  ## 💬 Slash commands in the input box
456
515
 
457
516
  Quick commands are available in the message input box (they start with `/`):
package/README.ru.md CHANGED
@@ -48,6 +48,7 @@
48
48
  - [Где хранятся файлы](#-где-хранятся-файлы)
49
49
  - [Разработка](#-разработка)
50
50
  - [Горячие клавиши](#️-горячие-клавиши)
51
+ - [Управление мышью](#-управление-мышью)
51
52
  - [Слэш-команды](#-слэш-команды-в-поле-ввода)
52
53
  - [Отправка файлов и картинок](#-отправка-файлов-и-картинок)
53
54
  - [Темы оформления](#-темы-оформления)
@@ -66,7 +67,8 @@
66
67
 
67
68
  - **Полноценный интерактивный TUI**:
68
69
  - Двухпанельный адаптивный интерфейс (список чатов слева + история и поле ввода справа);
69
- - Поддержка управления клавиатурой и мышью (клики, прокрутка колесом);
70
+ - Поддержка управления клавиатурой и мышью (левый клик — выделение сообщения,
71
+ правый — меню действий, клик по картинке — просмотр на весь экран, колесо — прокрутка);
70
72
  - Категории чатов: Все, Личные (ЛС), Группы, Каналы, Боты, Непрочитанные;
71
73
  - Мгновенный поиск и фильтрация чатов по названию и `@username` (`/`);
72
74
  - Отображение статуса набора текста («Собеседник печатает...») в реальном времени;
@@ -424,26 +426,84 @@ node scripts/check-package.js # проверка, что в пакет не у
424
426
  |---|---|---|
425
427
  | `Tab` / `Shift+Tab` | Глобально | Фокус по кругу: Список диалогов → Лента сообщений → Поле ввода |
426
428
  | `↑` / `↓` | Список диалогов | Выбор чата |
429
+ | `↑` / `↓`, `k` / `j` | Лента сообщений | Перемещение выделения между сообщениями |
427
430
  | `Enter` | Список диалогов | Открыть выбранный чат и загрузить историю |
431
+ | `Enter` | Лента сообщений | Меню действий над выделенным сообщением |
428
432
  | `1` .. `6` | Список диалогов | Переключение категорий: `1:Все`, `2:ЛС`, `3:Группы`, `4:Каналы`, `5:Боты`, `6:Непроч` |
429
433
  | `/` | Список диалогов | Поиск / фильтрация чатов |
430
434
  | `Enter` | Поле ввода | Отправить набранное сообщение |
431
435
  | `Ctrl+J` | Поле ввода | Перенос строки без отправки |
432
- | `Ctrl+R` | Чат / Ввод | Ответить (Reply) на последнее сообщение |
433
- | `Ctrl+E` | Чат / Ввод | Редактировать своё последнее сообщение |
434
- | `Ctrl+A` | Сообщения | Контекстное меню действий (Реакции, Удаление, Скачивание, Ответ) |
436
+ | `Ctrl+R` | Чат / Ввод | Ответить (Reply) на выделенное сообщение, иначе — на последнее |
437
+ | `Ctrl+E` | Чат / Ввод | Редактировать выделенное своё сообщение, иначе — последнее своё |
438
+ | `Ctrl+A` | Глобально | Контекстное меню действий (Реакции, Удаление, Скачивание, Ответ) |
435
439
  | `Ctrl+O` | Глобально | Отправить файл / фото / документ |
436
440
  | `Ctrl+F` | Окно отправки | Обзор файлов (навигация по папкам) |
437
441
  | `Ctrl+D` | Окно отправки | Отправить без сжатия, документом |
438
442
  | `Ctrl+P` | Глобально | Информация о текущем чате (ID, участники, ссылки) |
439
443
  | `PageUp` / `Ctrl+U`| История | Прокрутка вверх / подгрузка старой истории |
440
444
  | `PageDown` / `Ctrl+D`| История | Прокрутка вниз |
445
+ | `Home` / `End` | История | В начало ленты (подгрузка истории) / к последнему сообщению |
441
446
  | `Esc` | Модальные окна | Закрыть модальное окно / отменить Reply/Edit |
442
447
  | `F1` или `?` | Глобально | Окно справки со всеми горячими клавишами |
448
+ | `F12` | Глобально | Отдать мышь терминалу для выделения текста (повторно — вернуть) |
443
449
  | `Ctrl+Q` / `Ctrl+C` | Глобально | Безопасный выход из клиента |
444
450
 
445
451
  ---
446
452
 
453
+ ## 🖱 Управление мышью
454
+
455
+ TuiGram полноценно поддерживает управление мышью во всех современных терминалах (Terminal.app, iTerm2, Alacritty, Kitty, Windows Terminal, xterm):
456
+
457
+ | Действие | Где | Результат |
458
+ |---|---|---|
459
+ | Левый клик | Левая / Центральная / Нижняя панель | Переключение фокуса на выбранную панель |
460
+ | Левый клик | Диалог в списке чатов | Мгновенный выбор и открытие чата |
461
+ | Левый клик | Вкладки `1:Все` .. `6:Непроч` | Переключение фильтра категории чатов |
462
+ | Левый клик | Поле поиска `[/]` | Фокус в поиск с очисткой подсказки |
463
+ | Колесо мыши | Список чатов / Лента сообщений | Плавная прокрутка (прокрутка вверх в ленте подгружает историю) |
464
+ | **Левый клик** | **Сообщение в ленте** | **Выделение сообщения — оно помечается полосой `▌` слева** |
465
+ | **Правый клик** | **Сообщение в ленте** | **Меню действий (реакции, ответ, редактирование, скачивание, удаление)** |
466
+ | **Левый клик** | **Превью изображения в сообщении** | **Открытие картинки на весь экран** |
467
+ | Левый клик | Пустое место в ленте | Переключение фокуса на ленту сообщений |
468
+ | Левый клик | Логотип 🚀 в шапке | Открытие окна справки |
469
+ | Левый клик | Название активного чата в шапке | Открытие информации о текущем чате |
470
+ | Левый клик | Индикатор статуса в шапке | Просмотр статуса MTProto подключения |
471
+ | Левый клик | Контекстная плашка ввода | Сброс режима ответа/редактирования, `[Ctrl+R]`, `[Ctrl+E]` или вставка `/` |
472
+ | Левый клик | Кнопки строки состояния | Быстрый вызов действий (`[Tab]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+Q]` и др.) |
473
+ | Клик | Мимо любого модального окна | Закрытие модального окна |
474
+ | Клик | Кнопки в модалках | Нажатие кнопки (`[ Отправить ]`, `[ Отмена ]`, `[ Закрыть ]`, чекбоксы) |
475
+
476
+ ### Выделение сообщений
477
+
478
+ Левый клик выделяет сообщение: слева от всех его строк появляется цветная полоса `▌`.
479
+ Стрелки `↑` / `↓` (и `k` / `j`) в ленте двигают выделение между сообщениями и подкручивают
480
+ ленту к выбранному; дойдя до самого верхнего сообщения, клиент подгружает предыдущую
481
+ страницу истории. Прокрутка при этом остаётся на `PageUp` / `PageDown`, колесе и `Home` / `End`.
482
+
483
+ Выделенное сообщение становится целью действий: `[Enter]` и правый клик открывают меню
484
+ действий, `[Ctrl+R]` отвечает именно на него, `[Ctrl+E]` правит его (если оно ваше).
485
+ Без выделения все эти действия, как и раньше, применяются к последнему сообщению чата.
486
+
487
+ ### Просмотр изображений на весь экран
488
+
489
+ Левый клик по превью открывает изображение во весь терминал. Сначала мгновенно
490
+ показывается миниатюра, уже пришедшая вместе с сообщением, затем она заменяется
491
+ полноразмерной версией, как только та скачается. У видео и документов показывается
492
+ самая крупная миниатюра — сам файл не качается. Закрыть: `[Esc]`, `[Q]`, `[Enter]`
493
+ или клик в любом месте.
494
+
495
+ ### Правая кнопка и выделение текста терминалом
496
+
497
+ > **macOS Terminal.app** перехватывает правый клик под собственное контекстное меню и не
498
+ > передаёт его приложению. Там пользуйтесь `[Enter]` или `[Ctrl+A]` над выделенным
499
+ > сообщением. В iTerm2, Ghostty, Alacritty, Kitty и Windows Terminal правый клик работает.
500
+
501
+ Пока мышь захвачена приложением, терминал не даёт выделять текст для копирования.
502
+ Нажмите `[F12]`, чтобы временно отдать мышь терминалу, и `[F12]` ещё раз, чтобы вернуть
503
+ управление интерфейсу.
504
+
505
+ ---
506
+
447
507
  ## 💬 Слэш-команды в поле ввода
448
508
 
449
509
  В поле ввода сообщения доступны быстрые команды (начинаются с `/`):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emaxe/tuigram",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Полнофункциональный TUI & CLI клиент Telegram на Node.js на базе MTProto (teleproto)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -6,6 +6,39 @@ import { renderStrippedThumbnail, renderImageBuffer } from "../utils/image.js";
6
6
 
7
7
  const { FloodWaitError } = errors;
8
8
 
9
+ /**
10
+ * Синхронно рисует миниатюру, встроенную в само сообщение (PhotoStrippedSize).
11
+ * Сеть не нужна — байты уже пришли вместе с сообщением, поэтому картинку можно
12
+ * показать мгновенно, пока качается полноразмерная версия.
13
+ * @param {object} rawMessage
14
+ * @param {object} [options]
15
+ * @param {number} [options.maxWidth]
16
+ * @param {number} [options.maxHeight]
17
+ * @param {boolean} [options.useCache=true] класть результат в кэш псевдографики
18
+ * @returns {string} разметка blessed или пустая строка
19
+ */
20
+ export function renderMessageThumbnail(rawMessage, {
21
+ maxWidth = config.imageMaxWidth,
22
+ maxHeight = config.imageMaxHeight,
23
+ useCache = true,
24
+ } = {}) {
25
+ const media = rawMessage?.media;
26
+ if (!media) return "";
27
+
28
+ const photo = media.photo;
29
+ const doc = media.document;
30
+ const sizes = photo?.sizes || doc?.thumbs || [];
31
+ const stripped = sizes.find((s) => s?.className === "PhotoStrippedSize" || s?.type === "i" || (s?.bytes && s.bytes.length > 0));
32
+ if (!stripped?.bytes) return "";
33
+
34
+ // Полноэкранные рендеры не кэшируем: одна такая строка весит сотни килобайт
35
+ const cacheKey = useCache
36
+ ? (photo?.id ? `photo_${photo.id}` : (doc?.id ? `doc_${doc.id}` : `msg_${rawMessage.id}`))
37
+ : undefined;
38
+
39
+ return renderStrippedThumbnail(stripped.bytes, { maxWidth, maxHeight, cacheKey }) || "";
40
+ }
41
+
9
42
  /**
10
43
  * Преобразует объект Message из MTProto в нормализованный объект для TUI.
11
44
  * @param {object} message
@@ -41,25 +74,9 @@ export function normalizeMessage(message) {
41
74
  }
42
75
 
43
76
  // Извлечение и рендеринг PhotoStrippedSize в псевдографику
44
- let imagePreview = null;
45
- if (config.showImages && message.media) {
46
- const media = message.media;
47
- const photo = media.photo;
48
- const doc = media.document;
49
- const sizes = photo?.sizes || doc?.thumbs || [];
50
- const stripped = sizes.find((s) => s?.className === "PhotoStrippedSize" || s?.type === "i" || (s?.bytes && s.bytes.length > 0));
51
-
52
- if (stripped?.bytes) {
53
- const cacheKey = photo?.id
54
- ? `photo_${photo.id}`
55
- : (doc?.id ? `doc_${doc.id}` : `msg_${message.id}`);
56
- imagePreview = renderStrippedThumbnail(stripped.bytes, {
57
- maxWidth: config.imageMaxWidth,
58
- maxHeight: config.imageMaxHeight,
59
- cacheKey,
60
- }) || null;
61
- }
62
- }
77
+ const imagePreview = config.showImages
78
+ ? (renderMessageThumbnail(message) || null)
79
+ : null;
63
80
 
64
81
  return {
65
82
  id: message.id,
@@ -288,6 +305,68 @@ export async function markAsRead(client, rawPeer, maxId = 0) {
288
305
  }
289
306
  }
290
307
 
308
+ /**
309
+ * Выбирает самую крупную растровую миниатюру документа.
310
+ * PhotoPathSize (SVG-контур) и PhotoStrippedSize непригодны для полноэкранного показа.
311
+ * @param {Array<object>} thumbs
312
+ * @returns {object|null}
313
+ */
314
+ function pickLargestThumb(thumbs) {
315
+ if (!Array.isArray(thumbs) || thumbs.length === 0) return null;
316
+
317
+ const weight = (t) => {
318
+ if (typeof t?.size === "number") return t.size;
319
+ if (Array.isArray(t?.sizes) && t.sizes.length > 0) return Math.max(...t.sizes);
320
+ return 0;
321
+ };
322
+
323
+ const usable = thumbs.filter((t) => t?.className !== "PhotoPathSize" && weight(t) > 0);
324
+ if (usable.length === 0) return null;
325
+
326
+ return usable.reduce((best, t) => (weight(t) > weight(best) ? t : best), usable[0]);
327
+ }
328
+
329
+ /**
330
+ * Загружает изображение сообщения в максимальном доступном качестве — для просмотра
331
+ * на весь экран.
332
+ *
333
+ * Фото качается оригиналом. У документа (видео, gif, файл) качается только самая
334
+ * крупная миниатюра: сам файл может весить сотни мегабайт и всё равно не рисуется.
335
+ *
336
+ * @param {import("teleproto").TelegramClient} client
337
+ * @param {object} rawMessage
338
+ * @returns {Promise<{ buffer: Buffer, mimeType: string }>}
339
+ */
340
+ export async function downloadImageBuffer(client, rawMessage) {
341
+ const media = rawMessage?.media;
342
+ if (!media) {
343
+ throw new Error("У сообщения нет изображения.");
344
+ }
345
+
346
+ if (media.className === "MessageMediaPhoto") {
347
+ // Без thumb teleproto отдаёт самый большой размер фотографии
348
+ const buffer = await client.downloadMedia(media, {});
349
+ if (!buffer || buffer.length === 0) {
350
+ throw new Error("Не удалось загрузить изображение.");
351
+ }
352
+ return { buffer, mimeType: "image/jpeg" };
353
+ }
354
+
355
+ if (media.className === "MessageMediaDocument") {
356
+ const thumb = pickLargestThumb(media.document?.thumbs);
357
+ if (!thumb) {
358
+ throw new Error("У вложения нет пригодной для показа миниатюры.");
359
+ }
360
+ const buffer = await client.downloadMedia(media, { thumb });
361
+ if (!buffer || buffer.length === 0) {
362
+ throw new Error("Не удалось загрузить миниатюру вложения.");
363
+ }
364
+ return { buffer, mimeType: "image/jpeg" };
365
+ }
366
+
367
+ throw new Error("Этот тип вложения нельзя показать как изображение.");
368
+ }
369
+
291
370
  /**
292
371
  * Асинхронно загружает и декодирует превью изображения сообщения через MTProto.
293
372
  * @param {import("teleproto").TelegramClient} client