feat: improve visualization of tags
Previously tags where very much focused on color coding email and less about adding additional information. They were also visualized in different ways in different locations. This commit gets rid of all "Color-coding" references, aligns visualization of the tags across the whole project and tries to improve user experience of using tags in general. A search box is shown in the tagging control so the user can quickly search for a tag if they have a huge (more than 10) amount of tags.
This commit is contained in:
@@ -4,8 +4,9 @@ import {
|
||||
sortThreadGroups,
|
||||
getThreadParticipants,
|
||||
mergeThreadEmails,
|
||||
getEmailColorTag,
|
||||
getThreadColorTag,
|
||||
getEmailTagId,
|
||||
getThreadTagId,
|
||||
getThreadTagIds,
|
||||
} from '../thread-utils';
|
||||
import type { Email, ThreadGroup } from '../jmap/types';
|
||||
|
||||
@@ -245,47 +246,47 @@ describe('mergeThreadEmails', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEmailColorTag', () => {
|
||||
describe('getEmailTagId', () => {
|
||||
it('returns label from $label: keyword', () => {
|
||||
expect(getEmailColorTag({ '$label:red': true, $seen: true })).toBe('red');
|
||||
expect(getEmailTagId({ '$label:red': true, $seen: true })).toBe('red');
|
||||
});
|
||||
|
||||
it('returns label from legacy $color: keyword', () => {
|
||||
expect(getEmailColorTag({ '$color:red': true, $seen: true })).toBe('red');
|
||||
expect(getEmailTagId({ '$color:red': true, $seen: true })).toBe('red');
|
||||
});
|
||||
|
||||
it('returns null when no color keyword', () => {
|
||||
expect(getEmailColorTag({ $seen: true, $flagged: true })).toBeNull();
|
||||
expect(getEmailTagId({ $seen: true, $flagged: true })).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for undefined keywords', () => {
|
||||
expect(getEmailColorTag(undefined)).toBeNull();
|
||||
expect(getEmailTagId(undefined)).toBeNull();
|
||||
});
|
||||
|
||||
it('ignores keywords set to false', () => {
|
||||
expect(getEmailColorTag({ '$label:red': false } as unknown as Record<string, boolean>)).toBeNull();
|
||||
expect(getEmailTagId({ '$label:red': false } as unknown as Record<string, boolean>)).toBeNull();
|
||||
});
|
||||
|
||||
it('prefers $label: over $color: when both exist', () => {
|
||||
expect(getEmailColorTag({ '$label:blue': true, '$color:red': true })).toBe('blue');
|
||||
expect(getEmailTagId({ '$label:blue': true, '$color:red': true })).toBe('blue');
|
||||
});
|
||||
|
||||
it('handles custom keyword ids', () => {
|
||||
expect(getEmailColorTag({ '$label:my-custom-tag': true })).toBe('my-custom-tag');
|
||||
expect(getEmailTagId({ '$label:my-custom-tag': true })).toBe('my-custom-tag');
|
||||
});
|
||||
|
||||
it('returns null for empty keywords object', () => {
|
||||
expect(getEmailColorTag({})).toBeNull();
|
||||
expect(getEmailTagId({})).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getThreadColorTag', () => {
|
||||
describe('getThreadTagId', () => {
|
||||
it('returns first color found across thread emails', () => {
|
||||
const emails = [
|
||||
makeEmail({ id: 'e1', keywords: { $seen: true } }),
|
||||
makeEmail({ id: 'e2', keywords: { '$label:blue': true } }),
|
||||
];
|
||||
expect(getThreadColorTag(emails)).toBe('blue');
|
||||
expect(getThreadTagId(emails)).toBe('blue');
|
||||
});
|
||||
|
||||
it('returns null when no emails have color tags', () => {
|
||||
@@ -293,7 +294,7 @@ describe('getThreadColorTag', () => {
|
||||
makeEmail({ id: 'e1', keywords: { $seen: true } }),
|
||||
makeEmail({ id: 'e2', keywords: { $flagged: true } }),
|
||||
];
|
||||
expect(getThreadColorTag(emails)).toBeNull();
|
||||
expect(getThreadTagId(emails)).toBeNull();
|
||||
});
|
||||
|
||||
it('returns first tag from earliest tagged email', () => {
|
||||
@@ -301,7 +302,7 @@ describe('getThreadColorTag', () => {
|
||||
makeEmail({ id: 'e1', keywords: { '$label:red': true } }),
|
||||
makeEmail({ id: 'e2', keywords: { '$label:blue': true } }),
|
||||
];
|
||||
expect(getThreadColorTag(emails)).toBe('red');
|
||||
expect(getThreadTagId(emails)).toBe('red');
|
||||
});
|
||||
|
||||
it('returns legacy tag from thread emails', () => {
|
||||
@@ -309,10 +310,41 @@ describe('getThreadColorTag', () => {
|
||||
makeEmail({ id: 'e1', keywords: { $seen: true } }),
|
||||
makeEmail({ id: 'e2', keywords: { '$color:green': true } }),
|
||||
];
|
||||
expect(getThreadColorTag(emails)).toBe('green');
|
||||
expect(getThreadTagId(emails)).toBe('green');
|
||||
});
|
||||
|
||||
it('returns null for empty email array', () => {
|
||||
expect(getThreadColorTag([])).toBeNull();
|
||||
expect(getThreadTagId([])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getThreadTagIds', () => {
|
||||
it('gathers the tags of every message in the thread', () => {
|
||||
const emails = [
|
||||
makeEmail({ id: 'e1', keywords: { '$label:red': true } }),
|
||||
makeEmail({ id: 'e2', keywords: { '$label:blue': true, '$label:green': true } }),
|
||||
];
|
||||
expect(getThreadTagIds(emails).sort()).toEqual(['blue', 'green', 'red']);
|
||||
});
|
||||
|
||||
it('reports a tag shared by several messages once', () => {
|
||||
const emails = [
|
||||
makeEmail({ id: 'e1', keywords: { '$label:red': true } }),
|
||||
makeEmail({ id: 'e2', keywords: { '$label:red': true } }),
|
||||
];
|
||||
expect(getThreadTagIds(emails)).toEqual(['red']);
|
||||
});
|
||||
|
||||
it('reads the legacy prefix alongside the current one', () => {
|
||||
const emails = [
|
||||
makeEmail({ id: 'e1', keywords: { '$color:green': true } }),
|
||||
makeEmail({ id: 'e2', keywords: { '$label:red': true } }),
|
||||
];
|
||||
expect(getThreadTagIds(emails).sort()).toEqual(['green', 'red']);
|
||||
});
|
||||
|
||||
it('is empty for an untagged or empty thread', () => {
|
||||
expect(getThreadTagIds([makeEmail({ id: 'e1', keywords: { $seen: true } })])).toEqual([]);
|
||||
expect(getThreadTagIds([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user