First-run fixes: remove confirmation, matrix.org sign-up, Kick header and footer, Matrix mark (#259, #260, #261, #262)
- #261: Remove account asks first, in place of the button, and says what goes - the saved sign-in, the history kept here, and for Matrix the device keys. "Keep it" puts the button back. - #259: a homeserver that delegates its accounts (matrix.org) is closed to the registration API and open on its account service's site. The form said "not accepting new accounts" and greyed out Create account; it now says the server signs people up on its own website, and Create account opens it. - #260: worded chat modes (Kick's "followers only") are set as text on one line rather than as mode letters in monospace, which wrapped into a two-line box over the channel's picture. - #262: a signed-out Kick account's footer says "Watching, signed out" rather than "Online"; mute and deafen show on services with calls, or anywhere while a call is up, not beside IRC or Kick. Matrix's icon is its "[m]" mark rather than the wordmark, unreadable at icon size. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<!-- Matrix's short mark, "[m]": the wordmark is unreadable at icon size. -->
|
||||
<path fill="#fff" d="M2 1.5h3.5v2H4v17h1.5v2H2zM22 1.5h-3.5v2H20v17h-1.5v2H22z"/>
|
||||
<path fill="#fff" d="M6.5 8h1.9v1.05C9 8.3 9.8 7.85 10.8 7.85c1.05 0 1.8.45 2.2 1.25.6-.8 1.45-1.25 2.5-1.25 1.7 0 2.6 1.05 2.6 2.95V16h-2v-4.9c0-1.05-.4-1.55-1.2-1.55-.9 0-1.45.65-1.45 1.75V16h-2v-4.9c0-1.05-.4-1.55-1.2-1.55-.9 0-1.45.65-1.45 1.75V16h-2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 516 B |
@@ -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. */}
|
||||
<IgnoreList account={account} />
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="button danger"
|
||||
onClick={() => {
|
||||
// Its conversations and its rail entry with it, once it is
|
||||
// gone. An account's own entry is listed for as long as the
|
||||
// account exists, and nothing announces it going, so a removed
|
||||
// account's tile stayed until a restart.
|
||||
void window.moho
|
||||
.rpc('removeAccount', { accountId: account.id })
|
||||
.then(() => Promise.all([store.refreshAccounts(), store.refreshBuffers(), store.refreshGroups()]))
|
||||
.catch((e: Error) => store.toast('error', e.message))
|
||||
}}
|
||||
>
|
||||
<Icon name="delete" size={16} /> Remove account
|
||||
</button>
|
||||
{/* 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 ? (
|
||||
<div className="remove-confirm" role="alertdialog" aria-label="Remove this account?">
|
||||
<span className="small">
|
||||
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' : ''}
|
||||
.
|
||||
</span>
|
||||
<div className="field-row">
|
||||
<button
|
||||
type="button"
|
||||
className="button danger"
|
||||
onClick={() => {
|
||||
// Its conversations and its rail entry with it, once it
|
||||
// is gone. An account's own entry is listed for as long
|
||||
// as the account exists, and nothing announces it going,
|
||||
// so a removed account's tile stayed until a restart.
|
||||
void window.moho
|
||||
.rpc('removeAccount', { accountId: account.id })
|
||||
.then(() => Promise.all([store.refreshAccounts(), store.refreshBuffers(), store.refreshGroups()]))
|
||||
.catch((e: Error) => store.toast('error', e.message))
|
||||
}}
|
||||
>
|
||||
<Icon name="delete" size={16} /> Remove
|
||||
</button>
|
||||
<button type="button" className="button" autoFocus onClick={() => setConfirmingRemove(false)}>
|
||||
Keep it
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button type="button" className="button danger" onClick={() => setConfirmingRemove(true)}>
|
||||
<Icon name="delete" size={16} /> Remove account
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1504,6 +1527,12 @@ function MatrixForm(): JSX.Element {
|
||||
const [flows, setFlows] = useState<string[] | null>(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 {
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
{registration?.open === false && (
|
||||
<p className="small muted">This homeserver is not accepting new accounts.</p>
|
||||
{/* 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 ? (
|
||||
<p className="small muted">
|
||||
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.
|
||||
</p>
|
||||
) : (
|
||||
registration?.open === false && (
|
||||
<p className="small muted">This homeserver is not accepting new accounts.</p>
|
||||
)
|
||||
)}
|
||||
{/* 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 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="button subtle"
|
||||
title={`Opens ${signUpSite} in your browser`}
|
||||
onClick={() => void window.moho.openExternal(signUpSite)}
|
||||
>
|
||||
Create account <Icon name="open_in_new" size={14} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="button subtle"
|
||||
@@ -1858,6 +1912,7 @@ function MatrixForm(): JSX.Element {
|
||||
>
|
||||
Create account
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{status && <p className="small muted">{status}</p>}
|
||||
<p className="small muted">
|
||||
|
||||
@@ -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 && (
|
||||
<span className="channel-modes small" title={describeModes(buffer.channelModes)}>
|
||||
<span
|
||||
// Mode letters ("+nt") are set like code; a service that names its
|
||||
// modes in words - Kick's "followers only" - is set like text, or it
|
||||
// wraps into a two-line box in a monospace fallback.
|
||||
className={`channel-modes small${/^[+-][A-Za-z]/.test(buffer.channelModes) ? ' letters' : ''}`}
|
||||
title={describeModes(buffer.channelModes)}
|
||||
>
|
||||
{buffer.channelModes}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -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 {
|
||||
<Avatar name={name} url={account.avatarUrl} size={28} status={status} accountId={account.id} />
|
||||
<span className="user-identity">
|
||||
<span className="ellipsis user-name">{name}</span>
|
||||
<span className="ellipsis small muted">{label(status)}</span>
|
||||
<span className="ellipsis small muted">{watching ? 'Watching, signed out' : label(status)}</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{showVoice && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className={`user-audio-button${voice.micMuted ? ' muted' : ''}`}
|
||||
@@ -102,6 +112,8 @@ export function UserFooter({ account }: { account?: Account }): JSX.Element {
|
||||
>
|
||||
<Icon name={voice.deafened ? 'headset_off' : 'headset_mic'} size={18} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 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
|
||||
|
||||
@@ -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'
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user