mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: give user folders a folder icon even when their name looks like a system folder #80
This commit is contained in:
@@ -46,6 +46,7 @@ import {
|
|||||||
type NotificationMutes,
|
type NotificationMutes,
|
||||||
} from '../lib/notification-mutes';
|
} from '../lib/notification-mutes';
|
||||||
import { serverSupportsEmailPush } from '../lib/push-notifications';
|
import { serverSupportsEmailPush } from '../lib/push-notifications';
|
||||||
|
import { mailboxIconKind, type MailboxIconKind } from '../lib/mailbox-icon';
|
||||||
|
|
||||||
const CHEVRON_SLOT = 20;
|
const CHEVRON_SLOT = 20;
|
||||||
const INDENT_STEP = 12;
|
const INDENT_STEP = 12;
|
||||||
@@ -64,41 +65,28 @@ const UNIFIED_ROLES: UnifiedRole[] = ['sent', 'drafts', 'archive', 'junk', 'tras
|
|||||||
// in for them (#495).
|
// in for them (#495).
|
||||||
const HIDDEN_WITH_VIRTUAL_ROW = new Set(['scheduled']);
|
const HIDDEN_WITH_VIRTUAL_ROW = new Set(['scheduled']);
|
||||||
|
|
||||||
// Distinct icons for the special-use roles (#288), then a name-based guess
|
// Distinct icons for the special-use roles (#288), else a plain folder: by
|
||||||
// for servers that don't set roles, then a plain folder.
|
// role only, never by name (lib/mailbox-icon.ts, #80).
|
||||||
function iconFor(
|
const ICONS: Record<MailboxIconKind, AppIcon> = {
|
||||||
role: string | null | undefined,
|
inbox: Inbox,
|
||||||
name: string | undefined,
|
sent: Send,
|
||||||
hasChildren: boolean,
|
drafts: FileIcon,
|
||||||
isExpanded: boolean,
|
trash: Trash2,
|
||||||
): AppIcon {
|
junk: Ban,
|
||||||
switch (role) {
|
archive: Archive,
|
||||||
case 'inbox': return Inbox;
|
important: Flag,
|
||||||
case 'sent': return Send;
|
flagged: Star,
|
||||||
case 'drafts': return FileIcon;
|
all: Mails,
|
||||||
case 'trash': return Trash2;
|
memos: StickyNote,
|
||||||
case 'junk':
|
scheduled: Clock,
|
||||||
case 'spam': return Ban;
|
snoozed: AlarmClock,
|
||||||
case 'archive': return Archive;
|
shared: Users,
|
||||||
case 'important': return Flag;
|
folder: Folder,
|
||||||
case 'flagged': return Star;
|
'folder-open': FolderOpen,
|
||||||
case 'all': return Mails;
|
};
|
||||||
case 'memos': return StickyNote;
|
|
||||||
case 'scheduled': return Clock;
|
function iconFor(role: string | null | undefined, hasChildren: boolean, isExpanded: boolean): AppIcon {
|
||||||
case 'snoozed': return AlarmClock;
|
return ICONS[mailboxIconKind(role, hasChildren, isExpanded)];
|
||||||
case 'shared': return Users;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
const lower = (name ?? '').toLowerCase();
|
|
||||||
if (lower.includes('inbox')) return Inbox;
|
|
||||||
if (lower.includes('sent')) return Send;
|
|
||||||
if (lower.includes('draft')) return FileIcon;
|
|
||||||
if (lower.includes('trash') || lower.includes('deleted')) return Trash2;
|
|
||||||
if (lower.includes('junk') || lower.includes('spam')) return Ban;
|
|
||||||
if (lower.includes('archive')) return Archive;
|
|
||||||
if (lower.includes('star') || lower.includes('flag')) return Star;
|
|
||||||
if (hasChildren) return isExpanded ? FolderOpen : Folder;
|
|
||||||
return Folder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROLE_COLOR_FIXED: Record<string, string> = {
|
const ROLE_COLOR_FIXED: Record<string, string> = {
|
||||||
@@ -745,7 +733,7 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side
|
|||||||
};
|
};
|
||||||
}, [mailboxes, accountSnapshots, activeAccountId, unifiedCrossAccount, includeGroupInUnified]);
|
}, [mailboxes, accountSnapshots, activeAccountId, unifiedCrossAccount, includeGroupInUnified]);
|
||||||
|
|
||||||
const unifiedIcon = (role: UnifiedRole): AppIcon => iconFor(role, undefined, false, false);
|
const unifiedIcon = (role: UnifiedRole): AppIcon => iconFor(role, false, false);
|
||||||
|
|
||||||
const slideX = React.useRef(new Animated.Value(-Dimensions.get('window').width)).current;
|
const slideX = React.useRef(new Animated.Value(-Dimensions.get('window').width)).current;
|
||||||
const overlayOpacity = React.useRef(new Animated.Value(0)).current;
|
const overlayOpacity = React.useRef(new Animated.Value(0)).current;
|
||||||
@@ -1139,7 +1127,7 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side
|
|||||||
// tapping it only opens or closes that account's folders.
|
// tapping it only opens or closes that account's folders.
|
||||||
const Icon = node.isAccountNode
|
const Icon = node.isAccountNode
|
||||||
? Users
|
? Users
|
||||||
: iconFor(node.role, node.name, hasChildren, isExpanded);
|
: iconFor(node.role, hasChildren, isExpanded);
|
||||||
const isSelected = !node.isAccountNode && !tagViewActive && node.id === currentMailboxId;
|
const isSelected = !node.isAccountNode && !tagViewActive && node.id === currentMailboxId;
|
||||||
return (
|
return (
|
||||||
<SidebarRow
|
<SidebarRow
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { mailboxIconKind } from '../mailbox-icon';
|
||||||
|
|
||||||
|
describe('mailboxIconKind', () => {
|
||||||
|
it('gives each special-use role its icon', () => {
|
||||||
|
expect(mailboxIconKind('sent', false, false)).toBe('sent');
|
||||||
|
expect(mailboxIconKind('junk', false, false)).toBe('junk');
|
||||||
|
expect(mailboxIconKind('spam', false, false)).toBe('junk');
|
||||||
|
expect(mailboxIconKind('trash', true, true)).toBe('trash');
|
||||||
|
});
|
||||||
|
|
||||||
|
// native #80: a sieve-sorted folder "web/sentry" got the Sent icon.
|
||||||
|
it('gives a user folder a folder icon whatever its name says', () => {
|
||||||
|
for (const name of ['sentry', 'Sent Items', 'Drafts old', 'memory', 'spam-reports', 'archive 2019']) {
|
||||||
|
expect(mailboxIconKind(null, false, false), name).toBe('folder');
|
||||||
|
}
|
||||||
|
expect(mailboxIconKind(undefined, true, true)).toBe('folder-open');
|
||||||
|
expect(mailboxIconKind(null, true, false)).toBe('folder');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is what the sidebar uses, with no guess from the folder name', () => {
|
||||||
|
const source = readFileSync(path.join(process.cwd(), 'src/components/SidebarDrawer.tsx'), 'utf8');
|
||||||
|
expect(source).toContain('mailboxIconKind(');
|
||||||
|
expect(source).not.toMatch(/lower\.includes\('(?:sent|inbox|draft|trash|junk|archive|star)'\)/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Which icon a folder in the sidebar gets: one per special-use role (#288),
|
||||||
|
// else a plain folder. By role only, as the webmail does
|
||||||
|
// (components/layout/sidebar.tsx): a guess from the name gave a user folder
|
||||||
|
// "web/sentry" the Sent icon (native #80, webmail #1235), and a folder that
|
||||||
|
// holds no sent mail must not look like one that does.
|
||||||
|
|
||||||
|
export type MailboxIconKind =
|
||||||
|
| 'inbox' | 'sent' | 'drafts' | 'trash' | 'junk' | 'archive' | 'important' | 'flagged'
|
||||||
|
| 'all' | 'memos' | 'scheduled' | 'snoozed' | 'shared' | 'folder' | 'folder-open';
|
||||||
|
|
||||||
|
const ROLE_ICONS: Record<string, MailboxIconKind> = {
|
||||||
|
inbox: 'inbox',
|
||||||
|
sent: 'sent',
|
||||||
|
drafts: 'drafts',
|
||||||
|
trash: 'trash',
|
||||||
|
junk: 'junk',
|
||||||
|
spam: 'junk',
|
||||||
|
archive: 'archive',
|
||||||
|
important: 'important',
|
||||||
|
flagged: 'flagged',
|
||||||
|
all: 'all',
|
||||||
|
memos: 'memos',
|
||||||
|
scheduled: 'scheduled',
|
||||||
|
snoozed: 'snoozed',
|
||||||
|
shared: 'shared',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function mailboxIconKind(
|
||||||
|
role: string | null | undefined,
|
||||||
|
hasChildren: boolean,
|
||||||
|
isExpanded: boolean,
|
||||||
|
): MailboxIconKind {
|
||||||
|
const byRole = role ? ROLE_ICONS[role] : undefined;
|
||||||
|
if (byRole) return byRole;
|
||||||
|
return hasChildren && isExpanded ? 'folder-open' : 'folder';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user