Show unread count in private inbox

This commit is contained in:
m1ngsama 2026-05-29 18:06:45 +08:00
parent 845657e3c2
commit d7531f9305
9 changed files with 32 additions and 7 deletions

View file

@ -116,6 +116,8 @@ shows incoming and sent private messages newest-first; press `r` to refresh it
manually, and it refreshes when a new private message arrives while the inbox manually, and it refreshes when a new private message arrives while the inbox
is open. `:reply text` and `:r text` send to the latest private-message peer. is open. `:reply text` and `:r text` send to the latest private-message peer.
Unread incoming private messages are marked with `*` until `:inbox` renders. Unread incoming private messages are marked with `*` until `:inbox` renders.
The inbox title shows a transient unread count when new private messages are
present.
`:inbox clear` removes private messages and the reply target for this session. `:inbox clear` removes private messages and the reply target for this session.
Private messages are per-session only and are not written to `messages.log`. Private messages are per-session only and are not written to `messages.log`.

View file

@ -168,8 +168,9 @@ persisted to `messages.log` and are not included in exec `tail`, exec `dump`,
Each participant keeps a bounded in-memory `:inbox` for the current session. Each participant keeps a bounded in-memory `:inbox` for the current session.
Recipients see incoming private messages; senders see local sent-message Recipients see incoming private messages; senders see local sent-message
copies. Unread incoming messages are marked with `*` until `:inbox` renders. copies. Unread incoming messages are marked with `*` until `:inbox` renders.
`:inbox` displays newest messages first, can be refreshed with `r`, and `:inbox` displays newest messages first, shows a transient unread count, can
refreshes automatically while open when a new private message arrives. be refreshed with `r`, and refreshes automatically while open when a new
private message arrives.
`:inbox clear` removes the current session's private messages, unread count, `:inbox clear` removes the current session's private messages, unread count,
and reply target. and reply target.

View file

@ -38,9 +38,9 @@ The product path should stay short:
reconnect. reconnect.
- `:inbox` is live enough for normal chat use: it can be refreshed with `r` - `:inbox` is live enough for normal chat use: it can be refreshed with `r`
and refreshes automatically when a new private message arrives while the and refreshes automatically when a new private message arrives while the
inbox is open. Incoming unread messages are marked with `*` until the inbox inbox is open. Incoming unread messages are marked with `*` and counted in
renders them. `:inbox clear` removes private messages and the reply target the inbox title until the inbox renders them. `:inbox clear` removes private
for the current session. messages and the reply target for the current session.
- `:reply` / `:r` keeps the private-message path keyboard-short: it answers - `:reply` / `:r` keeps the private-message path keyboard-short: it answers
the latest private-message peer in the current session without retyping a the latest private-message peer in the current session without retyping a
username. username.

View file

@ -50,6 +50,7 @@ typedef enum {
I18N_INBOX_EMPTY, I18N_INBOX_EMPTY,
I18N_INBOX_SENT_TO_FORMAT, I18N_INBOX_SENT_TO_FORMAT,
I18N_INBOX_CLEARED, I18N_INBOX_CLEARED,
I18N_INBOX_UNREAD_FORMAT,
I18N_NICK_INVALID, I18N_NICK_INVALID,
I18N_NICK_TAKEN_FORMAT, I18N_NICK_TAKEN_FORMAT,
I18N_NICK_UNCHANGED, I18N_NICK_UNCHANGED,

View file

@ -138,9 +138,11 @@ static void append_inbox_output(client_t *client, char *output,
size_t buf_size, size_t *pos) { size_t buf_size, size_t *pos) {
whisper_t snapshot[WHISPER_INBOX_SIZE]; whisper_t snapshot[WHISPER_INBOX_SIZE];
int snap_count; int snap_count;
int unread_count;
pthread_mutex_lock(&client->whisper_lock); pthread_mutex_lock(&client->whisper_lock);
snap_count = client->whisper_inbox_count; snap_count = client->whisper_inbox_count;
unread_count = client->unread_whispers;
memcpy(snapshot, client->whisper_inbox, memcpy(snapshot, client->whisper_inbox,
snap_count * sizeof(whisper_t)); snap_count * sizeof(whisper_t));
for (int i = 0; i < snap_count; i++) { for (int i = 0; i < snap_count; i++) {
@ -150,9 +152,18 @@ static void append_inbox_output(client_t *client, char *output,
pthread_mutex_unlock(&client->whisper_lock); pthread_mutex_unlock(&client->whisper_lock);
buffer_appendf(output, buf_size, pos, buffer_appendf(output, buf_size, pos,
"\033[1;36m%s\033[0m \033[2;37m· %d\033[0m\n", "\033[1;36m%s\033[0m \033[2;37m· %d",
i18n_text(client->ui_lang, I18N_INBOX_TITLE), i18n_text(client->ui_lang, I18N_INBOX_TITLE),
snap_count); snap_count);
if (unread_count > 0) {
buffer_appendf(output, buf_size, pos,
" · ");
buffer_appendf(output, buf_size, pos,
i18n_text(client->ui_lang,
I18N_INBOX_UNREAD_FORMAT),
unread_count);
}
buffer_appendf(output, buf_size, pos, "\033[0m\n");
if (snap_count == 0) { if (snap_count == 0) {
buffer_appendf(output, buf_size, pos, buffer_appendf(output, buf_size, pos,
" \033[2;37m%s\033[0m\n", " \033[2;37m%s\033[0m\n",

View file

@ -141,6 +141,10 @@ static const i18n_string_t text_catalog[I18N_TEXT_COUNT] = {
"Private messages cleared\n", "Private messages cleared\n",
"私信已清空\n" "私信已清空\n"
), ),
[I18N_INBOX_UNREAD_FORMAT] = I18N_STRING(
"%d new",
"%d 新"
),
[I18N_NICK_INVALID] = I18N_STRING( [I18N_NICK_INVALID] = I18N_STRING(
"Invalid username\n", "Invalid username\n",
"用户名无效\n" "用户名无效\n"

View file

@ -288,7 +288,9 @@ fi
BOB_PID="" BOB_PID=""
if grep -q '.*alice.*private lifecycle second' "$STATE_DIR/bob.log" && if grep -q '.*alice.*private lifecycle second' "$STATE_DIR/bob.log" &&
grep -q '2 新' "$STATE_DIR/bob.log" &&
grep -q '\*.*alice.*private lifecycle second' "$STATE_DIR/bob.log" && grep -q '\*.*alice.*private lifecycle second' "$STATE_DIR/bob.log" &&
grep -q '1 新' "$STATE_DIR/alice.log" &&
grep -q '\*.*bob.*private lifecycle reply' "$STATE_DIR/alice.log"; then grep -q '\*.*bob.*private lifecycle reply' "$STATE_DIR/alice.log"; then
echo "✓ unread private messages are visibly marked in inbox" echo "✓ unread private messages are visibly marked in inbox"
PASS=$((PASS + 1)) PASS=$((PASS + 1))

View file

@ -164,6 +164,10 @@ TEST(text_lookup_matches_language) {
"cleared") != NULL); "cleared") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_INBOX_CLEARED), assert(strstr(i18n_text(UI_LANG_ZH, I18N_INBOX_CLEARED),
"清空") != NULL); "清空") != NULL);
assert(strstr(i18n_text(UI_LANG_EN, I18N_INBOX_UNREAD_FORMAT),
"new") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_INBOX_UNREAD_FORMAT),
"") != NULL);
assert(strstr(i18n_text(UI_LANG_EN, I18N_SEARCH_HEADER_FORMAT), assert(strstr(i18n_text(UI_LANG_EN, I18N_SEARCH_HEADER_FORMAT),
"Search") != NULL); "Search") != NULL);
assert(strstr(i18n_text(UI_LANG_ZH, I18N_SEARCH_HEADER_FORMAT), assert(strstr(i18n_text(UI_LANG_ZH, I18N_SEARCH_HEADER_FORMAT),

2
tnt.1
View file

@ -256,7 +256,7 @@ page shows incoming messages and local sent-message copies for the current
session. It refreshes automatically when a new private message arrives while session. It refreshes automatically when a new private message arrives while
it is open. Incoming unread messages are marked with it is open. Incoming unread messages are marked with
.B * .B *
until the inbox renders them. Use and counted in the inbox title until the inbox renders them. Use
.B :reply .B :reply
or or
.B :r .B :r