|
179 | 179 | import Sun from '@lucide/svelte/icons/sun'; |
180 | 180 | import Moon from '@lucide/svelte/icons/moon'; |
181 | 181 | import WifiOff from '@lucide/svelte/icons/wifi-off'; |
| 182 | + import MailOpen from '@lucide/svelte/icons/mail-open'; |
| 183 | + import EyeOff from '@lucide/svelte/icons/eye-off'; |
| 184 | + import StarOff from '@lucide/svelte/icons/star-off'; |
| 185 | + import EllipsisVertical from '@lucide/svelte/icons/ellipsis-vertical'; |
182 | 186 | import EmailIframe from './components/EmailIframe.svelte'; |
183 | 187 | import TabBar from './components/TabBar.svelte'; |
184 | 188 | import MessageTab from './components/MessageTab.svelte'; |
|
531 | 535 | mailboxView?.bulkMoveOpen, |
532 | 536 | false, |
533 | 537 | ); |
| 538 | + let bulkLabelOpen = $state(false); |
| 539 | + let bulkMoreOpen = $state(false); |
534 | 540 | let availableMoveTargets = chooseStore( |
535 | 541 | source.state?.availableMoveTargets, |
536 | 542 | mailboxView?.availableMoveTargets, |
|
2156 | 2162 | ) { |
2157 | 2163 | labelMenuOpen = false; |
2158 | 2164 | } |
| 2165 | + if (bulkLabelOpen && !e.target?.closest?.('[data-bulk-label]')) { |
| 2166 | + bulkLabelOpen = false; |
| 2167 | + } |
| 2168 | + if (bulkMoreOpen && !e.target?.closest?.('[data-bulk-more]')) { |
| 2169 | + bulkMoreOpen = false; |
| 2170 | + } |
2159 | 2171 | // Close action menu when clicking outside |
2160 | 2172 | if (actionMenuOpen && !e.target?.closest?.('[data-action-menu]')) { |
2161 | 2173 | actionMenuOpen = false; |
|
3408 | 3420 | }); |
3409 | 3421 | }; |
3410 | 3422 |
|
| 3423 | + const bulkSpam = async () => { |
| 3424 | + const path = spamFolderPath; |
| 3425 | + if (!path) { |
| 3426 | + showToast('Spam folder not found', 'error'); |
| 3427 | + return; |
| 3428 | + } |
| 3429 | + await bulkMoveTo(path); |
| 3430 | + }; |
| 3431 | +
|
| 3432 | + const bulkNotSpam = async () => { |
| 3433 | + const path = inboxFolderPath; |
| 3434 | + if (!path) { |
| 3435 | + showToast('Inbox folder not found', 'error'); |
| 3436 | + return; |
| 3437 | + } |
| 3438 | + await bulkMoveTo(path); |
| 3439 | + }; |
| 3440 | +
|
3411 | 3441 | const openContextMenu = (event, item) => { |
3412 | 3442 | event?.preventDefault?.(); |
3413 | 3443 | const isConversation = Array.isArray(item?.messages); |
|
3629 | 3659 | resolveFolderPath('getArchiveFolderPath', ['ARCHIVE'], $folders), |
3630 | 3660 | ); |
3631 | 3661 | const inboxFolderPath = $derived(resolveFolderPath(null, ['INBOX'], $folders)); |
| 3662 | + const spamFolderPath = $derived( |
| 3663 | + resolveFolderPath('getSpamFolderPath', ['SPAM', 'JUNK'], $folders), |
| 3664 | + ); |
3632 | 3665 |
|
3633 | 3666 | // ── Mobile tab bar state ────────────────────────────────────────────────── |
3634 | 3667 | const inboxUnseenCount = $derived(($folders || []).find((f) => f.path === 'INBOX')?.count || 0); |
|
3729 | 3762 | const canNotSpam = $derived(readerIsSpamOrJunk); |
3730 | 3763 | const showReaderMenuDivider = $derived(canReply || canForward || canEditDraft || canToggleRead); |
3731 | 3764 |
|
| 3765 | + // ── Bulk-action bar folder awareness ───────────────────────────────────── |
| 3766 | + const listIsSpamOrJunk = $derived(matchesFolderKey($selectedFolder, ['SPAM', 'JUNK'])); |
| 3767 | + const listIsTrashFolder = $derived( |
| 3768 | + matchesFolderKey($selectedFolder, ['TRASH', 'DELETED', 'DELETED ITEMS']), |
| 3769 | + ); |
| 3770 | + const listIsDraftFolder = $derived(isDraftFolder($selectedFolder)); |
| 3771 | +
|
3732 | 3772 | const openDraftFromMessage = async (msg) => { |
3733 | 3773 | if (!msg || !mailboxView?.composeModal?.open) return; |
3734 | 3774 | const account = $currentAccount || Local.get('email') || 'default'; |
|
5550 | 5590 | > |
5551 | 5591 | <span>{$selectedConversationIds.length}</span> |
5552 | 5592 | </div> |
5553 | | - <div class="flex items-center gap-1"> |
| 5593 | + <div class="flex items-center gap-1 flex-wrap"> |
5554 | 5594 | <button |
5555 | 5595 | class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
5556 | 5596 | type="button" |
|
5561 | 5601 | > |
5562 | 5602 | <X class="h-5 w-5" /> |
5563 | 5603 | </button> |
5564 | | - {#if $selectedFolder?.toUpperCase?.() !== 'ARCHIVE'} |
| 5604 | + {#if !listIsDraftFolder && !listIsTrashFolder && !listIsSpamOrJunk} |
| 5605 | + <button |
| 5606 | + class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
| 5607 | + type="button" |
| 5608 | + aria-label="Mark as read" |
| 5609 | + data-tooltip="Mark as read" |
| 5610 | + data-tooltip-position="bottom" |
| 5611 | + onclick={bulkMarkAsRead} |
| 5612 | + > |
| 5613 | + <MailOpen class="h-5 w-5" /> |
| 5614 | + </button> |
| 5615 | + <button |
| 5616 | + class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
| 5617 | + type="button" |
| 5618 | + aria-label="Mark as unread" |
| 5619 | + data-tooltip="Mark as unread" |
| 5620 | + data-tooltip-position="bottom" |
| 5621 | + onclick={bulkMarkAsUnread} |
| 5622 | + > |
| 5623 | + <EyeOff class="h-5 w-5" /> |
| 5624 | + </button> |
| 5625 | + {/if} |
| 5626 | + {#if !listIsDraftFolder} |
| 5627 | + <button |
| 5628 | + class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
| 5629 | + type="button" |
| 5630 | + aria-label="Star selected" |
| 5631 | + data-tooltip="Star selected" |
| 5632 | + data-tooltip-position="bottom" |
| 5633 | + onclick={bulkStar} |
| 5634 | + > |
| 5635 | + <Star class="h-5 w-5" /> |
| 5636 | + </button> |
| 5637 | + <button |
| 5638 | + class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
| 5639 | + type="button" |
| 5640 | + aria-label="Unstar selected" |
| 5641 | + data-tooltip="Unstar selected" |
| 5642 | + data-tooltip-position="bottom" |
| 5643 | + onclick={bulkUnstar} |
| 5644 | + > |
| 5645 | + <StarOff class="h-5 w-5" /> |
| 5646 | + </button> |
| 5647 | + {/if} |
| 5648 | + {#if !matchesFolderKey( $selectedFolder, ['ARCHIVE'], ) && !listIsSpamOrJunk && !listIsDraftFolder && !listIsTrashFolder} |
5565 | 5649 | <button |
5566 | 5650 | class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
5567 | 5651 | type="button" |
|
5573 | 5657 | <Archive class="h-5 w-5" /> |
5574 | 5658 | </button> |
5575 | 5659 | {/if} |
| 5660 | + {#if listIsSpamOrJunk} |
| 5661 | + <button |
| 5662 | + class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
| 5663 | + type="button" |
| 5664 | + aria-label="Not spam" |
| 5665 | + data-tooltip="Not spam" |
| 5666 | + data-tooltip-position="bottom" |
| 5667 | + onclick={bulkNotSpam} |
| 5668 | + > |
| 5669 | + <Inbox class="h-5 w-5" /> |
| 5670 | + </button> |
| 5671 | + {:else if !listIsDraftFolder && !listIsTrashFolder} |
| 5672 | + <button |
| 5673 | + class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
| 5674 | + type="button" |
| 5675 | + aria-label="Report spam" |
| 5676 | + data-tooltip="Report spam" |
| 5677 | + data-tooltip-position="bottom" |
| 5678 | + onclick={bulkSpam} |
| 5679 | + > |
| 5680 | + <ShieldAlert class="h-5 w-5" /> |
| 5681 | + </button> |
| 5682 | + {/if} |
5576 | 5683 | <button |
5577 | 5684 | class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
5578 | 5685 | type="button" |
|
5591 | 5698 | data-tooltip="Move selected" |
5592 | 5699 | data-tooltip-position="bottom" |
5593 | 5700 | onclick={() => { |
| 5701 | + bulkLabelOpen = false; |
| 5702 | + bulkMoreOpen = false; |
5594 | 5703 | if (bulkMoveOpen?.update) { |
5595 | 5704 | bulkMoveOpen.update((v) => !v); |
5596 | 5705 | } else if (mailboxView?.toggleBulkMove) { |
|
5616 | 5725 | </div> |
5617 | 5726 | {/if} |
5618 | 5727 | </div> |
| 5728 | + <div class="relative" data-bulk-label> |
| 5729 | + <button |
| 5730 | + class="inline-flex items-center justify-center h-11 w-11 hover:bg-accent hover:text-accent-foreground" |
| 5731 | + type="button" |
| 5732 | + aria-label="Label selected" |
| 5733 | + aria-expanded={bulkLabelOpen} |
| 5734 | + data-tooltip="Label selected" |
| 5735 | + data-tooltip-position="bottom" |
| 5736 | + onclick={() => { |
| 5737 | + bulkMoreOpen = false; |
| 5738 | + if (bulkMoveOpen?.set) bulkMoveOpen.set(false); |
| 5739 | + bulkLabelOpen = !bulkLabelOpen; |
| 5740 | + }} |
| 5741 | + > |
| 5742 | + <Tag class="h-5 w-5" /> |
| 5743 | + </button> |
| 5744 | + {#if bulkLabelOpen} |
| 5745 | + <div |
| 5746 | + class="absolute right-0 z-50 mt-1 min-w-[160px] max-h-[300px] overflow-y-auto border border-border bg-popover p-1 shadow-md" |
| 5747 | + > |
| 5748 | + <div |
| 5749 | + class="px-2 py-1 text-xs font-medium text-muted-foreground uppercase tracking-wider" |
| 5750 | + > |
| 5751 | + Apply label |
| 5752 | + </div> |
| 5753 | + {#if !availableLabelsFromStore.length} |
| 5754 | + <div class="px-3 py-2 text-sm text-muted-foreground">No labels yet.</div> |
| 5755 | + {/if} |
| 5756 | + {#each availableLabelsFromStore as label} |
| 5757 | + {#if label} |
| 5758 | + <button |
| 5759 | + type="button" |
| 5760 | + class={`flex items-center gap-2 w-full px-3 py-2 text-sm transition-colors ${labelState(label) === 'all' ? 'bg-accent text-accent-foreground' : 'hover:bg-accent'}`} |
| 5761 | + aria-pressed={labelState(label) === 'all'} |
| 5762 | + data-state={labelState(label)} |
| 5763 | + onclick={() => { |
| 5764 | + applyLabelToTargets(label); |
| 5765 | + bulkLabelOpen = false; |
| 5766 | + }} |
| 5767 | + > |
| 5768 | + <span |
| 5769 | + class="w-2.5 h-2.5 rounded-full shrink-0" |
| 5770 | + style={`background:${label.color || '#9ca3af'}`} |
| 5771 | + ></span> |
| 5772 | + <span class="flex-1 text-left" |
| 5773 | + >{label.name || label.label || label.value}</span |
| 5774 | + > |
| 5775 | + {#if labelState(label) === 'partial'} |
| 5776 | + <span class="text-muted-foreground">•</span> |
| 5777 | + {:else if labelState(label) === 'all'} |
| 5778 | + <Check class="h-4 w-4 shrink-0" /> |
| 5779 | + {/if} |
| 5780 | + </button> |
| 5781 | + {/if} |
| 5782 | + {/each} |
| 5783 | + <div class="my-1 h-px bg-border"></div> |
| 5784 | + <button |
| 5785 | + type="button" |
| 5786 | + class="flex items-center gap-2 w-full px-3 py-2 text-sm transition-colors hover:bg-accent text-muted-foreground" |
| 5787 | + onclick={() => { |
| 5788 | + openLabelModal(); |
| 5789 | + bulkLabelOpen = false; |
| 5790 | + }} |
| 5791 | + > |
| 5792 | + <span |
| 5793 | + class="w-2.5 h-2.5 rounded-full shrink-0" |
| 5794 | + style={`background:${labelFormColor || labelPalette[0]}`} |
| 5795 | + ></span> |
| 5796 | + <span>Create new label</span> |
| 5797 | + </button> |
| 5798 | + </div> |
| 5799 | + {/if} |
| 5800 | + </div> |
5619 | 5801 | </div> |
5620 | 5802 | </div> |
5621 | 5803 | {/if} |
|
0 commit comments