feat(folders): drag-and-drop reorder for all folders

This commit is contained in:
Shuki Vaknin
2026-07-21 20:56:38 +02:00
committed by Linus Rath
parent 2f791318df
commit 5716d91115
7 changed files with 196 additions and 22 deletions
+40
View File
@@ -230,6 +230,7 @@ interface EmailStore {
renameMailbox: (client: IJMAPClient, mailboxId: string, name: string) => Promise<void>;
deleteMailbox: (client: IJMAPClient, mailboxId: string) => Promise<void>;
setMailboxRole: (client: IJMAPClient, mailboxId: string, role: string | null) => Promise<void>;
reorderMailboxes: (client: IJMAPClient, orderedIds: string[]) => Promise<void>;
emptyMailbox: (client: IJMAPClient, mailboxId: string) => Promise<void>;
markMailboxAsRead: (client: IJMAPClient, mailboxId: string) => Promise<number>;
@@ -3227,6 +3228,45 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
}
},
reorderMailboxes: async (client, orderedIds) => {
// Assign a 1-based sortOrder to the given sibling group in its new order.
// sortOrder is the primary sort key (see buildMailboxTree), so this pins
// the folders' arrangement in the sidebar and settings.
const updates = orderedIds.map((id, idx) => ({ id, sortOrder: idx + 1 }));
const applyLocal = (list: Mailbox[]) =>
list.map(mb => {
const u = updates.find(x => x.id === mb.id);
return u ? { ...mb, sortOrder: u.sortOrder } : mb;
});
// Optimistic local update so the reorder is reflected immediately.
const viewingId = get().viewingAccountId;
if (viewingId) {
set((state) => ({
accountMailboxes: {
...state.accountMailboxes,
[viewingId]: applyLocal(state.accountMailboxes[viewingId] ?? []),
},
}));
} else {
set({ mailboxes: applyLocal(get().mailboxes) });
}
try {
const effectiveClient = resolveActionClient(client);
for (const u of updates) {
await effectiveClient.updateMailbox(u.id, { sortOrder: u.sortOrder });
}
} catch (error) {
set({ error: error instanceof Error ? error.message : 'Failed to reorder folders' });
// Re-sync from server so local state doesn't drift from persisted order.
if (viewingId) {
await refreshMailboxesForViewingAccount(client);
} else {
await get().fetchMailboxes(client);
}
throw error;
}
},
emptyMailbox: async (client, mailboxId) => {
try {
set({ isLoading: true, error: null });