Somewhere to type the words

Two places, because the words are two kinds. The ones that mean you
wherever they are said go in Settings, next to the rest of what a message
looks like; the ones that only mean you on one network go on that account's
own card, under the name it calls you by.

Neither is a preference of this window. Both read and write the daemon's
own lists, because a highlight kept here could colour a line and could
never reach a phone - so the global one is not a StringSetting, though it
is built to look exactly like its neighbours.

Both say the two things somebody will otherwise find out by surprise: whole
words only, and they apply to what arrives after they are added rather than
to what is already in the log.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-05 18:01:19 -04:00
co-authored by Claude Opus 5
parent 70ee40aea5
commit e6a9f485b2
4 changed files with 93 additions and 1 deletions
+1 -1
Submodule nobilis updated: c6c6a07177...9577b117c5
@@ -340,6 +340,27 @@ function AccountRow({ account }: { account: Account }): JSX.Element {
you does not change.
</div>
{/* The other half of being addressed: your name is simply the one
word everybody has. Per account rather than everywhere, because
a word that means you on one network is somebody's ordinary
vocabulary on another - the words that apply everywhere are in
Settings. */}
<LabeledInput
label="Highlight keywords"
defaultValue={(account.highlightKeywords ?? []).join(', ')}
placeholder="a project, a name you also answer to"
onCommit={(words) =>
call('setHighlightKeywords', {
accountId: account.id,
keywords: words.split(',')
})
}
/>
<div className="small muted">
Separate them with commas. Whole words only, and case does not
matter. They apply to messages that arrive after you add them.
</div>
<GroupIcons accountId={account.id} />
<HiddenAndMuted accountId={account.id} />
@@ -11,6 +11,68 @@ import { useChat, usePref, useStore } from '../../state/hooks'
import { Icon } from '../Icon'
import type { Account, DccPrefs } from '../../../../shared/wire'
/**
* The words that count on every account.
*
* Not a `StringSetting`, which writes to this window's own preferences: a
* highlight is decided in the daemon, where it becomes the flag the mentions
* inbox reads and the notification that reaches a phone. So this reads and
* writes the daemon's own list, and looks exactly like the settings either
* side of it.
*/
function GlobalHighlightKeywords(): JSX.Element {
const store = useStore()
const [words, setWords] = useState('')
const [saved, setSaved] = useState('')
useEffect(() => {
void window.moho
.rpc<{ global: string[] }>('getHighlightKeywords')
.then((answer) => {
const text = (answer.global || []).join(', ')
setWords(text)
setSaved(text)
})
.catch(() => {
/* An empty box is what somebody with no keywords sees anyway. */
})
}, [])
const commit = (): void => {
if (words === saved) return
void window.moho
.rpc<{ global: string[] }>('setHighlightKeywords', { keywords: words.split(',') })
// The daemon tidies the list - blanks out, repeats out - so what it
// says it kept is what the box should show.
.then((answer) => {
const text = (answer.global || []).join(', ')
setWords(text)
setSaved(text)
})
.catch((e: Error) => store.toast('error', e.message))
}
return (
<div className="setting-row">
<div className="setting-text">
<div>Words</div>
<div className="small muted">
Separated by commas. Whole words only, and case does not matter. They apply to messages
that arrive after you add them.
</div>
</div>
<input
className="text-field setting-input"
value={words}
placeholder="a project, a surname, a name you also answer to"
onChange={(e) => setWords(e.target.value)}
onBlur={commit}
onKeyDown={(e) => e.key === 'Enter' && commit()}
/>
</div>
)
}
/**
* Structured by service, so adding a protocol later means adding a rail entry
* and its own panel rather than redesigning this file - a left-hand list of
@@ -252,6 +314,13 @@ function GeneralSettings(): JSX.Element {
/>
</SettingsSection>
<SettingsSection
title="Highlight keywords"
description="Words that light a message up and notify you, the same way your own name does. These apply on every account; an account can add its own in the Accounts pane, for words that only mean you on one network."
>
<GlobalHighlightKeywords />
</SettingsSection>
<SettingsSection title="Media">
<ToggleSetting
settingKey="media.autoplay"
+2
View File
@@ -34,6 +34,8 @@ export interface Account {
* member list is you, and therefore whether you hold op.
*/
currentNick: string
/** Words that highlight a message on this account, besides its own name. */
highlightKeywords?: string[]
/**
* Whether the connection is encrypted. Only IRC is ever false - every other
* service here is HTTPS or WSS by construction.