mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 00:06:23 +00:00
fix: offer every folder as a tree in the search folder picker instead of the first twelve
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { searchScopeFolders } from '../search-scope-folders';
|
||||||
|
import type { Mailbox } from '../../api/types';
|
||||||
|
|
||||||
|
// The search panel's Folder picker was a flat list cut at twelve folders, so
|
||||||
|
// deeper or later folders couldn't be searched on their own (webmail
|
||||||
|
// lib/search-scope-folders.ts lists them all as a tree).
|
||||||
|
|
||||||
|
const box = (id: string, parentId: string | null = null, role: string | null = null) =>
|
||||||
|
({ id, name: id, parentId, role, sortOrder: 0, totalEmails: 0, unreadEmails: 0, isShared: false } as unknown as Mailbox);
|
||||||
|
|
||||||
|
describe('search folder picker', () => {
|
||||||
|
it('lists every folder, each subfolder after its parent with its depth, without the open one', () => {
|
||||||
|
const boxes = [box('inbox', null, 'inbox'), ...Array.from({ length: 15 }, (_, i) => box(`f${String(i).padStart(2, '0')}`)), box('child', 'f03')];
|
||||||
|
const list = searchScopeFolders(boxes, 'inbox');
|
||||||
|
expect(list).toHaveLength(16);
|
||||||
|
const ids = list.map((m) => m.id);
|
||||||
|
expect(ids.indexOf('child')).toBe(ids.indexOf('f03') + 1);
|
||||||
|
expect(list.find((m) => m.id === 'child')?.depth).toBe(1);
|
||||||
|
expect(ids).not.toContain('inbox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is what the panel shows, uncut', () => {
|
||||||
|
const list = fs.readFileSync(path.resolve(__dirname, '../../screens/EmailListScreen.tsx'), 'utf8');
|
||||||
|
expect(list).not.toMatch(/\.slice\(0, 12\)/);
|
||||||
|
expect(list).toMatch(/searchScopeFolders\(scopedMailboxes, currentMailboxId\)\.map/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { Mailbox } from '../api/types';
|
||||||
|
import { buildMailboxTree, flattenVisible, type MailboxNode } from './mailbox-tree';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The folders the search panel's Folder picker offers: every folder (it was
|
||||||
|
* cut at twelve), in sidebar order with each subfolder right after its parent
|
||||||
|
* and its `depth` for indenting (webmail lib/search-scope-folders.ts). The
|
||||||
|
* open folder is left out: "This folder" picks it. A shared account's
|
||||||
|
* grouping row is no folder and is left out too.
|
||||||
|
*/
|
||||||
|
export function searchScopeFolders(mailboxes: Mailbox[], currentMailboxId: string | null): MailboxNode[] {
|
||||||
|
const tree = buildMailboxTree(mailboxes);
|
||||||
|
const all = new Set<string>();
|
||||||
|
const collect = (nodes: MailboxNode[]) => {
|
||||||
|
for (const n of nodes) {
|
||||||
|
all.add(n.id);
|
||||||
|
collect(n.children);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
collect(tree);
|
||||||
|
return flattenVisible(tree, all).filter((n) => !n.isAccountNode && n.id !== currentMailboxId);
|
||||||
|
}
|
||||||
@@ -64,6 +64,7 @@ import { isPermanentDelete, confirmPermanentDelete } from '../lib/delete-confirm
|
|||||||
import { confirmMailRemoval } from '../lib/mail-removal-confirm';
|
import { confirmMailRemoval } from '../lib/mail-removal-confirm';
|
||||||
import { moveSheetCurrentId } from '../lib/move-targets';
|
import { moveSheetCurrentId } from '../lib/move-targets';
|
||||||
import { listViewKey } from '../lib/list-view-key';
|
import { listViewKey } from '../lib/list-view-key';
|
||||||
|
import { searchScopeFolders } from '../lib/search-scope-folders';
|
||||||
import { draftContextFromEmail, isDraftEmail } from '../lib/draft-context';
|
import { draftContextFromEmail, isDraftEmail } from '../lib/draft-context';
|
||||||
import { getFullEmail, emptyMailbox as apiEmptyMailbox } from '../api/email';
|
import { getFullEmail, emptyMailbox as apiEmptyMailbox } from '../api/email';
|
||||||
import type { RootStackParamList } from '../navigation/types';
|
import type { RootStackParamList } from '../navigation/types';
|
||||||
@@ -1854,17 +1855,18 @@ export default function EmailListScreen({
|
|||||||
active={folderScope === 'current'}
|
active={folderScope === 'current'}
|
||||||
onPress={() => setFolderScope('current')}
|
onPress={() => setFolderScope('current')}
|
||||||
/>
|
/>
|
||||||
{scopedMailboxes
|
</View>
|
||||||
.filter((m) => m.id !== currentMailboxId)
|
{/* Every folder, as a tree (it was a flat list cut at 12). */}
|
||||||
.slice(0, 12)
|
<View style={styles.scopeFolderList}>
|
||||||
.map((m) => (
|
{searchScopeFolders(scopedMailboxes, currentMailboxId).map((m) => (
|
||||||
|
<View key={m.id} style={{ marginLeft: m.depth * spacing.lg }}>
|
||||||
<ScopeChip
|
<ScopeChip
|
||||||
key={m.id}
|
|
||||||
label={localizeMailboxName(m.role, m.name, t)}
|
label={localizeMailboxName(m.role, m.name, t)}
|
||||||
active={folderScope === m.id}
|
active={folderScope === m.id}
|
||||||
onPress={() => setFolderScope(m.id)}
|
onPress={() => setFolderScope(m.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
</View>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@@ -2427,6 +2429,7 @@ function makeStyles(c: ThemePalette) {
|
|||||||
gap: spacing.sm,
|
gap: spacing.sm,
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
},
|
},
|
||||||
|
scopeFolderList: { alignItems: 'flex-start', gap: spacing.xs, marginTop: spacing.sm },
|
||||||
triToggle: {
|
triToggle: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
Reference in New Issue
Block a user