mirror of
https://github.com/ChrispyBacon-dev/DockFlare.git
synced 2026-04-28 03:39:32 +00:00
several webmail und mail manager fixes
This commit is contained in:
parent
5e0ddbd46b
commit
35b449a478
7 changed files with 62 additions and 13 deletions
|
|
@ -1460,7 +1460,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
fixResourcesAndBase();
|
fixResourcesAndBase();
|
||||||
themeManager.initialize();
|
themeManager.initialize();
|
||||||
initializeAllTomSelects();
|
initializeAllTomSelects();
|
||||||
|
|
||||||
const manualServiceTypeSelect = document.getElementById('manual_service_type');
|
const manualServiceTypeSelect = document.getElementById('manual_service_type');
|
||||||
if (manualServiceTypeSelect) {
|
if (manualServiceTypeSelect) {
|
||||||
manualServiceTypeSelect.addEventListener('change', updateManualRuleServiceFields);
|
manualServiceTypeSelect.addEventListener('change', updateManualRuleServiceFields);
|
||||||
|
|
|
||||||
|
|
@ -1075,7 +1075,7 @@ def internal_mail_config():
|
||||||
'outbound_worker_url': d.get('outbound_worker_url', ''),
|
'outbound_worker_url': d.get('outbound_worker_url', ''),
|
||||||
'outbound_auth_secret': d.get('outbound_auth_secret', ''),
|
'outbound_auth_secret': d.get('outbound_auth_secret', ''),
|
||||||
'mailboxes': {
|
'mailboxes': {
|
||||||
addr: {'display_name': m.get('display_name', '')}
|
addr: {'display_name': m.get('display_name', ''), 'quota_bytes': m.get('quota_bytes', 10737418240)}
|
||||||
for addr, m in d.get('mailboxes', {}).items()
|
for addr, m in d.get('mailboxes', {}).items()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,11 @@ def get_mailboxes():
|
||||||
mb['received_count'] = received.get(addr, 0)
|
mb['received_count'] = received.get(addr, 0)
|
||||||
mb['sent_count'] = sent.get(addr, 0)
|
mb['sent_count'] = sent.get(addr, 0)
|
||||||
mb['storage_bytes'] = storage.get(addr, 0)
|
mb['storage_bytes'] = storage.get(addr, 0)
|
||||||
|
if mb['quota_bytes'] and mb['quota_bytes'] > 0:
|
||||||
|
if storage.get(addr, 0) <= mb['quota_bytes'] and mb['quota_exceeded_count'] > 0:
|
||||||
|
db.execute("UPDATE mailboxes SET quota_exceeded_count=0 WHERE address=?", (addr,))
|
||||||
|
mb['quota_exceeded_count'] = 0
|
||||||
|
db.commit()
|
||||||
return jsonify(mailboxes)
|
return jsonify(mailboxes)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,9 @@ const confirmEdit = async () => {
|
||||||
class="z-50 rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md flex items-center gap-4"
|
class="z-50 rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md flex items-center gap-4"
|
||||||
>
|
>
|
||||||
{{ f.name }}
|
{{ f.name }}
|
||||||
<span class="ml-auto text-muted-foreground flex gap-1">
|
<span v-if="f.total_count > 0" class="ml-auto text-muted-foreground flex gap-1">
|
||||||
<span v-if="f.unread_count" class="font-bold">{{ f.unread_count }} /</span>
|
<span v-if="f.unread_count" class="font-bold">{{ f.unread_count }} /</span>
|
||||||
<span>{{ f.total_count || 0 }}</span>
|
<span>{{ f.total_count }}</span>
|
||||||
</span>
|
</span>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</TooltipPortal>
|
</TooltipPortal>
|
||||||
|
|
@ -199,13 +199,14 @@ const confirmEdit = async () => {
|
||||||
<component :is="getIcon(f.name)" class="size-4 flex-shrink-0" :style="f.color ? `color:${f.color}` : ''" />
|
<component :is="getIcon(f.name)" class="size-4 flex-shrink-0" :style="f.color ? `color:${f.color}` : ''" />
|
||||||
<span class="truncate">{{ f.name }}</span>
|
<span class="truncate">{{ f.name }}</span>
|
||||||
<span
|
<span
|
||||||
|
v-if="f.total_count > 0"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'ml-auto text-xs flex-shrink-0 flex gap-1',
|
'ml-auto text-xs flex-shrink-0 flex gap-1',
|
||||||
store.currentFolder === f.name ? 'text-primary-foreground' : 'text-muted-foreground',
|
store.currentFolder === f.name ? 'text-primary-foreground' : 'text-muted-foreground',
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
<span v-if="f.unread_count" class="font-bold">{{ f.unread_count }} /</span>
|
<span v-if="f.unread_count" class="font-bold">{{ f.unread_count }} /</span>
|
||||||
<span>{{ f.total_count || 0 }}</span>
|
<span>{{ f.total_count }}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<!-- Custom folder actions — absolutely positioned so they don't affect count alignment -->
|
<!-- Custom folder actions — absolutely positioned so they don't affect count alignment -->
|
||||||
|
|
@ -251,6 +252,12 @@ const confirmEdit = async () => {
|
||||||
:style="`background:${c}; border-color:${newFolderColor === c ? '#000' : 'transparent'}`"
|
:style="`background:${c}; border-color:${newFolderColor === c ? '#000' : 'transparent'}`"
|
||||||
@click="newFolderColor = newFolderColor === c ? '' : c"
|
@click="newFolderColor = newFolderColor === c ? '' : c"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
class="h-5 w-5 rounded-full border-2 text-xs flex items-center justify-center text-muted-foreground hover:bg-accent"
|
||||||
|
:style="`border-color:${!newFolderColor ? '#888' : 'transparent'}`"
|
||||||
|
title="No colour"
|
||||||
|
@click="newFolderColor = ''"
|
||||||
|
>✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-1 justify-end">
|
<div class="flex gap-1 justify-end">
|
||||||
<button class="text-xs px-2 py-1 rounded hover:bg-accent text-muted-foreground" @click="cancelNewFolder">Cancel</button>
|
<button class="text-xs px-2 py-1 rounded hover:bg-accent text-muted-foreground" @click="cancelNewFolder">Cancel</button>
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,8 @@ const trash = async () => {
|
||||||
await mailApi.deleteMessage(store.currentMailbox, props.message.id)
|
await mailApi.deleteMessage(store.currentMailbox, props.message.id)
|
||||||
store.messages = store.messages.filter((m: any) => m.id !== props.message!.id)
|
store.messages = store.messages.filter((m: any) => m.id !== props.message!.id)
|
||||||
store.currentMessage = null
|
store.currentMessage = null
|
||||||
|
const fRes = await mailApi.getFolders(store.currentMailbox)
|
||||||
|
store.folders = fRes.data
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to trash message', e)
|
console.error('Failed to trash message', e)
|
||||||
}
|
}
|
||||||
|
|
@ -164,12 +166,28 @@ const markUnread = async () => {
|
||||||
await mailApi.updateMessage(store.currentMailbox, props.message.id, { is_read: false })
|
await mailApi.updateMessage(store.currentMailbox, props.message.id, { is_read: false })
|
||||||
const idx = store.messages.findIndex((m: any) => m.id === props.message!.id)
|
const idx = store.messages.findIndex((m: any) => m.id === props.message!.id)
|
||||||
if (idx !== -1) store.messages[idx] = { ...store.messages[idx], is_read: 0 }
|
if (idx !== -1) store.messages[idx] = { ...store.messages[idx], is_read: 0 }
|
||||||
store.currentMessage = null
|
store.currentMessage = { ...store.currentMessage, is_read: 0 }
|
||||||
|
const fRes = await mailApi.getFolders(store.currentMailbox)
|
||||||
|
store.folders = fRes.data
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to mark unread', e)
|
console.error('Failed to mark unread', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const markRead = async () => {
|
||||||
|
if (!props.message || !store.currentMailbox) return
|
||||||
|
try {
|
||||||
|
await mailApi.updateMessage(store.currentMailbox, props.message.id, { is_read: true })
|
||||||
|
const idx = store.messages.findIndex((m: any) => m.id === props.message!.id)
|
||||||
|
if (idx !== -1) store.messages[idx] = { ...store.messages[idx], is_read: 1 }
|
||||||
|
store.currentMessage = { ...store.currentMessage, is_read: 1 }
|
||||||
|
const fRes = await mailApi.getFolders(store.currentMailbox)
|
||||||
|
store.folders = fRes.data
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to mark read', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toggleStar = async () => {
|
const toggleStar = async () => {
|
||||||
if (!props.message || !store.currentMailbox) return
|
if (!props.message || !store.currentMailbox) return
|
||||||
const newVal = props.message.is_starred ? 0 : 1
|
const newVal = props.message.is_starred ? 0 : 1
|
||||||
|
|
@ -192,6 +210,8 @@ const moveToFolder = async (targetFolder: any) => {
|
||||||
})
|
})
|
||||||
store.messages = store.messages.filter((m: any) => m.id !== props.message!.id)
|
store.messages = store.messages.filter((m: any) => m.id !== props.message!.id)
|
||||||
store.currentMessage = null
|
store.currentMessage = null
|
||||||
|
const fRes = await mailApi.getFolders(store.currentMailbox)
|
||||||
|
store.folders = fRes.data
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to move message', e)
|
console.error('Failed to move message', e)
|
||||||
}
|
}
|
||||||
|
|
@ -401,12 +421,21 @@ const sendInlineReply = async () => {
|
||||||
class="z-50 min-w-[160px] rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
|
class="z-50 min-w-[160px] rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
|
||||||
>
|
>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
|
v-if="props.message?.is_read"
|
||||||
class="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent"
|
class="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent"
|
||||||
@click="markUnread"
|
@click="markUnread"
|
||||||
>
|
>
|
||||||
<MailOpen class="mr-2 size-4" />
|
<MailOpen class="mr-2 size-4" />
|
||||||
Mark as unread
|
Mark as unread
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
v-else
|
||||||
|
class="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent"
|
||||||
|
@click="markRead"
|
||||||
|
>
|
||||||
|
<MailOpen class="mr-2 size-4" />
|
||||||
|
Mark as read
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
class="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent"
|
class="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent"
|
||||||
@click="toggleStar"
|
@click="toggleStar"
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,11 @@ export function useMailPolling() {
|
||||||
const payload = mRes.data
|
const payload = mRes.data
|
||||||
mailStore.messages = Array.isArray(payload) ? payload : payload.items || []
|
mailStore.messages = Array.isArray(payload) ? payload : payload.items || []
|
||||||
} catch { /* network error — skip */ }
|
} catch { /* network error — skip */ }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fRes = await mailApi.getFolders(s.address)
|
||||||
|
mailStore.folders = fRes.data
|
||||||
|
} catch { /* network error — skip */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Notification.permission === 'granted') {
|
if (Notification.permission === 'granted') {
|
||||||
|
|
|
||||||
|
|
@ -97,15 +97,16 @@ watch(() => store.sortOrder, () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => store.currentMessage, async (msg) => {
|
watch(() => store.currentMessage, async (msg) => {
|
||||||
if (!msg || msg.attachments !== undefined) return
|
if (!msg) return
|
||||||
try {
|
try {
|
||||||
const res = await mailApi.getMessage(store.currentMailbox, msg.id)
|
|
||||||
const fullMsg = res.data
|
|
||||||
store.currentMessage = fullMsg
|
|
||||||
|
|
||||||
const idx = store.messages.findIndex((m: any) => m.id === msg.id)
|
const idx = store.messages.findIndex((m: any) => m.id === msg.id)
|
||||||
if (idx !== -1) {
|
let fullMsg = msg
|
||||||
store.messages[idx] = fullMsg
|
|
||||||
|
if (msg.attachments === undefined) {
|
||||||
|
const res = await mailApi.getMessage(store.currentMailbox, msg.id)
|
||||||
|
fullMsg = res.data
|
||||||
|
store.currentMessage = fullMsg
|
||||||
|
if (idx !== -1) store.messages[idx] = fullMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fullMsg.is_read) {
|
if (!fullMsg.is_read) {
|
||||||
|
|
@ -114,6 +115,8 @@ watch(() => store.currentMessage, async (msg) => {
|
||||||
store.messages[idx] = { ...store.messages[idx], is_read: 1 }
|
store.messages[idx] = { ...store.messages[idx], is_read: 1 }
|
||||||
}
|
}
|
||||||
store.currentMessage = { ...store.currentMessage, is_read: 1 }
|
store.currentMessage = { ...store.currentMessage, is_read: 1 }
|
||||||
|
const fRes = await mailApi.getFolders(store.currentMailbox)
|
||||||
|
store.folders = fRes.data
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load message', e)
|
console.error('Failed to load message', e)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue