Pin the nobilis that can ignore somebody anywhere

And the menu that asks it: one entry on a message and one on a member,
saying in the answer whether the service was told or moho is simply
declining to show what still arrives.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-06 13:32:32 -04:00
co-authored by Claude Opus 5
parent 8608c8d03d
commit f4d7b21e8d
4 changed files with 46 additions and 11 deletions
+1 -1
Submodule nobilis updated: 5ddd7860b2...c04d211b44
+12 -5
View File
@@ -260,10 +260,14 @@ export function MessageRow({
] as MenuEntry[])
: []),
{ label: 'Reply', icon: 'reply', onClick: () => reply() },
// Never hearing from them again, on every client this account uses -
// Matrix keeps the list on the account rather than in one window, which
// is the difference between blocking somebody and hiding them here.
...(service === 'matrix' && !isSystem && !message.isOwn && message.senderId
// Never hearing from them again. What that costs differs by service and
// the daemon says which: Matrix keeps the list on the homeserver and
// Discord has a real block, so both hold on every client that account is
// signed in to; IRC, Kick and Sneedchat have nobody to tell, so it is
// this window's own refusal to show what still arrives. The entry is the
// same either way, because the person pressing it is asking the same
// thing - the toast is where the difference is said.
...(!isSystem && !message.isOwn && message.from
? ([
{
label: `Ignore ${message.from}`,
@@ -271,7 +275,10 @@ export function MessageRow({
danger: true,
onClick: () => {
const account = store.accountFor(bufferId)
if (account) store.setIgnored(account.id, message.senderId!, true)
// The protocol id where there is one, since a display name can
// be changed and reused; the name where there is not, which is
// all IRC ever has.
if (account) store.setIgnored(account.id, message.senderId || message.from, true)
}
}
] as MenuEntry[])
+23 -2
View File
@@ -256,6 +256,13 @@ export function NickList(): JSX.Element {
.catch((e: Error) => store.toast('error', e.message))
}}
onToggleBlock={toggleBlocked}
onIgnore={(ignored) => {
if (!account) return
// The protocol id where the service has one - a display
// name can be changed and reused, and on IRC it is all
// there is.
store.setIgnored(account.id, member.userId || member.nick, ignored)
}}
onMention={() => store.startReply('', member.nick, '')}
onModerate={(action) => {
if (!buffer || !account) return
@@ -390,6 +397,8 @@ interface MemberRowProps {
/** Which service this row belongs to, so an action is named as it acts. */
service: string | undefined
onToggleBlock: (key: string) => void
/** Tells the service, where the service has anything to be told. */
onIgnore: (ignored: boolean) => void
onMention: () => void
onModerate: (action: ModerationAction) => void
/** Gives this member a role, or takes it away. Discord only. */
@@ -417,6 +426,7 @@ function MemberRow({
service,
onSetPower,
onToggleBlock,
onIgnore,
onMention,
onModerate,
onSetRole,
@@ -442,10 +452,21 @@ function MemberRow({
...(canWhisper ? ([{ label: 'Whisper', icon: 'lock', onClick: onWhisper }] as MenuEntry[]) : []),
...(canSendFile ? ([{ label: 'Send a file', icon: 'upload_file', onClick: onSendFile }] as MenuEntry[]) : []),
{ separator: true },
// Not hearing from them. This used to be a list in this window's own
// preferences, which hid the lines and nothing else: they still counted
// as unread, still notified, still went into the log. It goes to the
// daemon now, which tells Matrix and Discord - both of which have a real
// list of their own - and keeps one itself for the services that have
// none. The window-local list stays only as the thing that hides what is
// already on screen.
{
label: blocked ? 'Unblock' : 'Block',
label: blocked ? `Stop ignoring ${member.nick}` : `Ignore ${member.nick}`,
icon: blocked ? 'check_circle' : 'block',
onClick: () => onToggleBlock(blockKey)
danger: !blocked,
onClick: () => {
onToggleBlock(blockKey)
onIgnore(!blocked)
}
},
...(perms.canMute
? ([{ label: 'Mute', icon: 'volume_off', onClick: () => onModerate('mute') }] as MenuEntry[])
+10 -3
View File
@@ -1667,10 +1667,17 @@ export class ChatStore {
* travels to every client signed in, which is what blocking somebody
* means, and it agrees with Element.
*/
setIgnored(accountId: string, userId: string, ignored: boolean): void {
setIgnored(accountId: string, target: string, ignored: boolean): void {
void window.moho
.rpc('setMatrixIgnored', { accountId, userId, ignored })
.then(() => this.toast('info', ignored ? `Ignoring ${userId}` : `No longer ignoring ${userId}`))
.rpc<{ scope?: string }>('setIgnored', { accountId, target, ignored })
.then((answer) => {
// Where it holds is the part worth saying. On Matrix and Discord the
// service itself is told, so it holds everywhere that account is
// signed in; on IRC, Kick and Sneedchat there is nobody to tell, and
// this is moho refusing to show what still arrives.
const scope = answer?.scope === 'account' ? '' : ' here'
this.toast('info', ignored ? `Ignoring ${target}${scope}` : `No longer ignoring ${target}`)
})
.catch((e: Error) => this.toast('error', e.message))
}