@emaxe/tuigram 1.3.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 +43 -1
- package/CHANGELOG.ru.md +47 -1
- package/README.md +50 -14
- package/README.ru.md +51 -14
- package/package.json +1 -1
- package/src/telegram/messages.js +98 -19
- package/src/ui/app.js +95 -40
- package/src/ui/components/chatList.js +4 -2
- package/src/ui/components/chatView.js +225 -61
- package/src/ui/components/header.js +2 -1
- package/src/ui/components/inputBox.js +2 -1
- package/src/ui/components/modals/actionModal.js +6 -14
- package/src/ui/components/modals/chatInfoModal.js +3 -13
- package/src/ui/components/modals/confirmModal.js +3 -13
- package/src/ui/components/modals/fileModal.js +6 -13
- package/src/ui/components/modals/filePickerModal.js +3 -13
- package/src/ui/components/modals/helpModal.js +13 -18
- package/src/ui/components/modals/imageViewerModal.js +172 -0
- package/src/ui/components/statusBar.js +2 -1
- package/src/ui/modalMouse.js +43 -0
- package/src/ui/screen.js +60 -20
- package/src/utils/image.js +8 -8
- package/src/utils/mouse.js +61 -74
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,47 @@ 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
|
+
|
|
16
57
|
## [1.3.0] — 2026-09-01
|
|
17
58
|
|
|
18
59
|
### Added
|
|
@@ -135,7 +176,8 @@ First public release.
|
|
|
135
176
|
- A WCAG contrast test (3:1 threshold) applied to every theme **after** conversion
|
|
136
177
|
to xterm-256 — the colors are checked exactly as the user sees them.
|
|
137
178
|
|
|
138
|
-
[Unreleased]: https://github.com/emaxe/tuigram/compare/v1.
|
|
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
|
|
139
181
|
[1.3.0]: https://github.com/emaxe/tuigram/compare/v1.2.0...v1.3.0
|
|
140
182
|
[1.2.0]: https://github.com/emaxe/tuigram/compare/v1.1.0...v1.2.0
|
|
141
183
|
[1.1.0]: https://github.com/emaxe/tuigram/compare/v1.0.1...v1.1.0
|
package/CHANGELOG.ru.md
CHANGED
|
@@ -13,6 +13,51 @@
|
|
|
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
|
+
|
|
16
61
|
## [1.3.0] — 2026-09-01
|
|
17
62
|
|
|
18
63
|
### Добавлено
|
|
@@ -137,7 +182,8 @@
|
|
|
137
182
|
- Тест контраста по WCAG (порог 3:1) для каждой темы — **после** конверсии в xterm-256,
|
|
138
183
|
то есть ровно в том виде, в каком цвет увидит пользователь.
|
|
139
184
|
|
|
140
|
-
[Unreleased]: https://github.com/emaxe/tuigram/compare/v1.
|
|
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
|
|
141
187
|
[1.3.0]: https://github.com/emaxe/tuigram/compare/v1.2.0...v1.3.0
|
|
142
188
|
[1.2.0]: https://github.com/emaxe/tuigram/compare/v1.1.0...v1.2.0
|
|
143
189
|
[1.1.0]: https://github.com/emaxe/tuigram/compare/v1.0.1...v1.1.0
|
package/README.md
CHANGED
|
@@ -71,7 +71,8 @@ TuiGram lets you use Telegram entirely from the terminal: browse your dialog lis
|
|
|
71
71
|
|
|
72
72
|
- **A complete interactive TUI**:
|
|
73
73
|
- Two-pane adaptive layout (dialog list on the left, history and input box on the right);
|
|
74
|
-
- Keyboard and mouse control (
|
|
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);
|
|
75
76
|
- Chat categories: All, Direct messages, Groups, Channels, Bots, Unread;
|
|
76
77
|
- Instant search and filtering of chats by title and `@username` (`/`);
|
|
77
78
|
- Live typing indicator ("… is typing");
|
|
@@ -433,22 +434,26 @@ node scripts/check-package.js # verifies that .env and the session do not leak
|
|
|
433
434
|
|---|---|---|
|
|
434
435
|
| `Tab` / `Shift+Tab` | Global | Cycle focus: dialog list → message feed → input box |
|
|
435
436
|
| `↑` / `↓` | Dialog list | Select a chat |
|
|
437
|
+
| `↑` / `↓`, `k` / `j` | Message feed | Move the selection between messages |
|
|
436
438
|
| `Enter` | Dialog list | Open the selected chat and load its history |
|
|
439
|
+
| `Enter` | Message feed | Action menu for the selected message |
|
|
437
440
|
| `1` .. `6` | Dialog list | Switch category: `1:All`, `2:DM`, `3:Groups`, `4:Channels`, `5:Bots`, `6:Unread` |
|
|
438
441
|
| `/` | Dialog list | Search / filter chats |
|
|
439
442
|
| `Enter` | Input box | Send the typed message |
|
|
440
443
|
| `Ctrl+J` | Input box | Insert a line break without sending |
|
|
441
|
-
| `Ctrl+R` | Chat / Input | Reply to the
|
|
442
|
-
| `Ctrl+E` | Chat / Input | Edit your
|
|
443
|
-
| `Ctrl+A` |
|
|
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) |
|
|
444
447
|
| `Ctrl+O` | Global | Send a file / photo / document |
|
|
445
448
|
| `Ctrl+F` | Send dialog | File browser (navigate folders) |
|
|
446
449
|
| `Ctrl+D` | Send dialog | Send uncompressed, as a document |
|
|
447
450
|
| `Ctrl+P` | Global | Info about the current chat (ID, members, links) |
|
|
448
451
|
| `PageUp` / `Ctrl+U` | History | Scroll up / load older history |
|
|
449
452
|
| `PageDown` / `Ctrl+D` | History | Scroll down |
|
|
453
|
+
| `Home` / `End` | History | Jump to the top (loads history) / to the last message |
|
|
450
454
|
| `Esc` | Modals | Close the modal / cancel reply or edit |
|
|
451
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) |
|
|
452
457
|
| `Ctrl+Q` / `Ctrl+C` | Global | Safely exit the client |
|
|
453
458
|
|
|
454
459
|
---
|
|
@@ -459,20 +464,51 @@ TuiGram fully supports mouse interaction in terminals that support mouse reporti
|
|
|
459
464
|
|
|
460
465
|
| Action | Target | Result |
|
|
461
466
|
|---|---|---|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
|
465
|
-
|
|
|
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 |
|
|
466
471
|
| Wheel scroll | Dialog list / Message feed | Smooth scroll (scrolling up to the top loads older messages) |
|
|
467
|
-
|
|
|
468
|
-
|
|
|
469
|
-
|
|
|
470
|
-
|
|
|
471
|
-
|
|
|
472
|
-
|
|
|
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.) |
|
|
473
481
|
| Click | Outside any modal window | Dismiss / close modal |
|
|
474
482
|
| Click | Buttons in modals | Click buttons (`[ Отправить ]`, `[ Отмена ]`, `[ Закрыть ]`, toggle checkboxes) |
|
|
475
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
|
+
|
|
476
512
|
---
|
|
477
513
|
|
|
478
514
|
## 💬 Slash commands in the input box
|
package/README.ru.md
CHANGED
|
@@ -67,7 +67,8 @@
|
|
|
67
67
|
|
|
68
68
|
- **Полноценный интерактивный TUI**:
|
|
69
69
|
- Двухпанельный адаптивный интерфейс (список чатов слева + история и поле ввода справа);
|
|
70
|
-
- Поддержка управления клавиатурой и мышью (
|
|
70
|
+
- Поддержка управления клавиатурой и мышью (левый клик — выделение сообщения,
|
|
71
|
+
правый — меню действий, клик по картинке — просмотр на весь экран, колесо — прокрутка);
|
|
71
72
|
- Категории чатов: Все, Личные (ЛС), Группы, Каналы, Боты, Непрочитанные;
|
|
72
73
|
- Мгновенный поиск и фильтрация чатов по названию и `@username` (`/`);
|
|
73
74
|
- Отображение статуса набора текста («Собеседник печатает...») в реальном времени;
|
|
@@ -425,22 +426,26 @@ node scripts/check-package.js # проверка, что в пакет не у
|
|
|
425
426
|
|---|---|---|
|
|
426
427
|
| `Tab` / `Shift+Tab` | Глобально | Фокус по кругу: Список диалогов → Лента сообщений → Поле ввода |
|
|
427
428
|
| `↑` / `↓` | Список диалогов | Выбор чата |
|
|
429
|
+
| `↑` / `↓`, `k` / `j` | Лента сообщений | Перемещение выделения между сообщениями |
|
|
428
430
|
| `Enter` | Список диалогов | Открыть выбранный чат и загрузить историю |
|
|
431
|
+
| `Enter` | Лента сообщений | Меню действий над выделенным сообщением |
|
|
429
432
|
| `1` .. `6` | Список диалогов | Переключение категорий: `1:Все`, `2:ЛС`, `3:Группы`, `4:Каналы`, `5:Боты`, `6:Непроч` |
|
|
430
433
|
| `/` | Список диалогов | Поиск / фильтрация чатов |
|
|
431
434
|
| `Enter` | Поле ввода | Отправить набранное сообщение |
|
|
432
435
|
| `Ctrl+J` | Поле ввода | Перенос строки без отправки |
|
|
433
|
-
| `Ctrl+R` | Чат / Ввод | Ответить (Reply) на последнее
|
|
434
|
-
| `Ctrl+E` | Чат / Ввод | Редактировать своё последнее
|
|
435
|
-
| `Ctrl+A` |
|
|
436
|
+
| `Ctrl+R` | Чат / Ввод | Ответить (Reply) на выделенное сообщение, иначе — на последнее |
|
|
437
|
+
| `Ctrl+E` | Чат / Ввод | Редактировать выделенное своё сообщение, иначе — последнее своё |
|
|
438
|
+
| `Ctrl+A` | Глобально | Контекстное меню действий (Реакции, Удаление, Скачивание, Ответ) |
|
|
436
439
|
| `Ctrl+O` | Глобально | Отправить файл / фото / документ |
|
|
437
440
|
| `Ctrl+F` | Окно отправки | Обзор файлов (навигация по папкам) |
|
|
438
441
|
| `Ctrl+D` | Окно отправки | Отправить без сжатия, документом |
|
|
439
442
|
| `Ctrl+P` | Глобально | Информация о текущем чате (ID, участники, ссылки) |
|
|
440
443
|
| `PageUp` / `Ctrl+U`| История | Прокрутка вверх / подгрузка старой истории |
|
|
441
444
|
| `PageDown` / `Ctrl+D`| История | Прокрутка вниз |
|
|
445
|
+
| `Home` / `End` | История | В начало ленты (подгрузка истории) / к последнему сообщению |
|
|
442
446
|
| `Esc` | Модальные окна | Закрыть модальное окно / отменить Reply/Edit |
|
|
443
447
|
| `F1` или `?` | Глобально | Окно справки со всеми горячими клавишами |
|
|
448
|
+
| `F12` | Глобально | Отдать мышь терминалу для выделения текста (повторно — вернуть) |
|
|
444
449
|
| `Ctrl+Q` / `Ctrl+C` | Глобально | Безопасный выход из клиента |
|
|
445
450
|
|
|
446
451
|
---
|
|
@@ -451,20 +456,52 @@ TuiGram полноценно поддерживает управление мы
|
|
|
451
456
|
|
|
452
457
|
| Действие | Где | Результат |
|
|
453
458
|
|---|---|---|
|
|
454
|
-
|
|
|
455
|
-
|
|
|
456
|
-
|
|
|
457
|
-
|
|
|
459
|
+
| Левый клик | Левая / Центральная / Нижняя панель | Переключение фокуса на выбранную панель |
|
|
460
|
+
| Левый клик | Диалог в списке чатов | Мгновенный выбор и открытие чата |
|
|
461
|
+
| Левый клик | Вкладки `1:Все` .. `6:Непроч` | Переключение фильтра категории чатов |
|
|
462
|
+
| Левый клик | Поле поиска `[/]` | Фокус в поиск с очисткой подсказки |
|
|
458
463
|
| Колесо мыши | Список чатов / Лента сообщений | Плавная прокрутка (прокрутка вверх в ленте подгружает историю) |
|
|
459
|
-
|
|
|
460
|
-
|
|
|
461
|
-
|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
|
464
|
+
| **Левый клик** | **Сообщение в ленте** | **Выделение сообщения — оно помечается полосой `▌` слева** |
|
|
465
|
+
| **Правый клик** | **Сообщение в ленте** | **Меню действий (реакции, ответ, редактирование, скачивание, удаление)** |
|
|
466
|
+
| **Левый клик** | **Превью изображения в сообщении** | **Открытие картинки на весь экран** |
|
|
467
|
+
| Левый клик | Пустое место в ленте | Переключение фокуса на ленту сообщений |
|
|
468
|
+
| Левый клик | Логотип 🚀 в шапке | Открытие окна справки |
|
|
469
|
+
| Левый клик | Название активного чата в шапке | Открытие информации о текущем чате |
|
|
470
|
+
| Левый клик | Индикатор статуса в шапке | Просмотр статуса MTProto подключения |
|
|
471
|
+
| Левый клик | Контекстная плашка ввода | Сброс режима ответа/редактирования, `[Ctrl+R]`, `[Ctrl+E]` или вставка `/` |
|
|
472
|
+
| Левый клик | Кнопки строки состояния | Быстрый вызов действий (`[Tab]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+Q]` и др.) |
|
|
465
473
|
| Клик | Мимо любого модального окна | Закрытие модального окна |
|
|
466
474
|
| Клик | Кнопки в модалках | Нажатие кнопки (`[ Отправить ]`, `[ Отмена ]`, `[ Закрыть ]`, чекбоксы) |
|
|
467
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
|
+
|
|
468
505
|
---
|
|
469
506
|
|
|
470
507
|
## 💬 Слэш-команды в поле ввода
|
package/package.json
CHANGED
package/src/telegram/messages.js
CHANGED
|
@@ -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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|