mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 16:05:39 +00:00
feat: take archive out of the swipe and quick action settings when the organisation turns it off
This commit is contained in:
@@ -7,7 +7,6 @@ import { useColors } from '../../theme/colors';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
import {
|
||||
useSettingsStore,
|
||||
normalizeBottomQuickActions,
|
||||
REPLY_QUICK_ACTIONS,
|
||||
ALL_QUICK_ACTIONS,
|
||||
type SwipeAction,
|
||||
@@ -16,7 +15,8 @@ import {
|
||||
} from '../../stores/settings-store';
|
||||
import { useEmailStore } from '../../stores/email-store';
|
||||
import { useKeywordsStore } from '../../stores/keywords-store';
|
||||
import { useSettingLocked } from '../../stores/policy-store';
|
||||
import { usePolicyFeature, useSettingLocked } from '../../stores/policy-store';
|
||||
import { bottomQuickActionsFor, swipeActionShown, withoutArchive } from '../../lib/swipe-actions';
|
||||
import {
|
||||
ORDER_PRESETS, detectPreset, presetLevels, sanitizeSortLevels,
|
||||
type OrderPreset, type MessageListOrderScope,
|
||||
@@ -74,12 +74,16 @@ export function LayoutSettings() {
|
||||
const hydrated = useSettingsStore((s) => s.hydrated);
|
||||
const hydrate = useSettingsStore((s) => s.hydrate);
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
const SWIPE_OPTIONS = React.useMemo(() => swipeOptions(t), [t]);
|
||||
// With the organisation's Archive action off, Archive is no swipe or quick
|
||||
// action, and a stored one shows as what it does (webmail
|
||||
// message-list-settings.tsx).
|
||||
const archiveEnabled = usePolicyFeature('archiveActionEnabled');
|
||||
const SWIPE_OPTIONS = React.useMemo(() => withoutArchive(swipeOptions(t), archiveEnabled), [t, archiveEnabled]);
|
||||
const SWIPE_MODE_OPTIONS = React.useMemo(() => swipeModeOptions(t), [t]);
|
||||
const QUICK_ACTION_LABELS = React.useMemo(() => quickActionLabels(t), [t]);
|
||||
const QUICK_ACTION_OPTIONS = React.useMemo(
|
||||
() => ALL_QUICK_ACTIONS.map((value) => ({ value, label: QUICK_ACTION_LABELS[value] })),
|
||||
[QUICK_ACTION_LABELS],
|
||||
() => withoutArchive(ALL_QUICK_ACTIONS.map((value) => ({ value, label: QUICK_ACTION_LABELS[value] })), archiveEnabled),
|
||||
[QUICK_ACTION_LABELS, archiveEnabled],
|
||||
);
|
||||
|
||||
useEffect(() => { if (!hydrated) void hydrate(); }, [hydrated, hydrate]);
|
||||
@@ -125,7 +129,7 @@ export function LayoutSettings() {
|
||||
{ value: 'all', label: t('settings.message_list_order.scope_all', 'All folders') },
|
||||
];
|
||||
|
||||
const bottomActions = normalizeBottomQuickActions(bottomQuickActionsRaw);
|
||||
const bottomActions = bottomQuickActionsFor(bottomQuickActionsRaw, archiveEnabled);
|
||||
// Pick an action for a bar slot. If the chosen action already occupies
|
||||
// another slot, swap the two so the three slots stay unique.
|
||||
const setQuickActionSlot = (index: number, value: QuickAction) => {
|
||||
@@ -242,7 +246,7 @@ export function LayoutSettings() {
|
||||
{t('settings.email_behavior.swipe_right_action.description_mobile', "Action when you drag a row from its left edge towards the right.")}
|
||||
</Text>
|
||||
<RadioGroup
|
||||
value={swipeRightAction}
|
||||
value={swipeActionShown(swipeRightAction, archiveEnabled)}
|
||||
onChange={(v) => setSwipeRightAction(v as SwipeAction)}
|
||||
options={SWIPE_OPTIONS}
|
||||
/>
|
||||
@@ -257,7 +261,7 @@ export function LayoutSettings() {
|
||||
{t('settings.email_behavior.swipe_left_action.description_mobile', "Action when you drag a row from its right edge towards the left.")}
|
||||
</Text>
|
||||
<RadioGroup
|
||||
value={swipeLeftAction}
|
||||
value={swipeActionShown(swipeLeftAction, archiveEnabled)}
|
||||
onChange={(v) => setSwipeLeftAction(v as SwipeAction)}
|
||||
options={SWIPE_OPTIONS}
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useEmailStore } from '../../stores/email-store';
|
||||
import { archiveEmails, queryEmails, getEmails } from '../../api/email';
|
||||
import { ownMailboxes } from '../../lib/mailbox-tree';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
import { usePolicyFeature } from '../../stores/policy-store';
|
||||
|
||||
export function ReadingSettings() {
|
||||
const c = useColors();
|
||||
@@ -28,6 +29,9 @@ export function ReadingSettings() {
|
||||
const update = useSettingsStore((s) => s.updateSetting);
|
||||
|
||||
const archiveMode = useSettingsStore((s) => s.archiveMode);
|
||||
// Where archived mail goes means nothing with the organisation's Archive
|
||||
// action off (webmail message-list-settings.tsx).
|
||||
const archiveEnabled = usePolicyFeature('archiveActionEnabled');
|
||||
const markAsReadDelay = useSettingsStore((s) => s.markAsReadDelay);
|
||||
const deleteAction = useSettingsStore((s) => s.deleteAction);
|
||||
const permanentlyDeleteJunk = useSettingsStore((s) => s.permanentlyDeleteJunk);
|
||||
@@ -147,40 +151,42 @@ export function ReadingSettings() {
|
||||
<View style={styles.divider} />
|
||||
</View>
|
||||
|
||||
<View style={styles.group}>
|
||||
<SettingItem label={t('settings.email_behavior.archive_mode.label', "Archive in")} description={t('settings.email_behavior.archive_mode.description', "How to organize emails when archiving")} noBorder />
|
||||
<Select
|
||||
value={archiveMode}
|
||||
onChange={(v) => update('archiveMode', v as ArchiveMode)}
|
||||
options={[
|
||||
{ value: 'single', label: t('settings.email_behavior.archive_mode.single', "A single folder") },
|
||||
{ value: 'year', label: t('settings.email_behavior.archive_mode.year', "A folder per year") },
|
||||
{ value: 'month', label: t('settings.email_behavior.archive_mode.month', "A folder per month") },
|
||||
]}
|
||||
/>
|
||||
{archiveMode !== 'single' && (
|
||||
<>
|
||||
<Pressable
|
||||
style={[styles.inlineBtn, reorganizing && styles.inlineBtnDisabled]}
|
||||
onPress={reorganizing ? undefined : handleReorganizeArchive}
|
||||
disabled={reorganizing}
|
||||
>
|
||||
{reorganizing ? (
|
||||
<ActivityIndicator size="small" color={c.text} />
|
||||
) : (
|
||||
<FolderSync size={14} color={c.text} />
|
||||
{archiveEnabled && (
|
||||
<View style={styles.group}>
|
||||
<SettingItem label={t('settings.email_behavior.archive_mode.label', "Archive in")} description={t('settings.email_behavior.archive_mode.description', "How to organize emails when archiving")} noBorder />
|
||||
<Select
|
||||
value={archiveMode}
|
||||
onChange={(v) => update('archiveMode', v as ArchiveMode)}
|
||||
options={[
|
||||
{ value: 'single', label: t('settings.email_behavior.archive_mode.single', "A single folder") },
|
||||
{ value: 'year', label: t('settings.email_behavior.archive_mode.year', "A folder per year") },
|
||||
{ value: 'month', label: t('settings.email_behavior.archive_mode.month', "A folder per month") },
|
||||
]}
|
||||
/>
|
||||
{archiveMode !== 'single' && (
|
||||
<>
|
||||
<Pressable
|
||||
style={[styles.inlineBtn, reorganizing && styles.inlineBtnDisabled]}
|
||||
onPress={reorganizing ? undefined : handleReorganizeArchive}
|
||||
disabled={reorganizing}
|
||||
>
|
||||
{reorganizing ? (
|
||||
<ActivityIndicator size="small" color={c.text} />
|
||||
) : (
|
||||
<FolderSync size={14} color={c.text} />
|
||||
)}
|
||||
<Text style={styles.inlineBtnText}>
|
||||
{reorganizing ? t('settings.email_behavior.archive_mode.reorganizing', "Reorganizing…") : t('settings.email_behavior.archive_mode.reorganize', "Reorganize existing archive")}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{reorganizeResult && (
|
||||
<Text style={styles.reorganizeResult}>{reorganizeResult}</Text>
|
||||
)}
|
||||
<Text style={styles.inlineBtnText}>
|
||||
{reorganizing ? t('settings.email_behavior.archive_mode.reorganizing', "Reorganizing…") : t('settings.email_behavior.archive_mode.reorganize', "Reorganize existing archive")}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{reorganizeResult && (
|
||||
<Text style={styles.reorganizeResult}>{reorganizeResult}</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<View style={styles.divider} />
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<View style={styles.divider} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<SettingItem label={t('settings.email_behavior.permanently_delete_junk.label', "Permanently Delete Junk")} description={t('settings.email_behavior.permanently_delete_junk.description', "Permanently delete emails from the Junk/Spam folder instead of moving them to Trash")}>
|
||||
<ToggleSwitch checked={permanentlyDeleteJunk} onChange={(v) => update('permanentlyDeleteJunk', v)} />
|
||||
|
||||
@@ -49,4 +49,18 @@ describe('organisation policy on the settings pages', () => {
|
||||
expect(page).toContain("const exportAllowed = usePolicyFeature('settingsExportEnabled');");
|
||||
expect(page).toMatch(/\{exportAllowed && \(\s*<SettingItem\s+label=\{t\('settings\.backup\.export_import'[\s\S]*?handleExport[\s\S]*?handleImport[\s\S]*?<\/SettingItem>\s*\)\}/);
|
||||
});
|
||||
|
||||
it('takes Archive out of the swipe and quick actions when the organisation turns it off', () => {
|
||||
const layout = settings('LayoutSettings');
|
||||
expect(layout).toContain("const archiveEnabled = usePolicyFeature('archiveActionEnabled');");
|
||||
expect(layout).toContain('withoutArchive(swipeOptions(t), archiveEnabled)');
|
||||
expect(layout).toContain('value={swipeActionShown(swipeRightAction, archiveEnabled)}');
|
||||
expect(layout).toContain('value={swipeActionShown(swipeLeftAction, archiveEnabled)}');
|
||||
expect(layout).toMatch(/withoutArchive\(ALL_QUICK_ACTIONS\.map\([\s\S]*?\), archiveEnabled\)/);
|
||||
expect(layout).toContain('const bottomActions = bottomQuickActionsFor(bottomQuickActionsRaw, archiveEnabled);');
|
||||
|
||||
const reading = settings('ReadingSettings');
|
||||
expect(reading).toContain("const archiveEnabled = usePolicyFeature('archiveActionEnabled');");
|
||||
expect(reading).toMatch(/\{archiveEnabled && \(\s*<View style=\{styles\.group\}>\s*<SettingItem label=\{t\('settings\.email_behavior\.archive_mode\.label'/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { bottomQuickActionsFor, swipeActionShown, withoutArchive } from '../swipe-actions';
|
||||
|
||||
describe('archive action gate', () => {
|
||||
it('shows a stored Archive swipe as none while archiving is off', () => {
|
||||
expect(swipeActionShown('archive', false)).toBe('none');
|
||||
expect(swipeActionShown('archive', true)).toBe('archive');
|
||||
expect(swipeActionShown('delete', false)).toBe('delete');
|
||||
});
|
||||
|
||||
it('drops Archive from the pickers while archiving is off', () => {
|
||||
const options = [{ value: 'none' }, { value: 'archive' }, { value: 'delete' }];
|
||||
expect(withoutArchive(options, false).map((o) => o.value)).toEqual(['none', 'delete']);
|
||||
expect(withoutArchive(options, true).map((o) => o.value)).toEqual(['none', 'archive', 'delete']);
|
||||
});
|
||||
|
||||
it('fills a stored Archive quick action with a reply action, keeping three', () => {
|
||||
expect(bottomQuickActionsFor(['archive', 'delete', 'reply'], false)).toEqual(['delete', 'reply', 'replyAll']);
|
||||
expect(bottomQuickActionsFor(['reply', 'archive', 'replyAll'], false)).toEqual(['reply', 'replyAll', 'forward']);
|
||||
expect(bottomQuickActionsFor(['archive', 'delete', 'reply'], true)).toEqual(['archive', 'delete', 'reply']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { normalizeBottomQuickActions, type QuickAction, type SwipeAction } from '../stores/settings-store';
|
||||
|
||||
// The organisation's archiveActionEnabled gate (webmail
|
||||
// message-list-settings.tsx): with it off, Archive is no swipe action, no
|
||||
// row action and no quick action, and a stored one does nothing.
|
||||
|
||||
/** What a swipe does: a stored Archive swipe does nothing while archiving is off. */
|
||||
export function swipeActionShown(action: SwipeAction, archiveEnabled: boolean): SwipeAction {
|
||||
return !archiveEnabled && action === 'archive' ? 'none' : action;
|
||||
}
|
||||
|
||||
/** The actions a picker offers: without Archive while archiving is off. */
|
||||
export function withoutArchive<T extends { value: string }>(options: readonly T[], archiveEnabled: boolean): T[] {
|
||||
return archiveEnabled ? [...options] : options.filter((option) => option.value !== 'archive');
|
||||
}
|
||||
|
||||
/**
|
||||
* The reader's three quick actions: a stored Archive slot gives way to a
|
||||
* reply action while archiving is off, so the bar keeps three buttons.
|
||||
*/
|
||||
export function bottomQuickActionsFor(value: unknown, archiveEnabled: boolean): QuickAction[] {
|
||||
const actions = normalizeBottomQuickActions(value);
|
||||
return archiveEnabled ? actions : normalizeBottomQuickActions(actions.filter((action) => action !== 'archive'));
|
||||
}
|
||||
Reference in New Issue
Block a user