mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 16:05:39 +00:00
fix: hide every archive action when the organisation turns archiving off
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
type EmailFilenameOptions,
|
||||
} from '../../lib/download-filename';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
import { usePolicyFeature } from '../../stores/policy-store';
|
||||
|
||||
const SAMPLE = buildSampleEmail();
|
||||
const SAMPLE_ATTACHMENT = { name: 'Rechnung 2026.pdf', type: 'application/pdf' };
|
||||
@@ -34,7 +35,11 @@ export function DownloadsSettings() {
|
||||
const spaceReplacement = useSettingsStore((s) => s.exportSpaceReplacement);
|
||||
const lowercase = useSettingsStore((s) => s.exportLowercase);
|
||||
const stripDiacritics = useSettingsStore((s) => s.exportStripDiacritics);
|
||||
const postExportAction = useSettingsStore((s) => s.postExportAction);
|
||||
// Without the organisation's Archive action there is no "move to archive"
|
||||
// after an export (webmail downloads-settings.tsx).
|
||||
const archiveEnabled = usePolicyFeature('archiveActionEnabled');
|
||||
const storedPostExportAction = useSettingsStore((s) => s.postExportAction);
|
||||
const postExportAction = archiveEnabled || storedPostExportAction !== 'archive' ? storedPostExportAction : 'keep';
|
||||
|
||||
useEffect(() => { if (!hydrated) void hydrate(); }, [hydrated, hydrate]);
|
||||
|
||||
@@ -136,7 +141,7 @@ export function DownloadsSettings() {
|
||||
onChange={(v) => update('postExportAction', v as PostExportAction)}
|
||||
options={[
|
||||
{ value: 'keep', label: t('settings.downloads.after_export.keep', "Keep in mailbox") },
|
||||
{ value: 'archive', label: t('settings.downloads.after_export.archive', "Move to archive") },
|
||||
...(archiveEnabled ? [{ value: 'archive', label: t('settings.downloads.after_export.archive', "Move to archive") }] : []),
|
||||
{ value: 'trash', label: t('settings.downloads.after_export.trash', "Move to trash") },
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -41,6 +41,8 @@ import {
|
||||
type EmailFilters,
|
||||
} from '../stores/email-store';
|
||||
import { useSettingsStore, type SwipeAction, type SwipeMode } from '../stores/settings-store';
|
||||
import { usePolicyFeature } from '../stores/policy-store';
|
||||
import { swipeActionShown } from '../lib/swipe-actions';
|
||||
import { useKeywordsStore, currentTagsLogin, type KeywordDef } from '../stores/keywords-store';
|
||||
import { mergeSharedTags } from '../lib/shared-tags';
|
||||
import { useBackWhileFocused } from '../lib/use-back-while-focused';
|
||||
@@ -538,8 +540,10 @@ export default function EmailListScreen({
|
||||
const retryFailedOps = useOutboxStore((s) => s.retryFailed);
|
||||
const discardFailedOps = useOutboxStore((s) => s.discardFailed);
|
||||
|
||||
const swipeLeftAction = useSettingsStore((s) => s.swipeLeftAction);
|
||||
const swipeRightAction = useSettingsStore((s) => s.swipeRightAction);
|
||||
// The organisation's Archive gate: a stored Archive swipe does nothing while it is off.
|
||||
const archiveEnabled = usePolicyFeature('archiveActionEnabled');
|
||||
const swipeLeftAction = swipeActionShown(useSettingsStore((s) => s.swipeLeftAction), archiveEnabled);
|
||||
const swipeRightAction = swipeActionShown(useSettingsStore((s) => s.swipeRightAction), archiveEnabled);
|
||||
const swipeMode = useSettingsStore((s) => s.swipeMode);
|
||||
const showPreview = useSettingsStore((s) => s.showPreview);
|
||||
const disableThreading = useSettingsStore((s) => s.disableThreading);
|
||||
@@ -1041,7 +1045,7 @@ export default function EmailListScreen({
|
||||
};
|
||||
|
||||
const canArchiveSelection =
|
||||
archiveMailboxId != null && currentMailboxId !== archiveMailboxId;
|
||||
archiveEnabled && archiveMailboxId != null && currentMailboxId !== archiveMailboxId;
|
||||
const canSpamSelection =
|
||||
currentRole !== 'sent' && currentRole !== 'drafts' && (inJunk || junkMailboxId != null);
|
||||
|
||||
|
||||
@@ -33,10 +33,11 @@ import { useEmailStore, listRowsOfAccount } from '../stores/email-store';
|
||||
import { toast } from '../stores/toast-store';
|
||||
import {
|
||||
useSettingsStore,
|
||||
normalizeBottomQuickActions,
|
||||
REPLY_QUICK_ACTIONS,
|
||||
type QuickAction,
|
||||
} from '../stores/settings-store';
|
||||
import { usePolicyFeature } from '../stores/policy-store';
|
||||
import { bottomQuickActionsFor } from '../lib/swipe-actions';
|
||||
import { patchKeywordsForEmails } from '../api/email';
|
||||
import {
|
||||
loadDetail, loadDetails, loadThread, patchDetail, peekDetail, peekRow, peekThread, subscribeEmailCache,
|
||||
@@ -356,9 +357,12 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
|
||||
|
||||
const markAsReadDelay = useSettingsStore((s) => s.markAsReadDelay);
|
||||
const bottomQuickActionsRaw = useSettingsStore((s) => s.bottomQuickActions);
|
||||
// The organisation's Archive gate (webmail lib/archive-gate.ts): off, a
|
||||
// stored Archive quick action gives way and no Archive button shows.
|
||||
const archiveEnabled = usePolicyFeature('archiveActionEnabled');
|
||||
const bottomActions = React.useMemo(
|
||||
() => normalizeBottomQuickActions(bottomQuickActionsRaw),
|
||||
[bottomQuickActionsRaw],
|
||||
() => bottomQuickActionsFor(bottomQuickActionsRaw, archiveEnabled),
|
||||
[bottomQuickActionsRaw, archiveEnabled],
|
||||
);
|
||||
// Any reply-family action the user pulled out of the bottom bar is surfaced
|
||||
// in the top toolbar so it stays reachable.
|
||||
@@ -488,7 +492,7 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
|
||||
[scopedMailboxes, email, currentMailboxId],
|
||||
);
|
||||
const isInTrash = !!(trashMailbox && sourceMailbox?.id === trashMailbox.id);
|
||||
const canArchive = !!archiveMailbox && sourceMailbox?.id !== archiveMailbox.id;
|
||||
const canArchive = archiveEnabled && !!archiveMailbox && sourceMailbox?.id !== archiveMailbox.id;
|
||||
// Not spam files back into the Inbox, so it needs one.
|
||||
const canToggleSpam = isInJunk ? !!inboxMailbox : !!junkMailbox;
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import { SearchSnippetText } from '../components/email/SearchSnippetText';
|
||||
import { useAccountStore } from '../stores/account-store';
|
||||
import { useAuthStore } from '../stores/auth-store';
|
||||
import { useSettingsStore, type SwipeAction } from '../stores/settings-store';
|
||||
import { usePolicyFeature } from '../stores/policy-store';
|
||||
import { swipeActionShown } from '../lib/swipe-actions';
|
||||
import { useLocaleStore } from '../stores/locale-store';
|
||||
import { formatListDate } from '../lib/date-format';
|
||||
import { singleLine } from '../lib/single-line';
|
||||
@@ -79,8 +81,10 @@ export default function UnifiedInboxScreen({ navigation, route }: Props) {
|
||||
const crossAccount = useSettingsStore((s) => s.unifiedCrossAccount);
|
||||
const deleteAction = useSettingsStore((s) => s.deleteAction);
|
||||
const permanentlyDeleteJunk = useSettingsStore((s) => s.permanentlyDeleteJunk);
|
||||
const swipeLeftAction = useSettingsStore((s) => s.swipeLeftAction);
|
||||
const swipeRightAction = useSettingsStore((s) => s.swipeRightAction);
|
||||
// The organisation's Archive gate: a stored Archive swipe does nothing while it is off.
|
||||
const archiveEnabled = usePolicyFeature('archiveActionEnabled');
|
||||
const swipeLeftAction = swipeActionShown(useSettingsStore((s) => s.swipeLeftAction), archiveEnabled);
|
||||
const swipeRightAction = swipeActionShown(useSettingsStore((s) => s.swipeRightAction), archiveEnabled);
|
||||
const swipeMode = useSettingsStore((s) => s.swipeMode);
|
||||
const showAvatarsInJunk = useSettingsStore((s) => s.showAvatarsInJunk);
|
||||
const locale = useLocaleStore((s) => s.locale);
|
||||
@@ -303,7 +307,7 @@ export default function UnifiedInboxScreen({ navigation, route }: Props) {
|
||||
|
||||
const handleSwipe = (email: UnifiedEmail, action: SwipeAction) => {
|
||||
switch (action) {
|
||||
case 'archive': if (role !== 'archive') void archive([email]); break;
|
||||
case 'archive': if (archiveEnabled && role !== 'archive') void archive([email]); break;
|
||||
case 'delete': void remove([email]); break;
|
||||
case 'spam': if (role !== 'sent' && role !== 'drafts') spam([email]); break;
|
||||
case 'read': setRead([email], !email.keywords?.$seen); break;
|
||||
@@ -482,7 +486,7 @@ export default function UnifiedInboxScreen({ navigation, route }: Props) {
|
||||
{role === 'junk' ? <ShieldCheck size={20} color={c.text} /> : <ShieldAlert size={20} color={c.text} />}
|
||||
</Pressable>
|
||||
)}
|
||||
{role !== 'archive' && (
|
||||
{archiveEnabled && role !== 'archive' && (
|
||||
<Pressable
|
||||
onPress={() => { void archive(selectedEmails).then((done) => { if (done) clearSelection(); }); }}
|
||||
style={styles.headerBtn}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
// The organisation's archiveActionEnabled gate (webmail lib/archive-gate.ts,
|
||||
// email-list.tsx, email-viewer.tsx, downloads-settings.tsx): off, no swipe,
|
||||
// selection bar, reader toolbar, quick action, more-menu or after-export
|
||||
// Archive. No React Native renderer in the test env, so this checks the
|
||||
// wiring; the helpers have unit tests in lib/__tests__/swipe-actions.test.ts.
|
||||
|
||||
const read = (rel: string) => fs.readFileSync(path.resolve(__dirname, '..', '..', rel), 'utf8');
|
||||
|
||||
describe('archive gate wiring', () => {
|
||||
it('drops a stored Archive swipe and the selection Archive in the folder list', () => {
|
||||
const src = read('screens/EmailListScreen.tsx');
|
||||
expect(src).toMatch(/const archiveEnabled = usePolicyFeature\('archiveActionEnabled'\);/);
|
||||
expect(src).toMatch(/const swipeLeftAction = swipeActionShown\(useSettingsStore\(\(s\) => s\.swipeLeftAction\), archiveEnabled\);/);
|
||||
expect(src).toMatch(/const swipeRightAction = swipeActionShown\(useSettingsStore\(\(s\) => s\.swipeRightAction\), archiveEnabled\);/);
|
||||
expect(src).toMatch(/const canArchiveSelection =\s+archiveEnabled && /);
|
||||
});
|
||||
|
||||
it('drops them in the unified inbox', () => {
|
||||
const src = read('screens/UnifiedInboxScreen.tsx');
|
||||
expect(src).toMatch(/const archiveEnabled = usePolicyFeature\('archiveActionEnabled'\);/);
|
||||
expect(src).toMatch(/const swipeLeftAction = swipeActionShown\(useSettingsStore\(\(s\) => s\.swipeLeftAction\), archiveEnabled\);/);
|
||||
expect(src).toMatch(/const swipeRightAction = swipeActionShown\(useSettingsStore\(\(s\) => s\.swipeRightAction\), archiveEnabled\);/);
|
||||
expect(src).toMatch(/\{archiveEnabled && role !== 'archive' && \(\s+<Pressable\s+onPress=\{\(\) => \{ void archive\(selectedEmails\)/);
|
||||
expect(src).toMatch(/case 'archive': if \(archiveEnabled && role !== 'archive'\)/);
|
||||
});
|
||||
|
||||
it('drops the reader Archive button, quick action and menu entry', () => {
|
||||
const src = read('screens/EmailThreadScreen.tsx');
|
||||
expect(src).toMatch(/bottomQuickActionsFor\(bottomQuickActionsRaw, archiveEnabled\)/);
|
||||
expect(src).not.toMatch(/normalizeBottomQuickActions\(bottomQuickActionsRaw\)/);
|
||||
// canArchive feeds the toolbar button, the quick action, the more menu
|
||||
// and the after-export move.
|
||||
expect(src).toMatch(/const canArchive = archiveEnabled && !!archiveMailbox/);
|
||||
});
|
||||
|
||||
it('offers no move to archive after an export', () => {
|
||||
const src = read('components/settings/DownloadsSettings.tsx');
|
||||
expect(src).toMatch(/archiveEnabled \|\| storedPostExportAction !== 'archive' \? storedPostExportAction : 'keep'/);
|
||||
expect(src).toMatch(/\.\.\.\(archiveEnabled \? \[\{ value: 'archive'/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user