feat(settings): quick wins from the settings audit (#453 phase 1) #464

Merged
jared merged 1 commits from feat/settings-quick-wins into lotus 2026-10-25 02:48:57 -04:00
21 changed files with 199 additions and 160 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ test.describe('security nudge (#110, #123)', () => {
const nudge = strip(page); const nudge = strip(page);
await expect(nudge).toContainText("Your encryption keys aren't backed up"); await expect(nudge).toContainText("Your encryption keys aren't backed up");
await nudge.getByRole('button', { name: 'Set up' }).click(); await nudge.getByRole('button', { name: 'Set up' }).click();
await expect(page.getByText('Device Verification').first()).toBeVisible(); await expect(page.getByText('Device verification').first()).toBeVisible();
}); });
test('a dismissed announcement no longer hides the nudge (#307)', async ({ page }) => { test('a dismissed announcement no longer hides the nudge (#307)', async ({ page }) => {
+1 -1
View File
@@ -128,7 +128,7 @@ export function ThreadPanel({ room, threadId, requestClose }: ThreadPanelProps)
const editor = useEditor(); const editor = useEditor();
const thread = useThreadInstance(room, threadId); const thread = useThreadInstance(room, threadId);
const [privateReadReceipts] = useSetting(settingsAtom, 'privateReadReceipts'); const [privateReadReceipts] = useSetting(settingsAtom, 'privateReadReceipts');
// "Hide Typing & Read Receipts" must also make thread receipts private (matches markAsRead). // "Hide typing and read receipts" must also make thread receipts private (matches markAsRead).
const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const fileDropContainerRef = useRef<HTMLDivElement>(null) as React.RefObject<HTMLDivElement>; const fileDropContainerRef = useRef<HTMLDivElement>(null) as React.RefObject<HTMLDivElement>;
+1 -1
View File
@@ -205,7 +205,7 @@ export function About({ requestClose }: AboutProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Clear Cache & Reload" title="Clear cache and reload"
description="Clear all your locally stored data and reload from server. This also deletes this device's encryption keys — older encrypted messages stay unreadable here unless key backup is set up." description="Clear all your locally stored data and reload from server. This also deletes this device's encryption keys — older encrypted messages stay unreadable here unless key backup is set up."
after={ after={
<Button <Button
@@ -22,14 +22,14 @@ export function ContactInformation() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Contact Information</Text> <Text size="L400">Contact information</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
direction="Column" direction="Column"
gap="400" gap="400"
> >
<SettingTile title="Email Address" description="Email address attached to your account."> <SettingTile title="Email address" description="Email address attached to your account.">
<Box> <Box>
{emailIds?.map((email) => ( {emailIds?.map((email) => (
<Chip key={email.address} as="span" variant="Secondary" radii="Pill"> <Chip key={email.address} as="span" variant="Secondary" radii="Pill">
@@ -136,7 +136,7 @@ export function IgnoredUserList() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200"> <Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
<Text size="L400">Blocked Users</Text> <Text size="L400">Blocked users</Text>
</Box> </Box>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
@@ -145,8 +145,8 @@ export function IgnoredUserList() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Select User" title="Block someone"
description="Prevent receiving messages or invites from user by adding their userId." description="Stop someone's messages and invites reaching you. Enter their Matrix ID, e.g. @name:server."
> >
<Box direction="Column" gap="300"> <Box direction="Column" gap="300">
<IgnoreUserInput userList={ignoredUsers} /> <IgnoreUserInput userList={ignoredUsers} />
@@ -31,7 +31,7 @@ export function OidcManageAccount() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Account Management</Text> <Text size="L400">Account management</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
@@ -72,7 +72,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Enable Developer Tools" title="Enable developer tools"
after={ after={
<Switch <Switch
variant="Primary" variant="Primary"
@@ -90,7 +90,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Access Token" title="Access token"
description="Copy access token to clipboard." description="Copy access token to clipboard."
after={ after={
<Button <Button
@@ -35,7 +35,7 @@ export function CryptoDiagnostics() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Crypto Diagnostics</Text> <Text size="L400">Crypto diagnostics</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
@@ -43,8 +43,8 @@ export function CryptoDiagnostics() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Crypto Diagnostics — captures E2EE error signatures this session" title="Crypto diagnostics"
description="Ring-buffers up to 200 matched console warnings/errors for the KE-1..KE-4 bug cluster. Local only — no network calls. The downloaded report includes the matched log lines as evidence." description="Captures encryption error signatures from this session. Ring-buffers up to 200 matched console warnings/errors for the KE-1..KE-4 bug cluster. Local only — no network calls. The downloaded report includes the matched log lines as evidence."
after={ after={
<Box alignItems="Center" gap="200" shrink="No"> <Box alignItems="Center" gap="200" shrink="No">
<Badge variant={count > 0 ? 'Critical' : 'Secondary'} fill="Solid" radii="Pill"> <Badge variant={count > 0 ? 'Critical' : 'Secondary'} fill="Solid" radii="Pill">
@@ -93,8 +93,8 @@ export function Devices({ requestClose }: DevicesProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Device Verification" title="Device verification"
description="To verify device identity and grant access to encrypted messages." description="Verify this device so it can read your encrypted messages and others can trust it."
after={ after={
<> <>
<EnableVerification visible={!crossSigningActive} /> <EnableVerification visible={!crossSigningActive} />
@@ -130,8 +130,8 @@ function ExportKeysTile() {
return ( return (
<> <>
<SettingTile <SettingTile
title="Export Messages Data" title="Export encryption keys"
description="Save password protected copy of encryption data on your device to decrypt messages later." description="Save a password-protected file of your encryption keys. Useful when moving to a device without key backup, or as an offline copy."
after={ after={
<Box> <Box>
<Button <Button
@@ -260,8 +260,8 @@ function ImportKeysTile() {
return ( return (
<> <>
<SettingTile <SettingTile
title="Import Messages Data" title="Import encryption keys"
description="Load password protected copy of encryption data from device to decrypt your messages." description="Load a password-protected encryption key file to read older encrypted messages on this device."
after={ after={
<Box> <Box>
{file ? ( {file ? (
@@ -307,7 +307,7 @@ function ImportKeysTile() {
export function LocalBackup() { export function LocalBackup() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Local Backup</Text> <Text size="L400">Encryption key backup file</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
@@ -135,7 +135,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Device Dashboard" title="Device dashboard"
description="Manage your devices on OIDC dashboard." description="Manage your devices on OIDC dashboard."
after={ after={
<Button <Button
@@ -423,7 +423,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
return ( return (
<> <>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Favorite Packs</Text> <Text size="L400">Favourite packs</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
@@ -431,7 +431,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Select Pack" title="Add a pack"
description="Pick emojis and stickers pack from rooms to use in all rooms." description="Pick emojis and stickers pack from rooms to use in all rooms."
after={ after={
<> <>
@@ -31,7 +31,7 @@ export function UserPack({ onViewPack }: UserPackProps) {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Default Pack</Text> <Text size="L400">Default pack</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
+152 -101
View File
@@ -155,7 +155,7 @@ function DesktopChromeSetting() {
return ( return (
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Custom Window Chrome (Beta)" title="Custom window chrome (beta)"
description="Replace the system title bar with a Lotus-styled one. Desktop only — reloads to apply." description="Replace the system title bar with a Lotus-styled one. Desktop only — reloads to apply."
after={<Switch variant="Primary" value={customChrome} onChange={handleToggle} />} after={<Switch variant="Primary" value={customChrome} onChange={handleToggle} />}
/> />
@@ -263,7 +263,7 @@ function KeychainMirrorSetting() {
} }
return ( return (
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile title="Login in the system keychain" description={description} /> <SettingTile title="Keep your sign-in in the system keychain" description={description} />
</SequenceCard> </SequenceCard>
); );
} }
@@ -294,12 +294,18 @@ const ThemeSelector = as<'div', ThemeSelectorProps>(
), ),
); );
function SelectTheme({ disabled }: { disabled?: boolean }) { function SelectTheme({ disabled, kind }: { disabled?: boolean; kind?: ThemeKind }) {
const themes = useThemes(); const allThemes = useThemes();
const themes = kind ? allThemes.filter((theme) => theme.kind === kind) : allThemes;
const themeNames = useThemeNames(); const themeNames = useThemeNames();
const [themeId, setThemeId] = useSetting(settingsAtom, 'themeId'); const [themeId, setThemeId] = useSetting(settingsAtom, 'themeId');
const [, setLightThemeId] = useSetting(settingsAtom, 'lightThemeId');
const [, setDarkThemeId] = useSetting(settingsAtom, 'darkThemeId');
const [menuCords, setMenuCords] = useState<RectCords>(); const [menuCords, setMenuCords] = useState<RectCords>();
const selectedTheme = themes.find((theme) => theme.id === themeId) ?? LightTheme; const selectedTheme =
themes.find((theme) => theme.id === themeId) ??
themes[0] ??
(kind === ThemeKind.Dark ? DarkTheme : LightTheme);
const handleThemeMenu: MouseEventHandler<HTMLButtonElement> = (evt) => { const handleThemeMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
setMenuCords(evt.currentTarget.getBoundingClientRect()); setMenuCords(evt.currentTarget.getBoundingClientRect());
@@ -307,6 +313,10 @@ function SelectTheme({ disabled }: { disabled?: boolean }) {
const handleThemeSelect = (theme: Theme) => { const handleThemeSelect = (theme: Theme) => {
setThemeId(theme.id); setThemeId(theme.id);
// [Gitea #455] Also remember it as this kind's variant, so switching to
// System later keeps the user's pick.
if (theme.kind === ThemeKind.Light) setLightThemeId(theme.id);
else setDarkThemeId(theme.id);
setMenuCords(undefined); setMenuCords(undefined);
}; };
@@ -391,7 +401,7 @@ function SystemThemePreferences() {
return ( return (
<Box wrap="Wrap" gap="400"> <Box wrap="Wrap" gap="400">
<SettingTile <SettingTile
title="Light Theme:" title="Light theme:"
after={ after={
<Chip <Chip
variant={themeKind === ThemeKind.Light ? 'Primary' : 'Secondary'} variant={themeKind === ThemeKind.Light ? 'Primary' : 'Secondary'}
@@ -432,7 +442,7 @@ function SystemThemePreferences() {
} }
/> />
<SettingTile <SettingTile
title="Dark Theme:" title="Dark theme:"
after={ after={
<Chip <Chip
variant={themeKind === ThemeKind.Dark ? 'Primary' : 'Secondary'} variant={themeKind === ThemeKind.Dark ? 'Primary' : 'Secondary'}
@@ -534,6 +544,26 @@ function PageZoomInput() {
function Appearance() { function Appearance() {
const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme'); const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme');
const allThemes = useThemes();
const [themeId, setThemeId] = useSetting(settingsAtom, 'themeId');
const [lightThemeId] = useSetting(settingsAtom, 'lightThemeId');
const [darkThemeId] = useSetting(settingsAtom, 'darkThemeId');
const currentKind = allThemes.find((t) => t.id === themeId)?.kind ?? ThemeKind.Light;
let themeMode: 'system' | 'light' | 'dark' = 'system';
if (!systemTheme) themeMode = currentKind === ThemeKind.Dark ? 'dark' : 'light';
const chooseThemeMode = (mode: 'system' | 'light' | 'dark') => {
if (mode === 'system') {
setSystemTheme(true);
return;
}
setSystemTheme(false);
// Use the variant already chosen for that kind (or the default one).
const kind = mode === 'dark' ? ThemeKind.Dark : ThemeKind.Light;
if (allThemes.find((t) => t.id === themeId)?.kind === kind) return;
const preferred = mode === 'dark' ? darkThemeId : lightThemeId;
const fallback = mode === 'dark' ? DarkTheme.id : LightTheme.id;
setThemeId(allThemes.find((t) => t.id === preferred && t.kind === kind)?.id ?? fallback);
};
const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode'); const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode');
const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
const [perMessageProfiles, setPerMessageProfiles] = useSetting( const [perMessageProfiles, setPerMessageProfiles] = useSetting(
@@ -575,38 +605,64 @@ function Appearance() {
direction="Column" direction="Column"
gap="400" gap="400"
> >
<SettingTile {/* [Gitea #455] One control instead of a System switch plus a Theme
title="System Theme" dropdown that was greyed out whenever the switch was on. */}
description="Choose between light and dark theme based on system preference."
after={<Switch variant="Primary" value={systemTheme} onChange={setSystemTheme} />}
/>
{systemTheme && <SystemThemePreferences />}
</SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Theme" title="Theme"
description="Theme to use when system theme is not enabled." description={
after={<SelectTheme disabled={systemTheme} />} themeMode === 'system'
? "Follows your device's light or dark setting."
: 'Always use this theme, whatever your device is set to.'
}
after={
<Box gap="100" role="radiogroup" aria-label="Theme" shrink="No" wrap="Wrap">
{(
[
['system', 'System'],
['light', 'Light'],
['dark', 'Dark'],
] as const
).map(([mode, label]) => (
<Chip
key={mode}
role="radio"
aria-checked={themeMode === mode}
variant={themeMode === mode ? 'Primary' : 'Secondary'}
outlined={themeMode === mode}
radii="Pill"
onClick={() => chooseThemeMode(mode)}
>
<Text size="B300">{label}</Text>
</Chip>
))}
</Box>
}
/> />
{themeMode === 'system' && <SystemThemePreferences />}
{themeMode !== 'system' && (
<SettingTile
title={themeMode === 'light' ? 'Light theme:' : 'Dark theme:'}
after={<SelectTheme kind={themeMode === 'light' ? ThemeKind.Light : ThemeKind.Dark} />}
/>
)}
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Monochrome Mode" title="Monochrome mode"
after={<Switch variant="Primary" value={monochromeMode} onChange={setMonochromeMode} />} after={<Switch variant="Primary" value={monochromeMode} onChange={setMonochromeMode} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Twitter Emoji" title="Emoji style: Twemoji"
after={<Switch variant="Primary" value={twitterEmoji} onChange={setTwitterEmoji} />} after={<Switch variant="Primary" value={twitterEmoji} onChange={setTwitterEmoji} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile title="Page Zoom" after={<PageZoomInput />} /> <SettingTile title="Page zoom" after={<PageZoomInput />} />
</SequenceCard> </SequenceCard>
<SequenceCard <SequenceCard
@@ -616,7 +672,7 @@ function Appearance() {
gap="200" gap="200"
> >
<SettingTile <SettingTile
title="Chat Background" title="Chat background"
description="Pattern applied behind the message timeline." description="Pattern applied behind the message timeline."
/> />
<Box style={{ padding: `0 ${config.space.S400} ${config.space.S300}` }}> <Box style={{ padding: `0 ${config.space.S400} ${config.space.S300}` }}>
@@ -626,7 +682,7 @@ function Appearance() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Pause Background Animations" title="Pause background animations"
description="Stop background animation to reduce motion or improve performance." description="Stop background animation to reduce motion or improve performance."
after={<Switch variant="Primary" value={pauseAnimations} onChange={setPauseAnimations} />} after={<Switch variant="Primary" value={pauseAnimations} onChange={setPauseAnimations} />}
/> />
@@ -639,7 +695,7 @@ function Appearance() {
gap="200" gap="200"
> >
<SettingTile <SettingTile
title="Seasonal Theme" title="Seasonal theme"
description="Decorative overlays for holidays and events. “Auto” follows the calendar — each theme below shows the dates it turns on. Click to select." description="Decorative overlays for holidays and events. “Auto” follows the calendar — each theme below shows the dates it turns on. Click to select."
/> />
<Box style={{ padding: `0 ${config.space.S400} ${config.space.S300}` }}> <Box style={{ padding: `0 ${config.space.S400} ${config.space.S300}` }}>
@@ -659,7 +715,7 @@ function Appearance() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Show Profile on Every Message" title="Show name and avatar on every message"
description="Display avatar and name on each message instead of grouping consecutive messages." description="Display avatar and name on each message instead of grouping consecutive messages."
after={ after={
<Switch variant="Primary" value={perMessageProfiles} onChange={setPerMessageProfiles} /> <Switch variant="Primary" value={perMessageProfiles} onChange={setPerMessageProfiles} />
@@ -673,7 +729,7 @@ function Appearance() {
gap="200" gap="200"
> >
<SettingTile <SettingTile
title="Night Light" title="Night light"
description="Reduce blue light with a warm orange tint overlay." description="Reduce blue light with a warm orange tint overlay."
after={ after={
<Switch variant="Primary" value={nightLightEnabled} onChange={setNightLightEnabled} /> <Switch variant="Primary" value={nightLightEnabled} onChange={setNightLightEnabled} />
@@ -753,7 +809,7 @@ function Appearance() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Glassmorphism Sidebar" title="Glass sidebar"
description="Semi-transparent sidebar with frosted glass effect. Works best with custom chat backgrounds." description="Semi-transparent sidebar with frosted glass effect. Works best with custom chat backgrounds."
after={ after={
<Switch <Switch
@@ -767,8 +823,8 @@ function Appearance() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Lotus Terminal Mode" title="Lotus Terminal mode"
description="LotusGuild Terminal Design System: Anduril Orange + Ice Cyan + Matrix Green, dot-grid background, CRT scanlines, and boot sequence animation (press Escape to skip)." description="A retro terminal look: orange and cyan on a dot grid, CRT scanlines and a boot animation (press Esc to skip)."
after={ after={
<Box direction="Row" gap="200" alignItems="Center"> <Box direction="Row" gap="200" alignItems="Center">
{lotusTerminal && ( {lotusTerminal && (
@@ -802,7 +858,7 @@ function Appearance() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="UI Font" title="Interface font"
description="Font used throughout the interface." description="Font used throughout the interface."
after={ after={
<SettingsSelect <SettingsSelect
@@ -822,7 +878,7 @@ function Appearance() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="@Mention Highlight Color" title="@mention highlight colour"
description="Color used to highlight messages that mention you. Leave empty to use the theme default." description="Color used to highlight messages that mention you. Leave empty to use the theme default."
after={ after={
<HexColorPickerPopOut <HexColorPickerPopOut
@@ -866,7 +922,7 @@ function Appearance() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Custom Accent Color" title="Custom accent colour"
description={ description={
lotusTerminal lotusTerminal
? 'Only applies to non-TDS themes. Disable Lotus Terminal Mode to use a custom accent.' ? 'Only applies to non-TDS themes. Disable Lotus Terminal Mode to use a custom accent.'
@@ -1240,7 +1296,7 @@ function SelectDateFormat() {
return ( return (
<> <>
<SettingTile <SettingTile
title="Date Format" title="Date format"
description={customDateFormat ? dayjs().format(dateFormatString) : ''} description={customDateFormat ? dayjs().format(dateFormatString) : ''}
after={<PresetDateFormat value={selectedDateFormat} onChange={handlePresetChange} />} after={<PresetDateFormat value={selectedDateFormat} onChange={handlePresetChange} />}
/> />
@@ -1256,10 +1312,10 @@ function DateAndTime() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Date & Time</Text> <Text size="L400">Date and time</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="24-Hour Time Format" title="24-hour time"
after={<Switch variant="Primary" value={hour24Clock} onChange={setHour24Clock} />} after={<Switch variant="Primary" value={hour24Clock} onChange={setHour24Clock} />}
/> />
</SequenceCard> </SequenceCard>
@@ -1471,7 +1527,7 @@ function Editor() {
<Text size="L400">Editor</Text> <Text size="L400">Editor</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="ENTER for Newline" title="Enter for a new line"
description={`Use ${ description={`Use ${
isMacOS() ? KeySymbol.Command : 'Ctrl' isMacOS() ? KeySymbol.Command : 'Ctrl'
} + ENTER to send message and ENTER for newline.`} } + ENTER to send message and ENTER for newline.`}
@@ -1480,20 +1536,20 @@ function Editor() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Markdown Formatting" title="Markdown formatting"
after={<Switch variant="Primary" value={isMarkdown} onChange={setIsMarkdown} />} after={<Switch variant="Primary" value={isMarkdown} onChange={setIsMarkdown} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Offer to Format Pasted Code" title="Offer to format pasted code"
description="When something you paste looks like code, offer to turn it into a code block. Nothing changes unless you accept." description="When something you paste looks like code, offer to turn it into a code block. Nothing changes unless you accept."
after={<Switch variant="Primary" value={offerCodePaste} onChange={setOfferCodePaste} />} after={<Switch variant="Primary" value={offerCodePaste} onChange={setOfferCodePaste} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Formatting Toolbar" title="Formatting toolbar"
description="Show bold, italic, code and other formatting buttons above the message input." description="Show bold, italic, code and other formatting buttons above the message input."
after={<Switch variant="Primary" value={editorToolbar} onChange={setEditorToolbar} />} after={<Switch variant="Primary" value={editorToolbar} onChange={setEditorToolbar} />}
/> />
@@ -1505,7 +1561,7 @@ function Editor() {
gap="300" gap="300"
> >
<SettingTile <SettingTile
title="Composer Toolbar" title="Composer toolbar"
description="Drag to reorder buttons, and tap a button to show or hide it in the message composer." description="Drag to reorder buttons, and tap a button to show or hide it in the message composer."
/> />
<Box direction="Column" style={{ paddingBottom: config.space.S200 }}> <Box direction="Column" style={{ paddingBottom: config.space.S200 }}>
@@ -1540,28 +1596,28 @@ function Privacy() {
<Text size="L400">Privacy</Text> <Text size="L400">Privacy</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Strip Tracking Parameters from Links" title="Strip tracking parameters from links"
description="Remove utm_, fbclid, YouTube si= and other ad/analytics identifiers from links you paste or send, and from links shown in chat. Runs entirely on this device." description="Remove utm_, fbclid, YouTube si= and other ad/analytics identifiers from links you paste or send, and from links shown in chat. Runs entirely on this device."
after={<Switch variant="Primary" value={stripTracking} onChange={setStripTracking} />} after={<Switch variant="Primary" value={stripTracking} onChange={setStripTracking} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Remove Photo Metadata Before Sending" title="Remove photo metadata before sending"
description="Strip location, camera model and timestamp (EXIF/XMP) from JPEG, PNG and WebP images you send or set as an avatar. Pixels are untouched; the rotation is kept. Videos and GIFs are not covered." description="Strip location, camera model and timestamp (EXIF/XMP) from JPEG, PNG and WebP images you send or set as an avatar. Pixels are untouched; the rotation is kept. Videos and GIFs are not covered."
after={<Switch variant="Primary" value={stripImageMeta} onChange={setStripImageMeta} />} after={<Switch variant="Primary" value={stripImageMeta} onChange={setStripImageMeta} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Hide Typing & Read Receipts" title="Hide typing and read receipts"
description="Turn off both typing status and read receipts to keep your activity private." description="Turn off both typing status and read receipts to keep your activity private."
after={<Switch variant="Primary" value={hideActivity} onChange={setHideActivity} />} after={<Switch variant="Primary" value={hideActivity} onChange={setHideActivity} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Private Read Receipts" title="Private read receipts"
description="Send read receipts only to you and the server — other users won't see when you've read messages." description="Send read receipts only to you and the server — other users won't see when you've read messages."
after={ after={
<Switch <Switch
@@ -1574,7 +1630,7 @@ function Privacy() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Hide Online Status" title="Hide online status"
description="Appear offline to everyone while still using the app. Disables online, away, and idle indicators." description="Appear offline to everyone while still using the app. Disables online, away, and idle indicators."
after={<Switch variant="Primary" value={hidePresence} onChange={setHidePresence} />} after={<Switch variant="Primary" value={hidePresence} onChange={setHidePresence} />}
/> />
@@ -1656,7 +1712,7 @@ function SettingsSyncSection() {
<Text size="L400">Sync</Text> <Text size="L400">Sync</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Sync Settings Across Devices" title="Sync settings across devices"
description="Keep your preferences (theme, composer toolbar, notifications, quiet hours, call keys…) the same on every device, stored as account data on your own homeserver. Device-specific settings such as zoom, media auto-load, noise suppression and volumes stay local." description="Keep your preferences (theme, composer toolbar, notifications, quiet hours, call keys…) the same on every device, stored as account data on your own homeserver. Device-specific settings such as zoom, media auto-load, noise suppression and volumes stay local."
after={<Switch variant="Primary" value={settingsSync} onChange={setSettingsSync} />} after={<Switch variant="Primary" value={settingsSync} onChange={setSettingsSync} />}
/> />
@@ -1664,7 +1720,7 @@ function SettingsSyncSection() {
{settingsSync && ( {settingsSync && (
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Use This Device's Settings Everywhere" title="Use this device's settings everywhere"
description="Push this device's current preferences so every other device adopts them, even ones that changed something more recently." description="Push this device's current preferences so every other device adopts them, even ones that changed something more recently."
// [Gitea #273] On phones the two buttons squeezed the text into a // [Gitea #273] On phones the two buttons squeezed the text into a
// narrow column; put them under the description there. // narrow column; put them under the description there.
@@ -1799,14 +1855,14 @@ function Calls() {
<Text size="L400">Calls</Text> <Text size="L400">Calls</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Join with Camera On" title="Join with camera on"
description="Enable camera automatically when joining a voice or video call. Camera is off by default." description="Enable camera automatically when joining a voice or video call. Camera is off by default."
after={<Switch variant="Primary" value={cameraOnJoin} onChange={setCameraOnJoin} />} after={<Switch variant="Primary" value={cameraOnJoin} onChange={setCameraOnJoin} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Noise Suppression" title="Noise suppression"
description={ description={
<Box direction="Column" gap="200"> <Box direction="Column" gap="200">
{/* [Gitea #271] T200 like every other setting description. */} {/* [Gitea #271] T200 like every other setting description. */}
@@ -1863,7 +1919,7 @@ function Calls() {
<Box direction="Column" gap="200"> <Box direction="Column" gap="200">
<Text size="L400">Model</Text> <Text size="L400">Model</Text>
<SettingTile <SettingTile
title="ML Model" title="ML model"
description="Choose the machine learning model used for noise removal. Heavier models clean more aggressively at a higher CPU cost." description="Choose the machine learning model used for noise removal. Heavier models clean more aggressively at a higher CPU cost."
after={ after={
<SettingsSelect<DenoiseModelId> <SettingsSelect<DenoiseModelId>
@@ -1979,7 +2035,7 @@ function Calls() {
> >
<Text size="L400">Enhancements</Text> <Text size="L400">Enhancements</Text>
<SettingTile <SettingTile
title="Series Suppression" title="Also use the browser's noise suppression"
description="Run the browser's native stationary noise filter before the ML model. Recommended for eliminating fan hum." description="Run the browser's native stationary noise filter before the ML model. Recommended for eliminating fan hum."
after={ after={
<Switch <Switch
@@ -1991,7 +2047,7 @@ function Calls() {
/> />
<SettingTile <SettingTile
title="Noise Gate" title="Noise gate"
description="Hard-cut audio when you aren't speaking to ensure absolute silence between sentences." description="Hard-cut audio when you aren't speaking to ensure absolute silence between sentences."
after={ after={
<Switch variant="Primary" value={callDenoiseGate} onChange={setCallDenoiseGate} /> <Switch variant="Primary" value={callDenoiseGate} onChange={setCallDenoiseGate} />
@@ -2027,7 +2083,7 @@ function Calls() {
borderTop: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`, borderTop: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
}} }}
> >
<Text size="L400">Test &amp; calibrate</Text> <Text size="L400">Test and calibrate</Text>
<Text size="T200" priority="300"> <Text size="T200" priority="300">
Audition the selected model and tune the gate without joining a call. Changes to the Audition the selected model and tune the gate without joining a call. Changes to the
settings above apply the next time you start a monitor or play a clip. settings above apply the next time you start a monitor or play a clip.
@@ -2049,7 +2105,7 @@ function Calls() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="After a Restart" title="Rejoin calls after a restart"
description="If Lotus crashes, reloads or restarts for an update while you are in a voice room, put you back in." description="If Lotus crashes, reloads or restarts for an update while you are in a voice room, put you back in."
after={ after={
<SettingsSelect<'ask' | 'auto' | 'off'> <SettingsSelect<'ask' | 'auto' | 'off'>
@@ -2064,7 +2120,7 @@ function Calls() {
} }
/> />
<SettingTile <SettingTile
title="Summary After Calls" title="Summary after calls"
description="When a call ends, show how long it was and how the connection held up, with a Details button for who was there, network numbers and your own talk/mute time." description="When a call ends, show how long it was and how the connection held up, with a Details button for who was there, network numbers and your own talk/mute time."
after={ after={
<Switch variant="Primary" value={callSummaryToast} onChange={setCallSummaryToast} /> <Switch variant="Primary" value={callSummaryToast} onChange={setCallSummaryToast} />
@@ -2072,19 +2128,19 @@ function Calls() {
/> />
{hapticsSupported() && ( {hapticsSupported() && (
<SettingTile <SettingTile
title="Haptic Feedback" title="Haptic feedback"
description="A short vibration when push-to-talk engages or releases and when you send a reaction. Off automatically when your system prefers reduced motion." description="A short vibration when push-to-talk engages or releases and when you send a reaction. Off automatically when your system prefers reduced motion."
after={<Switch variant="Primary" value={haptics} onChange={setHaptics} />} after={<Switch variant="Primary" value={haptics} onChange={setHaptics} />}
/> />
)} )}
<SettingTile <SettingTile
title="Push to Talk" title="Push to talk"
description="Mute your microphone by default. Hold the PTT key to speak." description="Mute your microphone by default. Hold the PTT key to speak."
after={<Switch variant="Primary" value={pttMode} onChange={setPttMode} />} after={<Switch variant="Primary" value={pttMode} onChange={setPttMode} />}
/> />
{pttMode && ( {pttMode && (
<SettingTile <SettingTile
title="PTT Key" title="Push-to-talk key"
description={pttBind.error ?? 'Press a key to bind it as your push-to-talk key.'} description={pttBind.error ?? 'Press a key to bind it as your push-to-talk key.'}
after={ after={
<Button <Button
@@ -2102,7 +2158,7 @@ function Calls() {
/> />
)} )}
<SettingTile <SettingTile
title="Push to Deafen" title="Push to deafen"
description={ description={
deafenBind.error ?? deafenBind.error ??
(deafenHotkey (deafenHotkey
@@ -2143,7 +2199,7 @@ function Calls() {
/> />
{isTauriEnv() && ( {isTauriEnv() && (
<SettingTile <SettingTile
title="Hotkeys Work Outside the Window" title="Hotkeys work outside the window"
description="Keep Push to Talk and Push to Deafen working while a game or another app has focus. The keys are only watched during a call and are never blocked from other apps. Windows only; some anti-cheat systems may prevent it." description="Keep Push to Talk and Push to Deafen working while a game or another app has focus. The keys are only watched during a call and are never blocked from other apps. Windows only; some anti-cheat systems may prevent it."
after={ after={
<Switch variant="Primary" value={globalCallHotkeys} onChange={setGlobalCallHotkeys} /> <Switch variant="Primary" value={globalCallHotkeys} onChange={setGlobalCallHotkeys} />
@@ -2165,13 +2221,13 @@ function Calls() {
} }
/> />
<SettingTile <SettingTile
title="AFK Auto-Mute" title="AFK auto-mute"
description="Automatically mute your microphone when you have been silent in a call." description="Automatically mute your microphone when you have been silent in a call."
after={<Switch variant="Primary" value={afkAutoMute} onChange={setAfkAutoMute} />} after={<Switch variant="Primary" value={afkAutoMute} onChange={setAfkAutoMute} />}
/> />
{afkAutoMute && ( {afkAutoMute && (
<SettingTile <SettingTile
title="Idle Timeout" title="Idle timeout"
description="How long to wait before auto-muting." description="How long to wait before auto-muting."
after={ after={
<SettingsSelect <SettingsSelect
@@ -2203,7 +2259,7 @@ function Calls() {
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile title="Ringtone Volume" description="Volume of the incoming call ringtone." /> <SettingTile title="Ringtone volume" description="Volume of the incoming call ringtone." />
<Box <Box
direction="Row" direction="Row"
alignItems="Center" alignItems="Center"
@@ -2227,7 +2283,7 @@ function Calls() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Join & Leave Sounds" title="Join and leave sounds"
description="Play a sound when someone joins or leaves a call you are in. Selecting an option plays a preview." description="Play a sound when someone joins or leaves a call you are in. Selecting an option plays a preview."
after={ after={
<SettingsSelect <SettingsSelect
@@ -2246,7 +2302,7 @@ function Calls() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Microphone Bitrate" title="Microphone bitrate"
description="Cap the audio bitrate your mic sends in calls. Lower saves bandwidth; higher is clearer. Auto lets Element Call decide." description="Cap the audio bitrate your mic sends in calls. Lower saves bandwidth; higher is clearer. Auto lets Element Call decide."
after={ after={
<SettingsSelect<CallAudioBitrate> <SettingsSelect<CallAudioBitrate>
@@ -2260,7 +2316,7 @@ function Calls() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Screenshare Bitrate" title="Screen share bitrate"
description="Cap the bitrate used when you share your screen. Lower is smoother on poor connections; higher is sharper." description="Cap the bitrate used when you share your screen. Lower is smoother on poor connections; higher is sharper."
after={ after={
<SettingsSelect<ScreenshareBitrate> <SettingsSelect<ScreenshareBitrate>
@@ -2274,7 +2330,7 @@ function Calls() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Screenshare Framerate" title="Screen share frame rate"
description="Cap the frames-per-second of your screenshare. 60 fps suits motion/gaming; 15 fps suits slides and saves bandwidth." description="Cap the frames-per-second of your screenshare. 60 fps suits motion/gaming; 15 fps suits slides and saves bandwidth."
after={ after={
<SettingsSelect<ScreenshareFramerate> <SettingsSelect<ScreenshareFramerate>
@@ -2300,7 +2356,7 @@ function Calls() {
/> />
{soundboardEnabled && ( {soundboardEnabled && (
<> <>
<SettingTile title="Soundboard Volume" /> <SettingTile title="Soundboard volume" />
<Box <Box
alignItems="Center" alignItems="Center"
gap="200" gap="200"
@@ -2645,14 +2701,14 @@ function Messages() {
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Messages</Text> <Text size="L400">Messages</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile title="Message Layout" after={<SelectMessageLayout />} /> <SettingTile title="Message layout" after={<SelectMessageLayout />} />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile title="Message Spacing" after={<SelectMessageSpacing />} /> <SettingTile title="Message spacing" after={<SelectMessageSpacing />} />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Legacy Username Color" title="Legacy username colour"
after={ after={
<Switch <Switch
variant="Primary" variant="Primary"
@@ -2664,7 +2720,7 @@ function Messages() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Hide Membership Change" title="Hide joins and leaves"
after={ after={
<Switch <Switch
variant="Primary" variant="Primary"
@@ -2676,7 +2732,7 @@ function Messages() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Hide Profile Change" title="Hide name and avatar changes"
after={ after={
<Switch <Switch
variant="Primary" variant="Primary"
@@ -2688,7 +2744,7 @@ function Messages() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Disable Media Auto Load" title="Don't load media automatically"
after={ after={
<Switch <Switch
variant="Primary" variant="Primary"
@@ -2700,14 +2756,14 @@ function Messages() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="URL Preview" title="Link previews"
description="Your homeserver fetches and caches link previews. It will see the URLs of all links you preview." description="Your homeserver fetches and caches link previews. It will see the URLs of all links you preview."
after={<Switch variant="Primary" value={urlPreview} onChange={setUrlPreview} />} after={<Switch variant="Primary" value={urlPreview} onChange={setUrlPreview} />}
/> />
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="URL Preview in Encrypted Rooms" title="Link previews in encrypted rooms"
description={ description={
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="T200" priority="300"> <Text size="T200" priority="300">
@@ -2731,34 +2787,29 @@ function Messages() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Inline Media Players" title="Inline media players"
description="Play YouTube, Vimeo, TikTok, Twitch, Spotify and more right in the chat. Nothing loads from the third-party site until you press play." description="Play YouTube, Vimeo, TikTok, Twitch, Spotify and more right in the chat. Nothing loads from the third-party site until you press play."
after={ after={
<Switch variant="Primary" value={inlineMediaEmbeds} onChange={setInlineMediaEmbeds} /> <Switch variant="Primary" value={inlineMediaEmbeds} onChange={setInlineMediaEmbeds} />
} }
/> />
</SequenceCard> </SequenceCard>
{/* [Gitea #457] Only where this build has a GIF key: a disabled switch
with a paragraph about missing configuration helps nobody. */}
{gifAvailable && (
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile
title="GIF picker"
description="Every search term (and your IP address) is sent directly to Giphy, not through your homeserver. Off by default."
after={
<Switch variant="Primary" value={gifPickerEnabled} onChange={setGifPickerEnabled} />
}
/>
</SequenceCard>
)}
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="GIF Picker" title="Show hidden events"
description={
gifAvailable
? 'Every search term (and your IP address) is sent directly to Giphy, not through your homeserver. Off by default.'
: "GIF search isn't set up for this version of the app (no Giphy key in its configuration), so there is no GIF button even when this is on."
}
after={
<Switch
variant="Primary"
value={gifPickerEnabled && gifAvailable}
onChange={setGifPickerEnabled}
disabled={!gifAvailable}
/>
}
/>
</SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile
title="Show Hidden Events"
after={ after={
<Switch variant="Primary" value={showHiddenEvents} onChange={setShowHiddenEvents} /> <Switch variant="Primary" value={showHiddenEvents} onChange={setShowHiddenEvents} />
} }
@@ -2766,7 +2817,7 @@ function Messages() {
</SequenceCard> </SequenceCard>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile <SettingTile
title="Enforce Message Retention" title="Enforce message retention"
description="Permanently delete your own messages once a room's retention window (Room Settings → Message Retention) has passed. Off by default; only affects your own messages." description="Permanently delete your own messages once a room's retention window (Room Settings → Message Retention) has passed. Off by default; only affects your own messages."
after={ after={
<Switch <Switch
@@ -2783,7 +2834,7 @@ function Messages() {
direction="Column" direction="Column"
> >
<SettingTile <SettingTile
title="Translate Messages Into" title="Translate messages into"
description={ description={
translationSupported translationSupported
? 'Translate messages written in other languages into this one. Runs entirely on your device — message text never leaves your computer or touches a cloud service.' ? 'Translate messages written in other languages into this one. Runs entirely on your device — message text never leaves your computer or touches a cloud service.'
@@ -2801,7 +2852,7 @@ function Messages() {
/> />
{translationSupported && ( {translationSupported && (
<SettingTile <SettingTile
title="Auto-translate Incoming Messages" title="Auto-translate incoming messages"
description="Automatically translate messages that aren’t already in your language. When off, translate individual messages from their menu. The first time a language is used, its model downloads once (a few MB)." description="Automatically translate messages that aren’t already in your language. When off, translate individual messages from their menu. The first time a language is used, its model downloads once (a few MB)."
after={<Switch variant="Primary" value={autoTranslate} onChange={setAutoTranslate} />} after={<Switch variant="Primary" value={autoTranslate} onChange={setAutoTranslate} />}
/> />
@@ -2962,9 +3013,9 @@ function AppUpdates() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">App Updates</Text> <Text size="L400">App updates</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile title="Check for Updates" description={description} after={after} /> <SettingTile title="Check for updates" description={description} after={after} />
{packageUpdate && packageHelp && <PackageUpdate help={packageHelp} />} {packageUpdate && packageHelp && <PackageUpdate help={packageHelp} />}
{status.state === 'error' && !packageUpdate && ( {status.state === 'error' && !packageUpdate && (
<Text size="T200" priority="300" style={{ wordBreak: 'break-word' }}> <Text size="T200" priority="300" style={{ wordBreak: 'break-word' }}>
@@ -82,7 +82,7 @@ export function AllMessagesNotifications() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200"> <Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
<Text size="L400">All Messages</Text> <Text size="L400">All messages</Text>
<Box gap="100"> <Box gap="100">
<Text size="T200">Badge: </Text> <Text size="T200">Badge: </Text>
<Badge radii="300" variant="Secondary" fill="Solid"> <Badge radii="300" variant="Secondary" fill="Solid">
@@ -97,7 +97,7 @@ export function AllMessagesNotifications() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="1-to-1 Chats" title="1-to-1 chats"
after={<AllMessagesModeSwitcher pushRules={pushRules} ruleId={RuleId.DM} oneToOne />} after={<AllMessagesModeSwitcher pushRules={pushRules} ruleId={RuleId.DM} oneToOne />}
/> />
</SequenceCard> </SequenceCard>
@@ -108,7 +108,7 @@ export function AllMessagesNotifications() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="1-to-1 Chats (Encrypted)" title="1-to-1 chats (encrypted)"
after={ after={
<AllMessagesModeSwitcher <AllMessagesModeSwitcher
pushRules={pushRules} pushRules={pushRules}
@@ -137,7 +137,7 @@ export function AllMessagesNotifications() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Rooms (Encrypted)" title="Rooms (encrypted)"
after={ after={
<AllMessagesModeSwitcher <AllMessagesModeSwitcher
pushRules={pushRules} pushRules={pushRules}
@@ -171,7 +171,7 @@ export function KeywordMessagesNotifications() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200"> <Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
<Text size="L400">Keyword Messages</Text> <Text size="L400">Keyword messages</Text>
<Box gap="100"> <Box gap="100">
<Text size="T200">Badge: </Text> <Text size="T200">Badge: </Text>
<Badge radii="300" variant="Success" fill="Solid"> <Badge radii="300" variant="Success" fill="Solid">
@@ -186,7 +186,7 @@ export function KeywordMessagesNotifications() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Select Keyword" title="Add a keyword"
description="Set a notification preference for message containing given keyword." description="Set a notification preference for message containing given keyword."
> >
<KeywordInput /> <KeywordInput />
@@ -9,7 +9,6 @@ import { KeywordMessagesNotifications } from './KeywordMessages';
import { PushRuleEditor } from './PushRuleEditor'; import { PushRuleEditor } from './PushRuleEditor';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css'; import { SequenceCardStyle } from '../styles.css';
import { SettingTile } from '../../../components/setting-tile';
import { settingsAtom, Settings } from '../../../state/settings'; import { settingsAtom, Settings } from '../../../state/settings';
import { notificationSnoozeUntilAtom } from '../../../state/notificationSnooze'; import { notificationSnoozeUntilAtom } from '../../../state/notificationSnooze';
@@ -81,7 +80,7 @@ function NotificationPresets() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Quick Presets</Text> <Text size="L400">Quick presets</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column"> <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<Box gap="300" style={{ padding: config.space.S300, flexWrap: 'wrap' }}> <Box gap="300" style={{ padding: config.space.S300, flexWrap: 'wrap' }}>
{PRESETS.map((preset) => ( {PRESETS.map((preset) => (
@@ -150,17 +149,6 @@ export function Notifications({ requestClose }: NotificationsProps) {
<SpecialMessagesNotifications /> <SpecialMessagesNotifications />
<KeywordMessagesNotifications /> <KeywordMessagesNotifications />
<PushRuleEditor /> <PushRuleEditor />
<Box direction="Column" gap="100">
<Text size="L400">Block Messages</Text>
<SequenceCard
className={SequenceCardStyle}
variant="SurfaceVariant"
direction="Column"
gap="400"
>
<SettingTile description='This option has been moved to "Account > Block Users" section.' />
</SequenceCard>
</Box>
</Box> </Box>
</PageContent> </PageContent>
</Scroll> </Scroll>
@@ -333,10 +333,10 @@ export function PushRuleEditor() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Advanced Push Rules</Text> <Text size="L400">Advanced push rules</Text>
<RuleSection title="Override Rules" kind={PushRuleKind.Override} rules={overrideRules} /> <RuleSection title="Override rules" kind={PushRuleKind.Override} rules={overrideRules} />
<RuleSection <RuleSection
title="Room Rules" title="Room rules"
kind={PushRuleKind.RoomSpecific} kind={PushRuleKind.RoomSpecific}
rules={roomRules} rules={roomRules}
addForm={ addForm={
@@ -348,7 +348,7 @@ export function PushRuleEditor() {
} }
/> />
<RuleSection <RuleSection
title="Sender Rules" title="Sender rules"
kind={PushRuleKind.SenderSpecific} kind={PushRuleKind.SenderSpecific}
rules={senderRules} rules={senderRules}
addForm={ addForm={
@@ -359,7 +359,7 @@ export function PushRuleEditor() {
/> />
} }
/> />
<RuleSection title="Underride Rules" kind={PushRuleKind.Underride} rules={underrideRules} /> <RuleSection title="Underride rules" kind={PushRuleKind.Underride} rules={underrideRules} />
</Box> </Box>
); );
} }
@@ -124,7 +124,7 @@ export function SpecialMessagesNotifications() {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200"> <Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
<Text size="L400">Special Messages</Text> <Text size="L400">Special messages</Text>
<Box gap="100"> <Box gap="100">
<Text size="T200">Badge: </Text> <Text size="T200">Badge: </Text>
<Badge radii="300" variant="Success" fill="Solid"> <Badge radii="300" variant="Success" fill="Solid">
@@ -63,7 +63,7 @@ function EmailNotification() {
return ( return (
<SettingTile <SettingTile
title="Email Notification" title="Email notifications"
description={ description={
<> <>
{result && !result.email && ( {result && !result.email && (
@@ -161,7 +161,7 @@ function PauseNotifications() {
return ( return (
<SettingTile <SettingTile
title="Pause Notifications" title="Pause notifications"
description={<span style={active ? { color: color.Warning.Main } : undefined}>{status}</span>} description={<span style={active ? { color: color.Warning.Main } : undefined}>{status}</span>}
after={ after={
active ? ( active ? (
@@ -221,7 +221,7 @@ export function SystemNotification() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Desktop Notifications" title="Desktop notifications"
description={ description={
notifPermission === 'denied' ? ( notifPermission === 'denied' ? (
<Text as="span" style={{ color: color.Critical.Main }} size="T200"> <Text as="span" style={{ color: color.Critical.Main }} size="T200">
@@ -230,7 +230,7 @@ export function SystemNotification() {
: 'Notifications are not supported by the system.'} : 'Notifications are not supported by the system.'}
</Text> </Text>
) : ( ) : (
<span>Show desktop notifications when message arrive.</span> <span>Show desktop notifications when messages arrive.</span>
) )
} }
after={ after={
@@ -263,8 +263,8 @@ export function SystemNotification() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Notification Sound" title="Notification sound"
description="Play sound when new message arrive." description="Play a sound when a new message arrives."
after={<Switch value={isNotificationSounds} onChange={setIsNotificationSounds} />} after={<Switch value={isNotificationSounds} onChange={setIsNotificationSounds} />}
/> />
{isNotificationSounds && ( {isNotificationSounds && (
@@ -353,7 +353,7 @@ export function SystemNotification() {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Quiet Hours" title="Quiet hours"
description="Notifications are silenced between these times. Handles overnight (e.g. 11:00 PM – 8:00 AM)." description="Notifications are silenced between these times. Handles overnight (e.g. 11:00 PM – 8:00 AM)."
after={<Switch value={quietHoursEnabled} onChange={setQuietHoursEnabled} />} after={<Switch value={quietHoursEnabled} onChange={setQuietHoursEnabled} />}
/> />
+2 -2
View File
@@ -1015,13 +1015,13 @@ function TauriUpdateFeature() {
dismissToast(UPDATE_PROGRESS_TOAST); dismissToast(UPDATE_PROGRESS_TOAST);
if (!failure) return; if (!failure) return;
// Short on purpose; the full explanation and a Download installer button // Short on purpose; the full explanation and a Download installer button
// are in Settings → General → App Updates. // are in Settings → General → App updates.
const busy = failure.phase !== 'install' && isNetworkUpdateError(failure.message); const busy = failure.phase !== 'install' && isNetworkUpdateError(failure.message);
setToast({ setToast({
id: UPDATE_FAILED_TOAST, id: UPDATE_FAILED_TOAST,
iconSrc: Icons.Warning, iconSrc: Icons.Warning,
displayName: 'Update didn’t install', displayName: 'Update didn’t install',
body: `${busy ? 'The update server seems busy. ' : ''}Click to try again, or get the installer in Settings → General → App Updates.`, body: `${busy ? 'The update server seems busy. ' : ''}Click to try again, or get the installer in Settings → General → App updates.`,
roomName: 'System', roomName: 'System',
roomId: '', roomId: '',
sticky: true, sticky: true,