mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 05:44:38 +08:00
Allow clearing private message inbox
This commit is contained in:
parent
2fca031362
commit
845657e3c2
13 changed files with 80 additions and 12 deletions
|
|
@ -99,6 +99,7 @@ Ctrl+C - Exit chat
|
||||||
:reply <text> - Reply to latest private message
|
:reply <text> - Reply to latest private message
|
||||||
:r <text> - Short alias for :reply
|
:r <text> - Short alias for :reply
|
||||||
:inbox - Show private messages
|
:inbox - Show private messages
|
||||||
|
:inbox clear - Clear private messages for this session
|
||||||
:last [N] - Show last N messages from history (max 50, default 10)
|
:last [N] - Show last N messages from history (max 50, default 10)
|
||||||
:search <keyword> - Search message history (shows last 15 matches)
|
:search <keyword> - Search message history (shows last 15 matches)
|
||||||
:mute-joins - Toggle join/leave system notifications
|
:mute-joins - Toggle join/leave system notifications
|
||||||
|
|
@ -115,6 +116,7 @@ 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.
|
||||||
|
`: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`.
|
||||||
|
|
||||||
**Special messages (INSERT mode)**
|
**Special messages (INSERT mode)**
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ Common commands:
|
||||||
:msg <user> <message> send private message
|
:msg <user> <message> send private message
|
||||||
:reply <message> reply to latest private message
|
:reply <message> reply to latest private message
|
||||||
:inbox show private messages
|
:inbox show private messages
|
||||||
|
:inbox clear clear private messages
|
||||||
:last [N] recent messages
|
:last [N] recent messages
|
||||||
:search <keyword> search message history
|
:search <keyword> search message history
|
||||||
:lang en|zh switch UI language
|
:lang en|zh switch UI language
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,8 @@ 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, can be refreshed with `r`, and
|
||||||
refreshes automatically while open when a new private message arrives.
|
refreshes automatically while open when a new private message arrives.
|
||||||
|
`:inbox clear` removes the current session's private messages, unread count,
|
||||||
|
and reply target.
|
||||||
|
|
||||||
### `help`
|
### `help`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ COMMANDS (COMMAND mode, prefix with :)
|
||||||
reply <text> reply to latest private message
|
reply <text> reply to latest private message
|
||||||
r <text> alias for reply
|
r <text> alias for reply
|
||||||
inbox show private messages, newest first
|
inbox show private messages, newest first
|
||||||
|
inbox clear clear private messages for this session
|
||||||
last [N] last N messages from log (default 10, max 50)
|
last [N] last N messages from log (default 10, max 50)
|
||||||
search <keyword> search full history (case-insensitive, 15 results)
|
search <keyword> search full history (case-insensitive, 15 results)
|
||||||
mute-joins toggle join/leave notifications
|
mute-joins toggle join/leave notifications
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ The product path should stay short:
|
||||||
- `: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 `*` until the inbox
|
||||||
renders them.
|
renders them. `:inbox clear` removes private 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.
|
||||||
|
|
@ -54,8 +55,8 @@ The product path should stay short:
|
||||||
- first user opens `?`, checks `:users`, sends a public message, scrolls, uses
|
- first user opens `?`, checks `:users`, sends a public message, scrolls, uses
|
||||||
`:last` and `:search`
|
`:last` and `:search`
|
||||||
- first user toggles `:mute-joins`, sends two `:msg` messages, receives a
|
- first user toggles `:mute-joins`, sends two `:msg` messages, receives a
|
||||||
`:reply`, confirms private-message copies in `:inbox`, changes nickname,
|
`:reply`, confirms private-message copies in `:inbox`, clears the inbox,
|
||||||
sends `/me`, and exits
|
changes nickname, sends `/me`, and exits
|
||||||
- second user opens `:inbox` before the private messages arrive, sees it
|
- second user opens `:inbox` before the private messages arrive, sees it
|
||||||
auto-refresh after delivery, newest first, and replies without retyping the
|
auto-refresh after delivery, newest first, and replies without retyping the
|
||||||
sender's username
|
sender's username
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ typedef enum {
|
||||||
I18N_INBOX_TITLE,
|
I18N_INBOX_TITLE,
|
||||||
I18N_INBOX_EMPTY,
|
I18N_INBOX_EMPTY,
|
||||||
I18N_INBOX_SENT_TO_FORMAT,
|
I18N_INBOX_SENT_TO_FORMAT,
|
||||||
|
I18N_INBOX_CLEARED,
|
||||||
I18N_NICK_INVALID,
|
I18N_NICK_INVALID,
|
||||||
I18N_NICK_TAKEN_FORMAT,
|
I18N_NICK_TAKEN_FORMAT,
|
||||||
I18N_NICK_UNCHANGED,
|
I18N_NICK_UNCHANGED,
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,11 @@ static const command_catalog_entry_t entries[] = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
{TNT_COMMAND_INBOX, "inbox", {"inbox", NULL}},
|
{TNT_COMMAND_INBOX, "inbox", {"inbox", NULL}},
|
||||||
|
I18N_STRING(":inbox, :inbox clear", ":inbox, :inbox clear"),
|
||||||
|
I18N_STRING("Show or clear private messages", "查看或清空私信"),
|
||||||
I18N_STRING(":inbox", ":inbox"),
|
I18N_STRING(":inbox", ":inbox"),
|
||||||
I18N_STRING("Show private messages", "查看私信"),
|
I18N_STRING("Usage: inbox [clear]\n", "用法: inbox [clear]\n"),
|
||||||
I18N_STRING(":inbox", ":inbox"),
|
2, false, false
|
||||||
I18N_STRING("Usage: inbox\n", "用法: inbox\n"),
|
|
||||||
2, true, false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
{TNT_COMMAND_NICK, "nick", {"nick", "name", NULL}},
|
{TNT_COMMAND_NICK, "nick", {"nick", "name", NULL}},
|
||||||
|
|
@ -239,6 +239,9 @@ bool command_catalog_args_valid(tnt_command_id_t id, const char *args) {
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (id == TNT_COMMAND_INBOX) {
|
||||||
|
return !args || args[0] == '\0' || strcmp(args, "clear") == 0;
|
||||||
|
}
|
||||||
if (entry->no_args) {
|
if (entry->no_args) {
|
||||||
return !args || args[0] == '\0';
|
return !args || args[0] == '\0';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,15 @@ static void append_inbox_output(client_t *client, char *output,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void clear_inbox(client_t *client) {
|
||||||
|
pthread_mutex_lock(&client->whisper_lock);
|
||||||
|
memset(client->whisper_inbox, 0, sizeof(client->whisper_inbox));
|
||||||
|
client->whisper_inbox_count = 0;
|
||||||
|
client->unread_whispers = 0;
|
||||||
|
client->last_whisper_peer[0] = '\0';
|
||||||
|
pthread_mutex_unlock(&client->whisper_lock);
|
||||||
|
}
|
||||||
|
|
||||||
bool commands_refresh_active_output(client_t *client) {
|
bool commands_refresh_active_output(client_t *client) {
|
||||||
char output[MAX_COMMAND_OUTPUT_LEN] = {0};
|
char output[MAX_COMMAND_OUTPUT_LEN] = {0};
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
|
|
@ -361,8 +370,17 @@ void commands_dispatch(client_t *client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (command_id == TNT_COMMAND_INBOX) {
|
} else if (command_id == TNT_COMMAND_INBOX) {
|
||||||
output_kind = TNT_COMMAND_OUTPUT_INBOX;
|
const char *inbox_arg = arg;
|
||||||
append_inbox_output(client, output, sizeof(output), &pos);
|
while (*inbox_arg == ' ') inbox_arg++;
|
||||||
|
if (strcmp(inbox_arg, "clear") == 0) {
|
||||||
|
clear_inbox(client);
|
||||||
|
buffer_appendf(output, sizeof(output), &pos, "%s",
|
||||||
|
i18n_text(client->ui_lang,
|
||||||
|
I18N_INBOX_CLEARED));
|
||||||
|
} else {
|
||||||
|
output_kind = TNT_COMMAND_OUTPUT_INBOX;
|
||||||
|
append_inbox_output(client, output, sizeof(output), &pos);
|
||||||
|
}
|
||||||
|
|
||||||
} else if (command_id == TNT_COMMAND_NICK) {
|
} else if (command_id == TNT_COMMAND_NICK) {
|
||||||
const char *new_name = arg;
|
const char *new_name = arg;
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,10 @@ static const i18n_string_t text_catalog[I18N_TEXT_COUNT] = {
|
||||||
"you -> %s",
|
"you -> %s",
|
||||||
"你 -> %s"
|
"你 -> %s"
|
||||||
),
|
),
|
||||||
|
[I18N_INBOX_CLEARED] = I18N_STRING(
|
||||||
|
"Private messages cleared\n",
|
||||||
|
"私信已清空\n"
|
||||||
|
),
|
||||||
[I18N_NICK_INVALID] = I18N_STRING(
|
[I18N_NICK_INVALID] = I18N_STRING(
|
||||||
"Invalid username\n",
|
"Invalid username\n",
|
||||||
"用户名无效\n"
|
"用户名无效\n"
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,20 @@ send -- "q"
|
||||||
expect "NORMAL"
|
expect "NORMAL"
|
||||||
send -- ":"
|
send -- ":"
|
||||||
expect ":"
|
expect ":"
|
||||||
|
send -- "inbox clear\r"
|
||||||
|
expect "私信已清空"
|
||||||
|
expect "q:关闭"
|
||||||
|
send -- "q"
|
||||||
|
expect "NORMAL"
|
||||||
|
send -- ":"
|
||||||
|
expect ":"
|
||||||
|
send -- "reply should not send after clear\r"
|
||||||
|
expect "没有可回复的私信"
|
||||||
|
expect "q:关闭"
|
||||||
|
send -- "q"
|
||||||
|
expect "NORMAL"
|
||||||
|
send -- ":"
|
||||||
|
expect ":"
|
||||||
send -- "nick alice2\r"
|
send -- "nick alice2\r"
|
||||||
expect "昵称已修改: alice -> alice2"
|
expect "昵称已修改: alice -> alice2"
|
||||||
expect "q:关闭"
|
expect "q:关闭"
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,10 @@ TEST(matches_canonical_names_and_aliases) {
|
||||||
assert(id == TNT_COMMAND_REPLY);
|
assert(id == TNT_COMMAND_REPLY);
|
||||||
assert(strcmp(args, "hello back") == 0);
|
assert(strcmp(args, "hello back") == 0);
|
||||||
|
|
||||||
|
assert(command_catalog_match("inbox clear", &id, &args));
|
||||||
|
assert(id == TNT_COMMAND_INBOX);
|
||||||
|
assert(strcmp(args, "clear") == 0);
|
||||||
|
|
||||||
assert(command_catalog_match("language zh", &id, &args));
|
assert(command_catalog_match("language zh", &id, &args));
|
||||||
assert(id == TNT_COMMAND_LANG);
|
assert(id == TNT_COMMAND_LANG);
|
||||||
assert(strcmp(args, "zh") == 0);
|
assert(strcmp(args, "zh") == 0);
|
||||||
|
|
@ -75,6 +79,9 @@ TEST(validates_argument_shapes) {
|
||||||
assert(command_catalog_args_valid(TNT_COMMAND_MSG, "alice hello"));
|
assert(command_catalog_args_valid(TNT_COMMAND_MSG, "alice hello"));
|
||||||
assert(!command_catalog_args_valid(TNT_COMMAND_REPLY, ""));
|
assert(!command_catalog_args_valid(TNT_COMMAND_REPLY, ""));
|
||||||
assert(command_catalog_args_valid(TNT_COMMAND_REPLY, "hello back"));
|
assert(command_catalog_args_valid(TNT_COMMAND_REPLY, "hello back"));
|
||||||
|
assert(command_catalog_args_valid(TNT_COMMAND_INBOX, NULL));
|
||||||
|
assert(command_catalog_args_valid(TNT_COMMAND_INBOX, "clear"));
|
||||||
|
assert(!command_catalog_args_valid(TNT_COMMAND_INBOX, "clear now"));
|
||||||
assert(!command_catalog_args_valid(TNT_COMMAND_SEARCH, ""));
|
assert(!command_catalog_args_valid(TNT_COMMAND_SEARCH, ""));
|
||||||
assert(command_catalog_args_valid(TNT_COMMAND_SEARCH, "needle"));
|
assert(command_catalog_args_valid(TNT_COMMAND_SEARCH, "needle"));
|
||||||
|
|
||||||
|
|
@ -103,12 +110,12 @@ TEST(generates_localized_help_sections) {
|
||||||
assert(strstr(en, "Show online users") != NULL);
|
assert(strstr(en, "Show online users") != NULL);
|
||||||
assert(strstr(en, ":msg <user> <message>") != NULL);
|
assert(strstr(en, ":msg <user> <message>") != NULL);
|
||||||
assert(strstr(en, ":reply <message>") != NULL);
|
assert(strstr(en, ":reply <message>") != NULL);
|
||||||
assert(strstr(en, "Show private messages") != NULL);
|
assert(strstr(en, "Show or clear private messages") != NULL);
|
||||||
assert(strstr(en, ":support") == NULL);
|
assert(strstr(en, ":support") == NULL);
|
||||||
|
|
||||||
assert(strstr(zh, ":users, :list, :who") != NULL);
|
assert(strstr(zh, ":users, :list, :who") != NULL);
|
||||||
assert(strstr(zh, "显示在线用户") != NULL);
|
assert(strstr(zh, "显示在线用户") != NULL);
|
||||||
assert(strstr(zh, "查看私信") != NULL);
|
assert(strstr(zh, "查看或清空私信") != NULL);
|
||||||
assert(strstr(zh, ":msg <user> <message>") != NULL);
|
assert(strstr(zh, ":msg <user> <message>") != NULL);
|
||||||
assert(strstr(zh, ":reply <message>") != NULL);
|
assert(strstr(zh, ":reply <message>") != NULL);
|
||||||
assert(strstr(zh, "<用户>") == NULL);
|
assert(strstr(zh, "<用户>") == NULL);
|
||||||
|
|
@ -139,6 +146,12 @@ TEST(generates_localized_usage) {
|
||||||
assert(strcmp(en, "Usage: reply <message>\n"
|
assert(strcmp(en, "Usage: reply <message>\n"
|
||||||
" r <message>\n") == 0);
|
" r <message>\n") == 0);
|
||||||
|
|
||||||
|
zh[0] = '\0';
|
||||||
|
zh_pos = 0;
|
||||||
|
command_catalog_append_usage(zh, sizeof(zh), &zh_pos,
|
||||||
|
TNT_COMMAND_INBOX, UI_LANG_ZH);
|
||||||
|
assert(strcmp(zh, "用法: inbox [clear]\n") == 0);
|
||||||
|
|
||||||
en[0] = '\0';
|
en[0] = '\0';
|
||||||
en_pos = 0;
|
en_pos = 0;
|
||||||
command_catalog_append_usage(en, sizeof(en), &en_pos,
|
command_catalog_append_usage(en, sizeof(en), &en_pos,
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,10 @@ TEST(text_lookup_matches_language) {
|
||||||
"you ->") != NULL);
|
"you ->") != NULL);
|
||||||
assert(strstr(i18n_text(UI_LANG_ZH, I18N_INBOX_SENT_TO_FORMAT),
|
assert(strstr(i18n_text(UI_LANG_ZH, I18N_INBOX_SENT_TO_FORMAT),
|
||||||
"你 ->") != NULL);
|
"你 ->") != NULL);
|
||||||
|
assert(strstr(i18n_text(UI_LANG_EN, I18N_INBOX_CLEARED),
|
||||||
|
"cleared") != NULL);
|
||||||
|
assert(strstr(i18n_text(UI_LANG_ZH, I18N_INBOX_CLEARED),
|
||||||
|
"清空") != 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),
|
||||||
|
|
|
||||||
6
tnt.1
6
tnt.1
|
|
@ -223,6 +223,7 @@ l l.
|
||||||
:reply \fItext\fR Reply to latest private message
|
:reply \fItext\fR Reply to latest private message
|
||||||
:r \fItext\fR Short alias for :reply
|
:r \fItext\fR Short alias for :reply
|
||||||
:inbox Show private messages, newest first
|
:inbox Show private messages, newest first
|
||||||
|
:inbox clear Clear private messages for this session
|
||||||
:last [\fIN\fR] Show last N messages from history (1\-50, default 10)
|
:last [\fIN\fR] Show last N messages from history (1\-50, default 10)
|
||||||
:search \fIkeyword\fR Case\-insensitive search; shows the last 15 matches
|
:search \fIkeyword\fR Case\-insensitive search; shows the last 15 matches
|
||||||
:mute\-joins Toggle join/leave system notifications on/off
|
:mute\-joins Toggle join/leave system notifications on/off
|
||||||
|
|
@ -259,7 +260,10 @@ until the inbox renders them. Use
|
||||||
.B :reply
|
.B :reply
|
||||||
or
|
or
|
||||||
.B :r
|
.B :r
|
||||||
to answer the latest private-message peer. Private messages are not written to
|
to answer the latest private-message peer.
|
||||||
|
.B :inbox clear
|
||||||
|
removes private messages and the reply target for this session. Private
|
||||||
|
messages are not written to
|
||||||
.IR messages.log .
|
.IR messages.log .
|
||||||
.SH EXEC INTERFACE
|
.SH EXEC INTERFACE
|
||||||
Commands can be run non\-interactively for scripting:
|
Commands can be run non\-interactively for scripting:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue