@@ -55,6 +55,9 @@ const collapsedAccounts = ref(new Set(savedReaderLayout.collapsedAccounts));
5555const collapsedMailboxGroups = ref (new Set (savedReaderLayout .collapsedMailboxGroups ));
5656const activeResize = ref <" nav" | " list" | null >(null );
5757const syncJobsOpen = ref (false );
58+ const customSyncDialogOpen = ref (false );
59+ const customSyncAccountId = ref (" " );
60+ const customSyncDays = ref (90 );
5861const documentVisible = ref (
5962 typeof document === " undefined" ? true : document .visibilityState !== " hidden" ,
6063);
@@ -124,6 +127,15 @@ const activeSyncJobs = computed(() =>
124127 syncJobs .value .filter ((job ) => job .state === " pending" || job .state === " running" ),
125128);
126129const syncingAccountIds = computed (() => new Set (activeSyncJobs .value .map ((job ) => job .accountId )));
130+ const customSyncAccount = computed (() =>
131+ mailAccounts .value .find ((account ) => account .id === customSyncAccountId .value ),
132+ );
133+ const customSyncRangeOptions = [
134+ { label: " Last 30 days" , value: 30 },
135+ { label: " Last 90 days" , value: 90 },
136+ { label: " Last 365 days" , value: 365 },
137+ { label: " Last 10 years" , value: 3650 },
138+ ];
127139
128140const messageListQuery = useQuery ({
129141 queryKey: computed (() => [" message-list" , readerRoute .value ]),
@@ -202,7 +214,7 @@ const logoutMutation = useMutation({
202214});
203215
204216const syncJobMutation = useMutation ({
205- mutationFn : (accountId : string ) => scheduleSyncJob ({ accountId } ),
217+ mutationFn : (request : { accountId: string ; days ? : number } ) => scheduleSyncJob (request ),
206218 onSuccess : async () => {
207219 await queryClient .invalidateQueries ({ queryKey: [" sync-jobs" ] });
208220 },
@@ -375,6 +387,32 @@ function syncJobsPollingInterval(query: { state: { data: SyncJobsResponse | unde
375387 return hasActiveJob ? 1_000 : 15_000 ;
376388}
377389
390+ function accountContextMenuItems(account : MailAccountMailboxTree ) {
391+ return [
392+ {
393+ label: " Custom sync..." ,
394+ icon: " i-lucide-calendar-clock" ,
395+ disabled: syncingAccountIds .value .has (account .id ) || syncJobMutation .isPending .value ,
396+ onSelect : () => openCustomSyncDialog (account ),
397+ },
398+ ];
399+ }
400+
401+ function openCustomSyncDialog(account : MailAccountMailboxTree ) {
402+ customSyncAccountId .value = account .id ;
403+ customSyncDays .value = 90 ;
404+ customSyncDialogOpen .value = true ;
405+ }
406+
407+ function submitCustomSync() {
408+ if (! customSyncAccountId .value ) {
409+ return ;
410+ }
411+
412+ syncJobMutation .mutate ({ accountId: customSyncAccountId .value , days: customSyncDays .value });
413+ customSyncDialogOpen .value = false ;
414+ }
415+
378416function syncJobSummary(job : SyncJobRecord ): string {
379417 if (job .state === " failed" ) {
380418 return job .error ?? " Sync job failed" ;
@@ -614,12 +652,14 @@ function participantsLabel(participants: Array<{ address: string; displayName?:
614652}
615653
616654function formatDate(value : string ): string {
617- return new Intl .DateTimeFormat (undefined , {
618- month: " short" ,
619- day: " numeric" ,
620- hour: " numeric" ,
621- minute: " 2-digit" ,
622- }).format (new Date (value ));
655+ const date = new Date (value );
656+ const year = date .getFullYear ();
657+ const month = String (date .getMonth () + 1 ).padStart (2 , " 0" );
658+ const day = String (date .getDate ()).padStart (2 , " 0" );
659+ const hour = String (date .getHours ()).padStart (2 , " 0" );
660+ const minute = String (date .getMinutes ()).padStart (2 , " 0" );
661+
662+ return ` ${year }-${month }-${day } ${hour }:${minute } ` ;
623663}
624664
625665function mailboxLabel(account : MailAccountMailboxTree , mailboxId : string ): string {
@@ -748,6 +788,42 @@ async function selectAccountDefault(account: MailAccountMailboxTree) {
748788 </div >
749789 </header >
750790
791+ <UModal
792+ v-model :open =" customSyncDialogOpen "
793+ title="Custom sync"
794+ :description =" customSyncAccount ?.emailAddress ?? customSyncAccountId "
795+ >
796+ <template #body >
797+ <form class =" space-y-4" @submit.prevent =" submitCustomSync" >
798+ <div class =" space-y-2" >
799+ <label class =" block text-sm font-medium text-slate-700" for =" custom-sync-range" >
800+ Message range
801+ </label >
802+ <USelect
803+ id="custom-sync-range"
804+ v-model =" customSyncDays "
805+ :items =" customSyncRangeOptions "
806+ value-key="value"
807+ label-key="label"
808+ class="w-full"
809+ />
810+ </div >
811+ <div class =" flex justify-end gap-2" >
812+ <UButton color="neutral" variant="ghost" @click =" customSyncDialogOpen = false " >
813+ Cancel
814+ </UButton >
815+ <button
816+ class =" h-8 rounded-md bg-slate-900 px-3 text-sm font-medium text-white hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-60"
817+ type =" submit"
818+ :disabled =" !customSyncAccountId || syncJobMutation.isPending.value"
819+ >
820+ {{ syncJobMutation.isPending.value ? "Starting..." : "Start sync" }}
821+ </button >
822+ </div >
823+ </form >
824+ </template >
825+ </UModal >
826+
751827 <div v-if =" mailAccounts.length === 0" class =" grid flex-1 place-items-center px-6 text-center" >
752828 <div >
753829 <h2 class =" text-xl font-semibold" >No mail accounts synced yet</h2 >
@@ -766,51 +842,55 @@ async function selectAccountDefault(account: MailAccountMailboxTree) {
766842 <div class =" flex h-full flex-col" >
767843 <div class =" min-h-0 flex-1 overflow-y-auto p-2" >
768844 <div v-for =" account in mailAccounts" :key =" account.id" class =" mb-3" >
769- <div class =" flex items-start justify-between gap-1" >
770- <button
771- class =" mt-0.5 grid h-5 w-5 shrink-0 place-items-center rounded text-slate-500 hover:bg-stone-200"
772- type =" button"
773- :aria-label ="
774- accountCollapsed(account.id) ? 'Expand account' : 'Collapse account'
775- "
776- @click =" toggleAccount(account.id)"
777- >
778- <span class =" text-[10px]" >{{ accountCollapsed(account.id) ? ">" : "v" }}</span >
779- </button >
780- <button
781- class =" min-w-0 flex-1 text-left"
782- type =" button"
783- @click =" selectAccountDefault(account)"
784- >
785- <span class =" block min-w-0 truncate text-xs font-semibold" >
786- {{ account.id }}
787- </span >
788- <span class =" block truncate text-[11px] text-slate-500" >{{
789- account.emailAddress
790- }}</span >
791- </button >
792- <div class =" flex shrink-0 items-center gap-1" >
793- <UBadge
794- v-if =" account .unreadCount > 0 "
795- color="neutral"
796- size="sm"
797- variant="subtle"
798- >{{ account.unreadCount }}</UBadge
799- >
800- <UButton
801- color="neutral"
802- icon="i-lucide-refresh-cw"
803- :loading =" syncingAccountIds .has (account .id )"
804- :disabled ="
805- syncingAccountIds .has (account .id ) || syncJobMutation .isPending .value
845+ <UContextMenu :items =" accountContextMenuItems (account )" >
846+ <div class =" flex items-start justify-between gap-1" >
847+ <button
848+ class =" mt-0.5 grid h-5 w-5 shrink-0 place-items-center rounded text-slate-500 hover:bg-stone-200"
849+ type =" button"
850+ :aria-label ="
851+ accountCollapsed(account.id) ? 'Expand account' : 'Collapse account'
806852 "
807- square
808- variant="ghost"
809- aria-label="Refresh account"
810- @click =" syncJobMutation .mutate (account .id )"
811- />
853+ @click =" toggleAccount(account.id)"
854+ >
855+ <span class =" text-[10px]" >{{
856+ accountCollapsed(account.id) ? ">" : "v"
857+ }}</span >
858+ </button >
859+ <button
860+ class =" min-w-0 flex-1 text-left"
861+ type =" button"
862+ @click =" selectAccountDefault(account)"
863+ >
864+ <span class =" block min-w-0 truncate text-xs font-semibold" >
865+ {{ account.id }}
866+ </span >
867+ <span class =" block truncate text-[11px] text-slate-500" >{{
868+ account.emailAddress
869+ }}</span >
870+ </button >
871+ <div class =" flex shrink-0 items-center gap-1" >
872+ <UBadge
873+ v-if =" account .unreadCount > 0 "
874+ color="neutral"
875+ size="sm"
876+ variant="subtle"
877+ >{{ account.unreadCount }}</UBadge
878+ >
879+ <UButton
880+ color="neutral"
881+ icon="i-lucide-refresh-cw"
882+ :loading =" syncingAccountIds .has (account .id )"
883+ :disabled ="
884+ syncingAccountIds .has (account .id ) || syncJobMutation .isPending .value
885+ "
886+ square
887+ variant="ghost"
888+ aria-label="Refresh account"
889+ @click =" syncJobMutation .mutate ({ accountId: account .id })"
890+ />
891+ </div >
812892 </div >
813- </div >
893+ </UContextMenu >
814894 <div v-if =" !accountCollapsed(account.id)" class =" mt-1 space-y-0.5" >
815895 <div
816896 v-for =" row in visibleMailboxRows(account)"
0 commit comments