fix: use canonical INBOX in Sieve filter paths #313
This commit is contained in:
@@ -78,7 +78,10 @@ export function FilterRuleModal({
|
|||||||
const pathMap = new Map<string, string>();
|
const pathMap = new Map<string, string>();
|
||||||
const buildPaths = (nodes: MailboxNode[], parentPath = "") => {
|
const buildPaths = (nodes: MailboxNode[], parentPath = "") => {
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const fullPath = parentPath ? `${parentPath}/${node.name}` : node.name;
|
// Sieve fileinto expects the IMAP-canonical "INBOX" for the inbox,
|
||||||
|
// not the localized JMAP display name (e.g. "Entrada" in pt-BR).
|
||||||
|
const segment = node.role === "inbox" ? "INBOX" : node.name;
|
||||||
|
const fullPath = parentPath ? `${parentPath}/${segment}` : segment;
|
||||||
pathMap.set(node.id, fullPath);
|
pathMap.set(node.id, fullPath);
|
||||||
if (node.children.length > 0) buildPaths(node.children, fullPath);
|
if (node.children.length > 0) buildPaths(node.children, fullPath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ function buildMailboxPathMap(tree: MailboxNode[]): Map<string, string> {
|
|||||||
const pathMap = new Map<string, string>();
|
const pathMap = new Map<string, string>();
|
||||||
const walk = (nodes: MailboxNode[], parentPath = '') => {
|
const walk = (nodes: MailboxNode[], parentPath = '') => {
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const fullPath = parentPath ? `${parentPath}/${node.name}` : node.name;
|
const segment = node.role === 'inbox' ? 'INBOX' : node.name;
|
||||||
|
const fullPath = parentPath ? `${parentPath}/${segment}` : segment;
|
||||||
pathMap.set(node.id, fullPath);
|
pathMap.set(node.id, fullPath);
|
||||||
if (node.children.length > 0) walk(node.children, fullPath);
|
if (node.children.length > 0) walk(node.children, fullPath);
|
||||||
}
|
}
|
||||||
@@ -47,7 +48,7 @@ describe('mailbox path building for sieve fileinto', () => {
|
|||||||
const mailboxes = [makeMailbox({ id: 'inbox', name: 'Inbox', role: 'inbox' })];
|
const mailboxes = [makeMailbox({ id: 'inbox', name: 'Inbox', role: 'inbox' })];
|
||||||
const tree = buildMailboxTree(mailboxes);
|
const tree = buildMailboxTree(mailboxes);
|
||||||
const paths = buildMailboxPathMap(tree);
|
const paths = buildMailboxPathMap(tree);
|
||||||
expect(paths.get('inbox')).toBe('Inbox');
|
expect(paths.get('inbox')).toBe('INBOX');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should produce correct path for a single-level subfolder', () => {
|
it('should produce correct path for a single-level subfolder', () => {
|
||||||
@@ -57,7 +58,7 @@ describe('mailbox path building for sieve fileinto', () => {
|
|||||||
];
|
];
|
||||||
const tree = buildMailboxTree(mailboxes);
|
const tree = buildMailboxTree(mailboxes);
|
||||||
const paths = buildMailboxPathMap(tree);
|
const paths = buildMailboxPathMap(tree);
|
||||||
expect(paths.get('sub1')).toBe('Inbox/Projects');
|
expect(paths.get('sub1')).toBe('INBOX/Projects');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should produce correct path for deeply nested subfolders', () => {
|
it('should produce correct path for deeply nested subfolders', () => {
|
||||||
@@ -68,7 +69,7 @@ describe('mailbox path building for sieve fileinto', () => {
|
|||||||
];
|
];
|
||||||
const tree = buildMailboxTree(mailboxes);
|
const tree = buildMailboxTree(mailboxes);
|
||||||
const paths = buildMailboxPathMap(tree);
|
const paths = buildMailboxPathMap(tree);
|
||||||
expect(paths.get('sub2')).toBe('Inbox/Test/Test2');
|
expect(paths.get('sub2')).toBe('INBOX/Test/Test2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle multiple root-level folders', () => {
|
it('should handle multiple root-level folders', () => {
|
||||||
@@ -79,7 +80,7 @@ describe('mailbox path building for sieve fileinto', () => {
|
|||||||
];
|
];
|
||||||
const tree = buildMailboxTree(mailboxes);
|
const tree = buildMailboxTree(mailboxes);
|
||||||
const paths = buildMailboxPathMap(tree);
|
const paths = buildMailboxPathMap(tree);
|
||||||
expect(paths.get('inbox')).toBe('Inbox');
|
expect(paths.get('inbox')).toBe('INBOX');
|
||||||
expect(paths.get('archive')).toBe('Archive');
|
expect(paths.get('archive')).toBe('Archive');
|
||||||
expect(paths.get('sub1')).toBe('Archive/Work');
|
expect(paths.get('sub1')).toBe('Archive/Work');
|
||||||
});
|
});
|
||||||
@@ -99,9 +100,25 @@ describe('mailbox path building for sieve fileinto', () => {
|
|||||||
expect(paths.has(node.id)).toBe(true);
|
expect(paths.has(node.id)).toBe(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(paths.get('inbox')).toBe('Inbox');
|
expect(paths.get('inbox')).toBe('INBOX');
|
||||||
expect(paths.get('sub1')).toBe('Inbox/Projects');
|
expect(paths.get('sub1')).toBe('INBOX/Projects');
|
||||||
expect(paths.get('sub2')).toBe('Inbox/Projects/Active');
|
expect(paths.get('sub2')).toBe('INBOX/Projects/Active');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses canonical INBOX even when JMAP returns a localized inbox name', () => {
|
||||||
|
// Stalwart returns localized display names for the inbox based on the
|
||||||
|
// user's locale (e.g. "Entrada" for pt-BR). Sieve fileinto must still
|
||||||
|
// target the IMAP-canonical "INBOX" so the message is filed correctly.
|
||||||
|
const mailboxes = [
|
||||||
|
makeMailbox({ id: 'inbox', name: 'Entrada', role: 'inbox' }),
|
||||||
|
makeMailbox({ id: 'host', name: 'Host', parentId: 'inbox' }),
|
||||||
|
makeMailbox({ id: 'eveo', name: 'EVEO', parentId: 'host' }),
|
||||||
|
];
|
||||||
|
const tree = buildMailboxTree(mailboxes);
|
||||||
|
const paths = buildMailboxPathMap(tree);
|
||||||
|
expect(paths.get('inbox')).toBe('INBOX');
|
||||||
|
expect(paths.get('host')).toBe('INBOX/Host');
|
||||||
|
expect(paths.get('eveo')).toBe('INBOX/Host/EVEO');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should preserve depth info in flattened tree', () => {
|
it('should preserve depth info in flattened tree', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user