Draw a bot's notice as a notice
m.notice exists so a client can hold it back from the conversation, and every other msgtype fell through protocol::message_body into a plain message. In a room with an active bridge that made automated traffic indistinguishable from people talking (#215). A notice is still a chat line - it has a sender, it can be replied to and redacted - so it keeps its place in the timeline and takes its own kind rather than the grey system stream: muted body, a rail down the edge that survives grouping, and the word "notice" on the byline, since a toned-down body can be mistaken for an unlucky one. It also stops announcing itself while the account's own .m.rule.suppress_notices override says so, which is the state every homeserver ships. Read from the real rules rather than assumed: somebody who turned it off meant to. Two places called this "chat" literally and greyed out every Matrix message in an HTML export; both now ask isChatKind. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+1
-1
Submodule nobilis updated: 276bbb0ca6...7d2a04a740
@@ -5877,6 +5877,40 @@ video.lightbox-media {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
/* --- Notices ---------------------------------------------------------- */
|
||||
|
||||
/* Matrix's m.notice: an announcement rather than a sentence. What bots and
|
||||
bridges send, and what a homeserver's own notices arrive as.
|
||||
|
||||
Held back rather than hidden. The body takes the muted ink and the name
|
||||
keeps its colour, so a bridge relaying a hundred build results reads as a
|
||||
hundred announcements while the people talking still stand out - which is
|
||||
the whole reason the msgtype exists. The rule sits on the body alone so a
|
||||
notice that mentions you can still be highlighted over the top of it. */
|
||||
.message-row.notice .message-body {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* The rail rather than a tag on the byline, because a run of notices is
|
||||
grouped and only the first of a run has a byline to hang anything on -
|
||||
which is exactly the case a bridge produces. */
|
||||
.message-row.notice {
|
||||
box-shadow: inset 2px 0 0 var(--border);
|
||||
}
|
||||
|
||||
.notice-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
margin-left: var(--spacing-xs);
|
||||
padding: 0 6px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
font-style: normal;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
/* --- Whispers --------------------------------------------------------- */
|
||||
|
||||
/* Said privately rather than to the room. Highlighted like a mention, because
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
formatTime,
|
||||
hasDirectMessages,
|
||||
isChatKind,
|
||||
isNotice,
|
||||
isReward,
|
||||
isWhisper,
|
||||
nickColor,
|
||||
@@ -343,6 +344,11 @@ function MessageRowBody({
|
||||
// Said to you rather than to the room. Drawn differently on purpose: the
|
||||
// whole risk with a private message is reading it as a public one.
|
||||
const whispered = isWhisper(message.kind)
|
||||
// An announcement rather than a sentence. Kept in the conversation and
|
||||
// drawn a shade back from it - the point of m.notice is that a bridge
|
||||
// relaying a hundred build results should not read like a hundred people
|
||||
// talking.
|
||||
const notice = isNotice(message.kind)
|
||||
const reward = isReward(message.kind)
|
||||
// Both of the modes that draw an avatar column. Bubbles is otherwise
|
||||
// nothing like comfy, but it wants the same picture beside the same first
|
||||
@@ -609,6 +615,7 @@ function MessageRowBody({
|
||||
'message-row',
|
||||
message.isHighlight && 'highlight',
|
||||
whispered && 'whisper',
|
||||
notice && 'notice',
|
||||
message.pending && 'pending',
|
||||
message.failed && 'failed',
|
||||
isSystem && 'system',
|
||||
@@ -695,6 +702,18 @@ function MessageRowBody({
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Named as well as toned down. The muted body says "less
|
||||
important", which a long or unlucky message can say by accident;
|
||||
the word says what it actually is. Only where there is a byline
|
||||
to put it on - a run of notices is grouped, and the rail down
|
||||
the edge is what carries it there. */}
|
||||
{notice && !grouped && (
|
||||
<span className="notice-tag small">
|
||||
<Icon name="smart_toy" size={11} />
|
||||
<span>notice</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Said in words as well as in styling. Italics and a tinted edge
|
||||
say "not like the others", which is not the same as saying what
|
||||
it is - and a private message being mistaken for a public one is
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { escapeHtml, extractMedia, formatMessage } from './format'
|
||||
import { fileNameOf } from './util'
|
||||
import { fileNameOf, isChatKind } from './util'
|
||||
import type { Message } from '../../../shared/wire'
|
||||
|
||||
/**
|
||||
@@ -203,7 +203,11 @@ async function renderMessage(
|
||||
cards += `<blockquote>${bits.join('<br>')}${link}</blockquote>`
|
||||
}
|
||||
|
||||
const system = m.kind && m.kind !== 'chat' ? ' system' : ''
|
||||
// The same question the live view asks, rather than a second answer to it:
|
||||
// "chat" is what IRC, Discord and Sneedchat call a message and "message" is
|
||||
// what Matrix calls one, so a literal comparison greyed out every Matrix
|
||||
// line in an export as though nobody had said it.
|
||||
const system = isChatKind(m.kind) ? '' : ' system'
|
||||
const who = m.isAction ? `* ${m.from}` : m.from
|
||||
const edited = m.edited ? ' <span class="dim">(edited)</span>' : ''
|
||||
return (
|
||||
|
||||
@@ -120,7 +120,21 @@ export function serviceLabel(service: string): string {
|
||||
* grey system line with no author or avatar.
|
||||
*/
|
||||
export function isChatKind(kind: string | undefined): boolean {
|
||||
return kind === 'chat' || kind === 'message' || kind === 'whisper'
|
||||
return kind === 'chat' || kind === 'message' || kind === 'whisper' || kind === 'notice'
|
||||
}
|
||||
|
||||
/**
|
||||
* An announcement rather than a sentence: Matrix's `m.notice`, which is what
|
||||
* bots and bridges send and what a homeserver's own notices arrive as.
|
||||
*
|
||||
* Still a chat line - it has a sender, it can be replied to and redacted, and
|
||||
* it belongs in the conversation rather than in the grey system stream. Only
|
||||
* its weight changes, which is the entire reason the msgtype exists: in a
|
||||
* room with an active bridge, automated traffic drawn like people talking
|
||||
* makes the people impossible to find.
|
||||
*/
|
||||
export function isNotice(kind: string | undefined): boolean {
|
||||
return kind === 'notice'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user