Feature: pin emails to the top of the folder list
Outlook-Web-style pinning: a context-menu Pin/Unpin action stores a $pinned keyword on the message (plain IMAP-compatible flag, survives other clients), and pinned mails stay at the top of the folder list regardless of age, marked with a pin icon. Ordering is done server-side via the hasKeyword sort comparator (RFC 8621), applied consistently to the folder fetch, pagination and the push-refresh so page windows stay stable. The client-side safety sort in getEmails mirrors it, and sortThreadGroups keeps threads containing a pinned mail on top so the client-side thread grouping does not undo the order. The new-mail notification in refreshCurrentMailbox now checks the first non-pinned entry: with pinned mails on top, the newest mail is no longer at index 0 and arrivals would never have notified. The toggle reuses the color-tag pathway (routed keyword write for unified views, in-place local patch), then refetches the first page so the mail floats or sinks immediately. Search and unified views keep their existing order. Pin/Unpin strings are added to all 21 locales.
This commit is contained in:
@@ -945,7 +945,7 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
|
||||
// When filtering by tag, omit the mailbox constraint so emails across
|
||||
// all folders that carry the tag are returned.
|
||||
const result = await effectiveClient.getEmails(selectedKeyword ? undefined : jmapMailboxId, accountId, emailsPerPage, 0, keywordFilter);
|
||||
const result = await effectiveClient.getEmails(selectedKeyword ? undefined : jmapMailboxId, accountId, emailsPerPage, 0, keywordFilter, true);
|
||||
set({
|
||||
emails: annotateScheduledEmails(result.emails, get().scheduledSubmissionByEmailId),
|
||||
hasMoreEmails: result.hasMore,
|
||||
@@ -1108,7 +1108,7 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
const jmapMailboxId = mailbox?.originalId || selectedMailbox;
|
||||
|
||||
// When filtering by tag, omit the mailbox constraint (same rationale as fetchEmails).
|
||||
result = await effectiveClient.getEmails(selectedKeyword ? undefined : jmapMailboxId, accountId, emailsPerPage, position, selectedKeyword ? `$label:${selectedKeyword}` : undefined);
|
||||
result = await effectiveClient.getEmails(selectedKeyword ? undefined : jmapMailboxId, accountId, emailsPerPage, position, selectedKeyword ? `$label:${selectedKeyword}` : undefined, true);
|
||||
}
|
||||
|
||||
if (selectedMailbox === ALL_MAIL_MAILBOX_ID) {
|
||||
@@ -2643,7 +2643,7 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
const filter = buildJMAPFilter(searchQuery, searchFilters, jmapMailboxId);
|
||||
result = await effectiveClient.advancedSearchEmails(filter, accountId, emailsPerPage, 0);
|
||||
} else {
|
||||
result = await effectiveClient.getEmails(jmapMailboxId, accountId, emailsPerPage, 0);
|
||||
result = await effectiveClient.getEmails(jmapMailboxId, accountId, emailsPerPage, 0, undefined, true);
|
||||
}
|
||||
|
||||
const currentEmails = get().emails;
|
||||
@@ -2653,7 +2653,9 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
// Without these guards the toast/sound also fires when sending,
|
||||
// saving drafts, or moving/deleting the top message in any mailbox,
|
||||
// because all of those change the first-email id of the current view.
|
||||
const newFirst = result.emails[0];
|
||||
// Pinned mails sit above the date order, so the newest mail is the
|
||||
// first NON-pinned entry (a just-arrived mail cannot be pinned yet).
|
||||
const newFirst = result.emails.find(e => !e.keywords?.['$pinned']) ?? result.emails[0];
|
||||
if (
|
||||
newFirst &&
|
||||
mailbox?.role === 'inbox' &&
|
||||
|
||||
Reference in New Issue
Block a user