mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-02 08:15:55 +00:00
refactor: move identities, sub-addressing and multi-account options onto the account page
This commit is contained in:
@@ -84,7 +84,7 @@ export function AccountArchiveSettings() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsSection title={t('title')} description={t(passwordsAllowed ? 'description_passwords' : 'description')}>
|
||||
<SettingsSection id="backup" advanced title={t('title')} description={t(passwordsAllowed ? 'description_passwords' : 'description')}>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{saved === null ? '…' : t(saved ? 'status_saved' : 'status_none', { account: toUnicodeEmail(owner.username) })}
|
||||
</p>
|
||||
|
||||
@@ -146,7 +146,11 @@ function PasswordChangeSection() {
|
||||
);
|
||||
}
|
||||
|
||||
function DisplayNameSection() {
|
||||
/**
|
||||
* Editable display name (the Stalwart principal's name). Rendered by the
|
||||
* Account page's Profile section; the Security page no longer repeats it.
|
||||
*/
|
||||
export function DisplayNameSection() {
|
||||
const t = useTranslations('settings.security');
|
||||
const { displayName, updateDisplayName, isSaving, isLoadingPrincipal } = useAccountSecurityStore();
|
||||
const [name, setName] = useState(displayName);
|
||||
@@ -1508,8 +1512,6 @@ export function AccountSecuritySettings() {
|
||||
<>
|
||||
<PasswordChangeSection />
|
||||
<div className="border-t border-border" />
|
||||
<DisplayNameSection />
|
||||
<div className="border-t border-border" />
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Shield className="w-4 h-4 text-muted-foreground" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useCallback, useMemo } from 'react';
|
||||
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Check, GripVertical, Plus, Star, AlertCircle, ChevronRight } from '@/components/icons';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
@@ -18,6 +18,10 @@ import { toUnicodeDomain, toUnicodeEmail } from '@/lib/idn';
|
||||
import { IS_LITE } from '@/lib/lite';
|
||||
import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { AccountArchiveSettings } from './account-archive-settings';
|
||||
import { DisplayNameSection } from './account-security-settings';
|
||||
import { EmailAddressesSection } from './identity-settings';
|
||||
import { MultipleAccountsSection } from './layout-settings';
|
||||
import { useAccountSecurityStore } from '@/stores/account-security-store';
|
||||
|
||||
function hostnameOf(serverUrl: string): string {
|
||||
try { return toUnicodeDomain(new URL(serverUrl).hostname); } catch { return serverUrl; }
|
||||
@@ -42,6 +46,22 @@ export function AccountSettings() {
|
||||
const switchAccount = useAuthStore((s) => s.switchAccount);
|
||||
const setManagedAccount = useManagedAccountStore((s) => s.setManagedAccount);
|
||||
const archiveEnabled = usePolicyStore((s) => s.isFeatureEnabled('accountArchiveEnabled')) && !IS_LITE && !isDemoMode;
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
// The display name is editable on Stalwart (the principal's name) for
|
||||
// password sign-ins; OAuth and token sessions cannot manage the principal.
|
||||
const { isStalwart, probe, fetchPrincipal } = useAccountSecurityStore();
|
||||
const canEditDisplayName = isStalwart === true && authMode !== 'oauth' && authMode !== 'token';
|
||||
// Wait for `client` before probing, as the Security page does: on reload
|
||||
// the persisted session flips authenticated before the client reconnects.
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated || !client) return;
|
||||
if (isStalwart === null) {
|
||||
void probe().then((detected) => { if (detected) void fetchPrincipal(); });
|
||||
} else if (isStalwart) {
|
||||
void fetchPrincipal();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isAuthenticated, client, isStalwart]);
|
||||
|
||||
// Shared/group accounts delegated to this session (excludes the user's own
|
||||
// primary account). These can be drilled into for scoped settings editing.
|
||||
@@ -123,11 +143,15 @@ export function AccountSettings() {
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<SettingsSection title={t('title')} description={t('description')}>
|
||||
{/* Display Name */}
|
||||
<SettingItem label={t('name_label')}>
|
||||
<span className="text-sm text-foreground">{displayName || tCommon('unknown')}</span>
|
||||
</SettingItem>
|
||||
<SettingsSection id="profile" title={t('title')} description={t('description')}>
|
||||
{/* Display Name: editable where the server lets us, read-only otherwise. */}
|
||||
{canEditDisplayName ? (
|
||||
<DisplayNameSection />
|
||||
) : (
|
||||
<SettingItem label={t('name_label')}>
|
||||
<span className="text-sm text-foreground">{displayName || tCommon('unknown')}</span>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
{/* Email Address */}
|
||||
<SettingItem label={t('email.label')}>
|
||||
@@ -151,7 +175,7 @@ export function AccountSettings() {
|
||||
</SettingItem>
|
||||
|
||||
{/* Server */}
|
||||
<SettingItem label={t('server.label')}>
|
||||
<SettingItem label={t('server.label')} advanced>
|
||||
<span className="text-sm text-foreground truncate max-w-xs">
|
||||
{serverUrl || tCommon('unknown')}
|
||||
</span>
|
||||
@@ -191,9 +215,11 @@ export function AccountSettings() {
|
||||
)}
|
||||
</SettingsSection>
|
||||
|
||||
<EmailAddressesSection />
|
||||
|
||||
{/* Logged-in accounts list */}
|
||||
{accounts.length > 0 && (
|
||||
<SettingsSection title={t('accounts.title')} description={t('accounts.description')}>
|
||||
<SettingsSection id="browser" title={t('accounts.title')} description={t('accounts.description')}>
|
||||
<div className="space-y-2">
|
||||
{accounts.map((a, index) => (
|
||||
<AccountRow
|
||||
@@ -241,10 +267,12 @@ export function AccountSettings() {
|
||||
|
||||
{archiveEnabled && <AccountArchiveSettings />}
|
||||
|
||||
<MultipleAccountsSection />
|
||||
|
||||
{/* Shared / group accounts delegated to this session. Clicking one drills
|
||||
into a scoped settings view (filters, vacation, calendars, contacts). */}
|
||||
{sharedAccounts.length > 0 && (
|
||||
<SettingsSection title={t('shared_accounts.title')} description={t('shared_accounts.description')}>
|
||||
<SettingsSection id="shared" title={t('shared_accounts.title')} description={t('shared_accounts.description')}>
|
||||
<div className="space-y-2">
|
||||
{sharedAccounts.map((acc) => {
|
||||
const editable = firstScopedTab(acc.capabilities) !== null;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useThemeStore } from '@/stores/theme-store';
|
||||
import { useSettingsStore, type Density } from '@/stores/settings-store';
|
||||
import { SettingsSection, SettingItem, RadioGroup, ToggleSwitch } from './settings-section';
|
||||
import { ThemesSettings } from './themes-settings';
|
||||
import { LayoutSection, SidebarSection, MultipleAccountsSection, ExperimentalSection } from './layout-settings';
|
||||
import { LayoutSection, SidebarSection, ExperimentalSection } from './layout-settings';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTour } from '@/components/tour/tour-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -139,7 +139,6 @@ export function AppearanceSettings() {
|
||||
|
||||
<LayoutSection />
|
||||
<SidebarSection />
|
||||
<MultipleAccountsSection />
|
||||
|
||||
<SettingsSection id="tour" title={tTour('settings_section')}>
|
||||
<SettingItem label={tTour('restart_title')} description={tTour('restart_desc')}>
|
||||
|
||||
@@ -8,14 +8,6 @@ import { useAuthStore } from '@/stores/auth-store';
|
||||
import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
|
||||
import { X } from '@/components/icons';
|
||||
import {
|
||||
SUPPORTED_SUB_ADDRESS_DELIMITERS,
|
||||
isSupportedSubAddressDelimiter,
|
||||
isValidSubAddressDelimiter,
|
||||
} from '@/lib/sub-addressing';
|
||||
|
||||
const CUSTOM_DELIMITER_SENTINEL = '__custom__';
|
||||
const DEFAULT_CUSTOM_DELIMITER = '~';
|
||||
|
||||
export function ComposingSettings() {
|
||||
const t = useTranslations('settings.email_behavior');
|
||||
@@ -32,7 +24,6 @@ export function ComposingSettings() {
|
||||
recipientMentionsEnabled,
|
||||
mentionAddsRecipientTo,
|
||||
sendDelaySeconds,
|
||||
subAddressDelimiter,
|
||||
signaturePosition,
|
||||
signatureSeparatorEnabled,
|
||||
requestReadReceiptDefault,
|
||||
@@ -135,49 +126,6 @@ export function ComposingSettings() {
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
label={t('sub_address_delimiter.label')}
|
||||
description={t('sub_address_delimiter.description', { delimiter: subAddressDelimiter })}
|
||||
>
|
||||
<div className="flex flex-col items-end gap-2">
|
||||
<Select
|
||||
value={isSupportedSubAddressDelimiter(subAddressDelimiter) ? subAddressDelimiter : CUSTOM_DELIMITER_SENTINEL}
|
||||
onChange={(value) => {
|
||||
if (value === CUSTOM_DELIMITER_SENTINEL) {
|
||||
if (isSupportedSubAddressDelimiter(subAddressDelimiter)) {
|
||||
updateSetting('subAddressDelimiter', DEFAULT_CUSTOM_DELIMITER);
|
||||
}
|
||||
} else {
|
||||
updateSetting('subAddressDelimiter', value);
|
||||
}
|
||||
}}
|
||||
options={[
|
||||
...SUPPORTED_SUB_ADDRESS_DELIMITERS.map((delim) => ({
|
||||
value: delim,
|
||||
label: t('sub_address_delimiter.option', { delimiter: delim }),
|
||||
})),
|
||||
{ value: CUSTOM_DELIMITER_SENTINEL, label: t('sub_address_delimiter.custom') },
|
||||
]}
|
||||
/>
|
||||
{!isSupportedSubAddressDelimiter(subAddressDelimiter) && (
|
||||
<input
|
||||
type="text"
|
||||
maxLength={1}
|
||||
value={subAddressDelimiter}
|
||||
onChange={(e) => {
|
||||
const next = e.target.value.slice(0, 1);
|
||||
if (next && isValidSubAddressDelimiter(next)) {
|
||||
updateSetting('subAddressDelimiter', next);
|
||||
}
|
||||
}}
|
||||
aria-label={t('sub_address_delimiter.custom_input_label')}
|
||||
placeholder={DEFAULT_CUSTOM_DELIMITER}
|
||||
className="w-16 px-2 py-1 text-sm font-mono text-center bg-background border border-border rounded-md focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t('empty_subject_warning.label')} description={t('empty_subject_warning.description')}>
|
||||
<ToggleSwitch
|
||||
checked={emptySubjectWarningEnabled}
|
||||
|
||||
@@ -3,18 +3,36 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SettingsSection, SettingItem } from './settings-section';
|
||||
import { SettingsSection, SettingItem, Select } from './settings-section';
|
||||
import { IdentityManagerModal } from '@/components/identity/identity-manager-modal';
|
||||
import { useIdentityStore } from '@/stores/identity-store';
|
||||
import { useSettingsStore } from '@/stores/settings-store';
|
||||
import {
|
||||
SUPPORTED_SUB_ADDRESS_DELIMITERS,
|
||||
isSupportedSubAddressDelimiter,
|
||||
isValidSubAddressDelimiter,
|
||||
} from '@/lib/sub-addressing';
|
||||
|
||||
export function IdentitySettings() {
|
||||
const CUSTOM_DELIMITER_SENTINEL = '__custom__';
|
||||
const DEFAULT_CUSTOM_DELIMITER = '~';
|
||||
|
||||
/**
|
||||
* The Account page's "Email addresses" section: the identities you send
|
||||
* from (each with its name and signature), sub-addressing and, with
|
||||
* Advanced on, the delimiter the server uses for it. Was its own
|
||||
* Identities tab, with the delimiter over on Composing.
|
||||
*/
|
||||
export function EmailAddressesSection() {
|
||||
const t = useTranslations('settings.identities');
|
||||
const tAccount = useTranslations('settings.account');
|
||||
const tEmail = useTranslations('settings.email_behavior');
|
||||
const { identities } = useIdentityStore();
|
||||
const { subAddressDelimiter, updateSetting } = useSettingsStore();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSection title={t('title')} description={t('description')}>
|
||||
<SettingsSection id="addresses" title={tAccount('sections.addresses')} description={t('description')}>
|
||||
{/* Identity Count */}
|
||||
<SettingItem
|
||||
label={t('identities_count.label')}
|
||||
@@ -43,6 +61,51 @@ export function IdentitySettings() {
|
||||
{t('sub_addressing.learn_more')}
|
||||
</Button>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
nested
|
||||
advanced
|
||||
label={tEmail('sub_address_delimiter.label')}
|
||||
description={tEmail('sub_address_delimiter.description', { delimiter: subAddressDelimiter })}
|
||||
>
|
||||
<div className="flex flex-col items-end gap-2">
|
||||
<Select
|
||||
value={isSupportedSubAddressDelimiter(subAddressDelimiter) ? subAddressDelimiter : CUSTOM_DELIMITER_SENTINEL}
|
||||
onChange={(value) => {
|
||||
if (value === CUSTOM_DELIMITER_SENTINEL) {
|
||||
if (isSupportedSubAddressDelimiter(subAddressDelimiter)) {
|
||||
updateSetting('subAddressDelimiter', DEFAULT_CUSTOM_DELIMITER);
|
||||
}
|
||||
} else {
|
||||
updateSetting('subAddressDelimiter', value);
|
||||
}
|
||||
}}
|
||||
options={[
|
||||
...SUPPORTED_SUB_ADDRESS_DELIMITERS.map((delim) => ({
|
||||
value: delim,
|
||||
label: tEmail('sub_address_delimiter.option', { delimiter: delim }),
|
||||
})),
|
||||
{ value: CUSTOM_DELIMITER_SENTINEL, label: tEmail('sub_address_delimiter.custom') },
|
||||
]}
|
||||
/>
|
||||
{!isSupportedSubAddressDelimiter(subAddressDelimiter) && (
|
||||
<input
|
||||
type="text"
|
||||
maxLength={1}
|
||||
value={subAddressDelimiter}
|
||||
onChange={(e) => {
|
||||
const next = e.target.value.slice(0, 1);
|
||||
if (next && isValidSubAddressDelimiter(next)) {
|
||||
updateSetting('subAddressDelimiter', next);
|
||||
}
|
||||
}}
|
||||
aria-label={tEmail('sub_address_delimiter.custom_input_label')}
|
||||
placeholder={DEFAULT_CUSTOM_DELIMITER}
|
||||
className="w-16 px-2 py-1 text-sm font-mono text-center bg-background border border-border rounded-md focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Identity Manager Modal */}
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
Search,
|
||||
User,
|
||||
Shield,
|
||||
UserPen,
|
||||
PalmtreeIcon,
|
||||
Calendar,
|
||||
Filter,
|
||||
@@ -44,7 +43,6 @@ import { ReadingSettings } from '@/components/settings/reading-settings';
|
||||
import { MessageListSettings } from '@/components/settings/message-list-settings';
|
||||
import { ComposingSettings } from '@/components/settings/composing-settings';
|
||||
import { AccountSettings } from '@/components/settings/account-settings';
|
||||
import { IdentitySettings } from '@/components/settings/identity-settings';
|
||||
import { VacationSettings } from '@/components/settings/vacation-settings';
|
||||
import { CalendarSettings } from '@/components/settings/calendar-settings';
|
||||
import { CalendarManagementSettings } from '@/components/settings/calendar-management-settings';
|
||||
@@ -134,7 +132,6 @@ const tabIcons: Record<Tab, AppIcon> = {
|
||||
reading: BookOpen,
|
||||
composing: PenLine,
|
||||
downloads: Download,
|
||||
identities: UserPen,
|
||||
vacation: PalmtreeIcon,
|
||||
filters: Filter,
|
||||
templates: FileText,
|
||||
@@ -162,6 +159,7 @@ export const LEGACY_TAB_MAP: Record<string, Tab> = {
|
||||
layout: 'appearance',
|
||||
themes: 'appearance',
|
||||
content_senders: 'reading',
|
||||
identities: 'account',
|
||||
};
|
||||
|
||||
function readPersistedTab(): SettingsTabId {
|
||||
@@ -571,7 +569,6 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
{ id: 'reading', label: t('tabs.reading'), icon: tabIcons.reading, group: 'mail' },
|
||||
{ id: 'composing', label: t('tabs.composing'), icon: tabIcons.composing, group: 'mail' },
|
||||
{ id: 'downloads', label: t('tabs.downloads'), icon: tabIcons.downloads, group: 'mail' },
|
||||
{ id: 'identities', label: t('tabs.identities'), icon: tabIcons.identities, group: 'mail' },
|
||||
...(supportsVacation ? [{ id: 'vacation' as Tab, label: t('tabs.vacation'), icon: tabIcons.vacation, group: 'mail' as TabGroup }] : []),
|
||||
...(supportsSieve ? [{ id: 'filters' as Tab, label: t('tabs.filters'), icon: tabIcons.filters, group: 'mail' as TabGroup }] : []),
|
||||
...(isFeatureEnabled('templatesEnabled') ? [{ id: 'templates' as Tab, label: t('tabs.templates'), icon: tabIcons.templates, group: 'mail' as TabGroup }] : []),
|
||||
@@ -715,7 +712,6 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
{effectiveActiveTab === 'reading' && <ReadingSettings />}
|
||||
{effectiveActiveTab === 'composing' && <ComposingSettings />}
|
||||
{effectiveActiveTab === 'downloads' && <DownloadsSettings />}
|
||||
{effectiveActiveTab === 'identities' && <IdentitySettings />}
|
||||
{effectiveActiveTab === 'vacation' && <VacationSettings />}
|
||||
{effectiveActiveTab === 'filters' && <FilterSettings />}
|
||||
{effectiveActiveTab === 'templates' && <TemplateSettings />}
|
||||
|
||||
@@ -93,7 +93,8 @@ describe('settings search index (English messages)', () => {
|
||||
expect(labelsFor('composing')).toContain('Undo send / send delay');
|
||||
expect(labelsFor('composing')).toContain('Request read receipts by default');
|
||||
expect(labelsFor('appearance')).toContain('Pro Interface (Experimental)');
|
||||
expect(labelsFor('appearance')).toContain('Tell accounts apart');
|
||||
expect(labelsFor('account')).toContain('Tell accounts apart');
|
||||
expect(labelsFor('account')).toContain('Your Identities');
|
||||
expect(labelsFor('appearance')).toContain('Themes');
|
||||
expect(labelsFor('language')).toContain('Time zone');
|
||||
});
|
||||
|
||||
@@ -38,10 +38,10 @@ import { RELEASE_VERSION } from '@/lib/release-version';
|
||||
* opens the page that holds the setting now (the settings page migrates
|
||||
* the id). Listed so an unknown tab is refused here.
|
||||
*/
|
||||
const LEGACY_SETTINGS_TABS = ['layout', 'themes', 'content_senders'] as const;
|
||||
const LEGACY_SETTINGS_TABS = ['layout', 'themes', 'content_senders', 'identities'] as const;
|
||||
const SETTINGS_TABS: readonly (SettingsSearchTab | typeof LEGACY_SETTINGS_TABS[number])[] = [
|
||||
'account', 'language', 'notifications', 'appearance', 'message_list', 'reading',
|
||||
'composing', 'downloads', 'identities', 'vacation', 'filters', 'templates',
|
||||
'composing', 'downloads', 'vacation', 'filters', 'templates',
|
||||
'folders', 'keywords', 'security', 'calendar', 'contacts',
|
||||
'files', 'protocol_handlers', 'sidebar_apps', 'about_data',
|
||||
'plugins', 'debug',
|
||||
|
||||
+14
-12
@@ -11,7 +11,6 @@ export type SettingsSearchTab =
|
||||
| 'reading'
|
||||
| 'composing'
|
||||
| 'downloads'
|
||||
| 'identities'
|
||||
| 'vacation'
|
||||
| 'filters'
|
||||
| 'templates'
|
||||
@@ -36,13 +35,25 @@ type Tab = SettingsSearchTab;
|
||||
export const tabSearchPaths: Record<Tab, string[]> = {
|
||||
account: [
|
||||
'settings.account.name_label',
|
||||
'settings.security.display_name',
|
||||
'settings.account.username_label',
|
||||
'settings.account.account_type_label',
|
||||
'settings.account.auth_method_label',
|
||||
'settings.account.email',
|
||||
'settings.account.server',
|
||||
'settings.account.storage',
|
||||
'settings.identities',
|
||||
'settings.email_behavior.sub_address_delimiter',
|
||||
'settings.account.accounts',
|
||||
'settings.account.backup',
|
||||
'settings.appearance.unified_mailbox',
|
||||
'settings.appearance.cross_unread',
|
||||
'settings.appearance.cross_starred',
|
||||
'settings.appearance.cross_all',
|
||||
'settings.appearance.all_mail',
|
||||
'settings.appearance.account_marker',
|
||||
'settings.appearance.show_shared_mail_accounts',
|
||||
'settings.account.shared_accounts',
|
||||
],
|
||||
language: ['settings.language_region'],
|
||||
notifications: ['settings.notifications'],
|
||||
@@ -61,13 +72,6 @@ export const tabSearchPaths: Record<Tab, string[]> = {
|
||||
'settings.appearance.hide_account_switcher',
|
||||
'settings.appearance.show_rail_account_list',
|
||||
'settings.appearance.favicon_unread_badge',
|
||||
'settings.appearance.unified_mailbox',
|
||||
'settings.appearance.cross_unread',
|
||||
'settings.appearance.cross_starred',
|
||||
'settings.appearance.cross_all',
|
||||
'settings.appearance.all_mail',
|
||||
'settings.appearance.account_marker',
|
||||
'settings.appearance.show_shared_mail_accounts',
|
||||
'tour.restart_title',
|
||||
'tour.show_on_new_devices_title',
|
||||
'settings.appearance.pro_interface',
|
||||
@@ -120,7 +124,6 @@ export const tabSearchPaths: Record<Tab, string[]> = {
|
||||
'settings.email_behavior.sub_address_delimiter',
|
||||
],
|
||||
downloads: ['settings.downloads'],
|
||||
identities: ['settings.identities'],
|
||||
vacation: ['settings.vacation'],
|
||||
filters: ['settings.filters'],
|
||||
templates: ['settings.templates'],
|
||||
@@ -143,15 +146,14 @@ export const tabSearchPaths: Record<Tab, string[]> = {
|
||||
export const archiveActionSearchPaths: ReadonlySet<string> = new Set(['settings.email_behavior.archive_mode']);
|
||||
|
||||
export const tabKeywords: Record<Tab, string> = {
|
||||
account: 'profile email password user signin signout reorder rearrange drag dropdown switcher multi-account',
|
||||
account: 'profile email user signin signout reorder rearrange drag dropdown switcher multi-account identities from address signature plus addressing unified mailbox shared group',
|
||||
language: 'locale region timezone date time format',
|
||||
notifications: 'sound alert push badge',
|
||||
appearance: 'theme dark light font size accent color animation density custom theme css skin toolbar sidebar account switcher unified mailbox icons rail layout reading pane tour',
|
||||
appearance: 'theme dark light font size accent color animation density custom theme css skin toolbar sidebar account switcher icons rail layout reading pane tour',
|
||||
message_list: 'inbox list preview thread conversation grouping sort order columns archive delete swipe hover favicon avatar tag color',
|
||||
reading: 'mark read attachment open monospace mono font plain text images remote content block sender privacy tracking dark light',
|
||||
composing: 'editor signature plain text reply forward draft compose',
|
||||
downloads: 'download filename template eml attachment save export',
|
||||
identities: 'from address signature email',
|
||||
vacation: 'auto reply away out of office holiday responder',
|
||||
filters: 'sieve rules block junk forward',
|
||||
templates: 'snippet quick reply',
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "مشترك معي",
|
||||
"description": "الحسابات الجماعية والمشتركة التي يمكنك إدارتها. اختر واحدًا لتعديل مرشحاته، والرد التلقائي أثناء الغياب، والتقويمات، وجهات الاتصال.",
|
||||
"shared_label": "حساب مشترك"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "عناوين البريد الإلكتروني"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Compartit amb mi",
|
||||
"description": "Comptes de grup i compartits que podeu gestionar. Seleccioneu-ne un per editar-ne els filtres, la resposta automàtica, els calendaris i els contactes.",
|
||||
"shared_label": "Compte compartit"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Adreces de correu"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Sdíleno se mnou",
|
||||
"description": "Skupinové a sdílené účty, které můžete spravovat. Vyberte jeden pro úpravu jeho filtrů, odpovědi v nepřítomnosti, kalendářů a kontaktů.",
|
||||
"shared_label": "Sdílený účet"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-mailové adresy"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Delt med mig",
|
||||
"description": "Gruppe- og delte konti, du kan administrere. Vælg en for at redigere dens filtre, autosvar ved fravær, kalendere og kontakter.",
|
||||
"shared_label": "Delt konto"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-mailadresser"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Mit mir geteilt",
|
||||
"description": "Gruppen- und freigegebene Konten, die Sie verwalten können. Wählen Sie eines aus, um dessen Filter, Abwesenheitsnotiz, Kalender und Kontakte zu bearbeiten.",
|
||||
"shared_label": "Freigegebenes Konto"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-Mail-Adressen"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Shared with me",
|
||||
"description": "Group and shared accounts you can manage. Select one to edit its filters, vacation responder, calendars, and contacts.",
|
||||
"shared_label": "Shared account"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Email addresses"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Compartido conmigo",
|
||||
"description": "Cuentas de grupo y compartidas que puedes gestionar. Selecciona una para editar sus filtros, respuesta de vacaciones, calendarios y contactos.",
|
||||
"shared_label": "Cuenta compartida"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Direcciones de correo"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Minuga jagatud",
|
||||
"description": "Grupi- ja jagatud kontod, mida saad hallata. Vali üks, et muuta selle filtreid, puhkusevastust, kalendreid ja kontakte.",
|
||||
"shared_label": "Jagatud konto"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-posti aadressid"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "اشتراکی با من",
|
||||
"description": "حسابهای گروهی",
|
||||
"shared_label": "حساب اشتراکی"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "نشانیهای ایمیل"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Partagé avec moi",
|
||||
"description": "Comptes de groupe et partagés que vous pouvez gérer. Sélectionnez-en un pour modifier ses filtres, son répondeur d'absence, ses calendriers et ses contacts.",
|
||||
"shared_label": "Compte partagé"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Adresses e-mail"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1795,6 +1795,9 @@
|
||||
"title": "משותף איתי",
|
||||
"description": "חשבונות קבוצתיים ומשותפים שאתה יכול לנהל. בחר אחד כדי לערוך את המסננים, המגיב לחופשה, לוחות השנה והאנשים שלו.",
|
||||
"shared_label": "חשבון משותף"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "כתובות אימייל"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Velem megosztva",
|
||||
"description": "Csoportos és megosztott fiókok, amelyeket kezelhet. Válasszon egyet a szűrői, távolléti válasza, naptárai és névjegyei szerkesztéséhez.",
|
||||
"shared_label": "Megosztott fiók"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-mail-címek"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Condivisi con me",
|
||||
"description": "Account di gruppo e condivisi che puoi gestire. Selezionane uno per modificarne filtri, risponditore automatico, calendari e contatti.",
|
||||
"shared_label": "Account condiviso"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Indirizzi email"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "自分と共有",
|
||||
"description": "管理できるグループアカウントと共有アカウントです。1つ選択して、フィルター、不在応答、カレンダー、連絡先を編集します。",
|
||||
"shared_label": "共有アカウント"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "メールアドレス"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "나와 공유됨",
|
||||
"description": "관리할 수 있는 그룹 및 공유 계정입니다. 하나를 선택하여 필터, 부재 중 자동 응답, 캘린더, 연락처를 편집하세요.",
|
||||
"shared_label": "공유 계정"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "이메일 주소"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Kopīgots ar mani",
|
||||
"description": "Grupas un kopīgotie konti, kurus varat pārvaldīt. Atlasiet vienu, lai rediģētu tā filtrus, prombūtnes atbildētāju, kalendārus un kontaktus.",
|
||||
"shared_label": "Kopīgots konts"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-pasta adreses"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Надтай хуваалцсан",
|
||||
"description": "Бүлэг болон хуваалцсан акаунтуудыг удирдах боломжтой. Шүүлтүүр, амралтын хариулагч, хуанли, харилцагчдыг засахын тулд нэгийг нь сонгоно уу.",
|
||||
"shared_label": "Хуваалцсан бүртгэл"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Имэйл хаягууд"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Delt med meg",
|
||||
"description": "Gruppekontoer og delte kontoer du kan administrere. Velg en konto for å redigere filtre, feriesvar, kalendere og kontakter.",
|
||||
"shared_label": "Delt konto"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-postadresser"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Gedeeld met mij",
|
||||
"description": "Groeps- en gedeelde accounts die u kunt beheren. Selecteer er een om de filters, afwezigheidsassistent, agenda's en contacten te bewerken.",
|
||||
"shared_label": "Gedeeld account"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-mailadressen"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Udostępnione mnie",
|
||||
"description": "Konta grupowe i udostępnione, którymi możesz zarządzać. Wybierz jedno, aby edytować jego filtry, odpowiedź wakacyjną, kalendarze i kontakty.",
|
||||
"shared_label": "Konto udostępnione"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Adresy e-mail"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Compartilhado comigo",
|
||||
"description": "Contas de grupo e compartilhadas que você pode gerenciar. Selecione uma para editar seus filtros, resposta de ausência, calendários e contatos.",
|
||||
"shared_label": "Conta compartilhada"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Endereços de e-mail"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Partajat cu mine",
|
||||
"description": "Conturi de grup și conturi partajate pe care le puteți gestiona. Selectați unul pentru a-i edita filtrele, răspunsul automat de vacanță, calendarele și contactele.",
|
||||
"shared_label": "Cont partajat"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Adrese de e-mail"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Доступные мне",
|
||||
"description": "Групповые и общие учётные записи, которыми вы можете управлять. Выберите одну, чтобы изменить её фильтры, автоответчик, календари и контакты.",
|
||||
"shared_label": "Общая учётная запись"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Адреса электронной почты"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "Zdieľané so mnou",
|
||||
"description": "Skupinové a zdieľané účty, ktoré môžete spravovať.",
|
||||
"shared_label": "Zdieľaný účet"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-mailové adresy"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Benimle paylaşılanlar",
|
||||
"description": "Yönetebileceğiniz grup ve paylaşılan hesaplar. Filtrelerini, tatil yanıtlayıcısını, takvimlerini ve kişilerini düzenlemek için birini seçin.",
|
||||
"shared_label": "Paylaşılan hesap"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "E-posta adresleri"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "Доступні мені",
|
||||
"description": "Групові та спільні облікові записи, якими ви можете керувати. Виберіть один, щоб змінити його фільтри, автовідповідач, календарі та контакти.",
|
||||
"shared_label": "Спільний обліковий запис"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "Адреси електронної пошти"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
@@ -1833,6 +1833,9 @@
|
||||
"title": "與我共用",
|
||||
"description": "您可以管理的群組與共用帳號。選取一個帳號,即可編輯其篩選規則、休假自動回覆、行事曆與聯絡人。",
|
||||
"shared_label": "共用帳號"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "電子郵件地址"
|
||||
}
|
||||
},
|
||||
"scoped": {
|
||||
|
||||
@@ -1830,6 +1830,9 @@
|
||||
"title": "与我共享",
|
||||
"description": "您可以管理的群组和共享账户。选择一个以编辑其筛选器、休假自动回复、日历和联系人。",
|
||||
"shared_label": "共享账户"
|
||||
},
|
||||
"sections": {
|
||||
"addresses": "电子邮件地址"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
||||
Reference in New Issue
Block a user