diff --git a/src/renderer/src/app.css b/src/renderer/src/app.css index c7d0f9d..689c2b5 100644 --- a/src/renderer/src/app.css +++ b/src/renderer/src/app.css @@ -5078,6 +5078,13 @@ video.lightbox-media { border-radius: var(--corner-radius-small); background: var(--surface-container-high); color: var(--surface-variant-text); + /* One line, and not the thing that gives way: the stream title beside it + is what shrinks into an ellipsis. */ + white-space: nowrap; + flex: none; +} + +.channel-modes.letters { font-family: var(--font-mono, monospace); letter-spacing: 0.04em; } @@ -8396,3 +8403,13 @@ video.lightbox-media { .composer-watching .button { margin-left: auto; } + +/* Removing an account, asked in place of the button that asked for it. */ +.remove-confirm { + display: flex; + flex-direction: column; + gap: var(--spacing-s); + padding: var(--spacing-s) var(--spacing-m); + border: 1px solid #c9403c; + border-radius: var(--corner-radius-small); +} diff --git a/src/renderer/src/assets/matrix-mark.svg b/src/renderer/src/assets/matrix-mark.svg new file mode 100644 index 0000000..3a5961a --- /dev/null +++ b/src/renderer/src/assets/matrix-mark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/renderer/src/components/AccountsPanel.tsx b/src/renderer/src/components/AccountsPanel.tsx index e871899..b009acd 100644 --- a/src/renderer/src/components/AccountsPanel.tsx +++ b/src/renderer/src/components/AccountsPanel.tsx @@ -300,6 +300,7 @@ function AccountRow({ account }: { account: Account }): JSX.Element { const media = useMediaUrl(account.id) const store = useStore() const [expanded, setExpanded] = useState(false) + const [confirmingRemove, setConfirmingRemove] = useState(false) const icon = serviceIcon(account.service) const connected = account.state === 'connected' /** @@ -459,22 +460,44 @@ function AccountRow({ account }: { account: Account }): JSX.Element { on and never find again. */} - + {/* Asked first, in place. Removing takes the saved sign-in, the + history kept here and - for Matrix - this device's keys, and + none of it comes back; it used to happen on the first click. */} + {confirmingRemove ? ( +
+ + Remove {account.displayName || account.id}? Its saved sign-in and the history kept + here are deleted + {account.service === 'matrix' ? ', and so are this device’s keys, so encrypted history stays unreadable if you add it again' : ''} + . + +
+ + +
+
+ ) : ( + + )} )} @@ -1504,6 +1527,12 @@ function MatrixForm(): JSX.Element { const [flows, setFlows] = useState(null) /** Whether this homeserver's accounts live with an OAuth provider. */ const [delegated, setDelegated] = useState(false) + /** + * Where a delegating server signs people up: its account service's own + * site. matrix.org is one - closed to registration through the API this + * form uses, open to anyone through account.matrix.org. + */ + const [signUpSite, setSignUpSite] = useState('') /** Whether the homeserver keeps QR sign-in mailboxes (MSC4108). */ const [qrOffered, setQrOffered] = useState(false) /** A QR sign-in under way: the code to show, and whether it now wants digits. */ @@ -1547,12 +1576,16 @@ function MatrixForm(): JSX.Element { // say what the homeserver itself accepts, and this says whether it has // handed its accounts to somebody else entirely. void window.moho - .rpc<{ delegated: boolean; qr?: boolean }>('matrixAuthMetadata', { useTor, homeserverUrl: url }) + .rpc<{ delegated: boolean; qr?: boolean; issuer?: string }>('matrixAuthMetadata', { useTor, homeserverUrl: url }) .then((a) => { setDelegated(!!a.delegated) setQrOffered(!!a.qr) + setSignUpSite(a.delegated && a.issuer && /^https:\/\//.test(a.issuer) ? a.issuer : '') + }) + .catch(() => { + setDelegated(false) + setSignUpSite('') }) - .catch(() => setDelegated(false)) void window.moho .rpc<{ open: boolean; needsToken: boolean; wants: string[] }>('matrixRegistrationFlows', { useTor, homeserverUrl: url @@ -1667,8 +1700,19 @@ function MatrixForm(): JSX.Element { /> )} - {registration?.open === false && ( -

This homeserver is not accepting new accounts.

+ {/* A server that hands its accounts to an account service is closed to + the registration API and open on that service's site - matrix.org + is. Said as "not accepting new accounts", a new user's first look at + Matrix told them sign-up was impossible. */} + {signUpSite ? ( +

+ This server signs people up on its own website, {new URL(signUpSite).host}. Make the + account there, then come back and use Sign in with a code. +

+ ) : ( + registration?.open === false && ( +

This homeserver is not accepting new accounts.

+ ) )} {/* While a code sign-in is waiting. The code is the whole of what somebody needs, so it is the largest thing here and selectable - @@ -1827,6 +1871,16 @@ function MatrixForm(): JSX.Element { server wants a captcha, an email or its terms agreed to, the daemon says which and says to sign up on the server's own page, because those need a person somewhere this window is not. */} + {signUpSite ? ( + + ) : ( + )} {status &&

{status}

}

diff --git a/src/renderer/src/components/ConversationTools.tsx b/src/renderer/src/components/ConversationTools.tsx index 2068570..59e0098 100644 --- a/src/renderer/src/components/ConversationTools.tsx +++ b/src/renderer/src/components/ConversationTools.tsx @@ -835,7 +835,13 @@ export function ConversationTools({ buffer }: { buffer: BufferEntry }): JSX.Elem with the long form, since the letters are only obvious to people who already knew. */} {buffer.channelModes && ( - + {buffer.channelModes} )} diff --git a/src/renderer/src/components/UserFooter.tsx b/src/renderer/src/components/UserFooter.tsx index 1303026..154402f 100644 --- a/src/renderer/src/components/UserFooter.tsx +++ b/src/renderer/src/components/UserFooter.tsx @@ -55,6 +55,7 @@ export function UserFooter({ account }: { account?: Account }): JSX.Element { const store = useStore() const { menu, open, close } = useContextMenu() const voice = useChat((s) => s.voicePrefs) + const inACall = useChat((s) => s.voiceSessions.length > 0 || !!s.activeCall) const details = useChat((s) => s.connectionDetail) // With no account selected there is nobody to show, but the footer still @@ -64,6 +65,13 @@ export function UserFooter({ account }: { account?: Account }): JSX.Element { const status = effectiveStatus(account) const name = account.displayName || account.id const detail = details[account.id] + // A signed-out Kick account is a reader: it has no presence to show, and + // "Online" claimed one. + const watching = account.service === 'kick' && !account.hasPassword && status !== 'connecting' && status !== 'offline' + // Mute and deafen are the whole client's, for whatever call is up - so + // shown on a service that has calls, or anywhere while one is running, + // and not beside an IRC or Kick account with nothing to apply them to. + const showVoice = account.service === 'discord' || account.service === 'matrix' || inACall return ( <> @@ -77,10 +85,12 @@ export function UserFooter({ account }: { account?: Account }): JSX.Element { {name} - {label(status)} + {watching ? 'Watching, signed out' : label(status)} + {showVoice && ( + <> + + )} {/* Joining something new, beside the account it would be joined on. It used to sit in the heading above the channel list, where it read diff --git a/src/renderer/src/lib/util.ts b/src/renderer/src/lib/util.ts index 0c9c52d..709e47a 100644 --- a/src/renderer/src/lib/util.ts +++ b/src/renderer/src/lib/util.ts @@ -1,6 +1,6 @@ import discordSvg from '../assets/discord.svg' import sneedchatMark from '../assets/sneedchat.png' -import matrixSvg from '../assets/matrix.svg' +import matrixSvg from '../assets/matrix-mark.svg' import kickSvg from '../assets/kick.svg' /**