mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Improvements about mailbox listing and email display
This commit is contained in:
@@ -55,4 +55,12 @@ export default {
|
||||
}
|
||||
return repository.post('/webmail/emails/mark_as_not_junk/', body)
|
||||
},
|
||||
flagSelection(mailbox, selection, status) {
|
||||
const body = {
|
||||
mailbox,
|
||||
selection,
|
||||
status,
|
||||
}
|
||||
return repository.post('/webmail/emails/flag/', body)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<v-card class="mt-6 mb-2">
|
||||
<v-card class="mt-6 mb-2 mx-1">
|
||||
<v-toolbar color="white" flat>
|
||||
<v-checkbox class="mr-4" hide-details />
|
||||
<v-checkbox
|
||||
v-model="selectAll"
|
||||
class="mr-4"
|
||||
hide-details
|
||||
@update:model-value="toggleAllSelection"
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
@@ -14,54 +19,138 @@
|
||||
density="compact"
|
||||
class="flex-grow-0 w-33 mr-4"
|
||||
></v-text-field>
|
||||
<v-btn
|
||||
class="ml-2"
|
||||
color="error"
|
||||
variant="tonal"
|
||||
icon="mdi-trash-can"
|
||||
size="small"
|
||||
:loading="working"
|
||||
@click="deleteSelection"
|
||||
>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="$route.params.mailbox === 'Junk'"
|
||||
class="ml-2"
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
icon="mdi-fire"
|
||||
size="small"
|
||||
:loading="working"
|
||||
@click="markSelectionAsJunk"
|
||||
>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
class="ml-2"
|
||||
color="success"
|
||||
variant="tonal"
|
||||
icon="mdi-thumb-up"
|
||||
size="small"
|
||||
:loading="working"
|
||||
@click="markSelectionAsNotJunk"
|
||||
>
|
||||
</v-btn>
|
||||
<v-btn class="ml-2" variant="tonal" icon size="small">
|
||||
<v-icon icon="mdi-cog" />
|
||||
<v-menu activator="parent">
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
:title="$gettext('Mark as read')"
|
||||
prepend-icon="mdi-eye"
|
||||
@click="() => flagSelection('read')"
|
||||
/>
|
||||
<v-list-item
|
||||
:title="$gettext('Mark as unread')"
|
||||
prepend-icon="mdi-eye-outline"
|
||||
@click="() => flagSelection('unread')"
|
||||
/>
|
||||
<v-list-item
|
||||
:title="$gettext('Mark as followed')"
|
||||
prepend-icon="mdi-star"
|
||||
@click="() => flagSelection('flagged')"
|
||||
/>
|
||||
<v-list-item
|
||||
:title="$gettext('Mark as unfollowed')"
|
||||
prepend-icon="mdi-star-outline"
|
||||
@click="() => flagSelection('unflagged')"
|
||||
/>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<div v-if="emails.results" class="d-flex align-center">
|
||||
<div class="text-caption mr-2">
|
||||
{{ emails.first_index }}-{{ emails.last_index }} {{ $gettext('on') }}
|
||||
{{ emails.count }}
|
||||
</div>
|
||||
<div>
|
||||
<v-btn
|
||||
icon="mdi-chevron-left"
|
||||
size="x-small"
|
||||
:disabled="emails.prev_page === null"
|
||||
@click="page = emails.prev_page"
|
||||
/>
|
||||
<v-btn
|
||||
icon="mdi-chevron-right"
|
||||
size="x-small"
|
||||
:disabled="emails.next_page === null"
|
||||
@click="page = emails.next_page"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-toolbar>
|
||||
</v-card>
|
||||
<v-skeleton-loader v-if="loading" type="card@2"></v-skeleton-loader>
|
||||
<template v-else>
|
||||
<v-card
|
||||
v-for="email in emails"
|
||||
:key="email.imapid"
|
||||
density="compact"
|
||||
class="mb-2"
|
||||
>
|
||||
<v-card-text
|
||||
class="d-flex align-center"
|
||||
:class="{ 'font-weight-bold': email.style === 'unseen' }"
|
||||
<div class="emails position-absolute top-0 bottom-0 w-100 overflow-y-auto">
|
||||
<v-card
|
||||
v-for="email in emails.results"
|
||||
:key="email.imapid"
|
||||
density="compact"
|
||||
class="mb-2 mx-1"
|
||||
>
|
||||
<v-checkbox :value="email.imapid" hide-details />
|
||||
<v-btn
|
||||
:icon="email.flagged ? 'mdiu-star' : 'mdi-star-outline'"
|
||||
variant="flat"
|
||||
/>
|
||||
<div class="ml-4 clickable" @click="openEmail(email.imapid)">
|
||||
<div>{{ email.subject }}</div>
|
||||
<div class="mt-1 text-grey">
|
||||
<span
|
||||
v-if="email.from_address.name"
|
||||
:title="email.from_address.address"
|
||||
>{{ email.from_address.name }}</span
|
||||
>
|
||||
<span v-else>{{ email.from_address.address }}</span>
|
||||
<v-card-text
|
||||
class="d-flex align-center"
|
||||
:class="{ 'font-weight-bold': email.style === 'unseen' }"
|
||||
>
|
||||
<v-checkbox v-model="selection" :value="email.imapid" hide-details />
|
||||
<v-btn
|
||||
:icon="email.flagged ? 'mdiu-star' : 'mdi-star-outline'"
|
||||
variant="flat"
|
||||
/>
|
||||
<div class="ml-4 clickable" @click="openEmail(email.imapid)">
|
||||
<div>{{ email.subject }}</div>
|
||||
<div class="mt-1 text-grey">
|
||||
<span
|
||||
v-if="email.from_address.name"
|
||||
:title="email.from_address.address"
|
||||
>{{ email.from_address.name }}</span
|
||||
>
|
||||
<span v-else>{{ email.from_address.address }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-spacer />
|
||||
<div class="text-right">
|
||||
<div>{{ email.date }}</div>
|
||||
<div class="mt-1">
|
||||
<v-icon v-if="email.answered" icon="mdi-reply-outline" />
|
||||
<v-icon v-if="email.forwarded" icon="mdi-share-outline" />
|
||||
<v-icon v-if="email.attachments" icon="mdi-paperclip" />
|
||||
<span class="text-grey">{{ $filesize(email.size) }}</span>
|
||||
<v-spacer />
|
||||
<div class="text-right">
|
||||
<div>{{ email.date }}</div>
|
||||
<div class="mt-1">
|
||||
<v-icon v-if="email.answered" icon="mdi-reply-outline" />
|
||||
<v-icon v-if="email.forwarded" icon="mdi-share-outline" />
|
||||
<v-icon v-if="email.attachments" icon="mdi-paperclip" />
|
||||
<span class="text-grey">{{ $filesize(email.size) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useBusStore } from '@/stores'
|
||||
import api from '@/api/webmail'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -71,11 +160,18 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const { displayNotification } = useBusStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const loading = ref(false)
|
||||
const emails = ref([])
|
||||
const emails = ref({})
|
||||
const page = ref(1)
|
||||
const search = ref('')
|
||||
const selectAll = ref(false)
|
||||
const selection = ref([])
|
||||
const working = ref(false)
|
||||
|
||||
const openEmail = (emailid) => {
|
||||
router.push({
|
||||
@@ -84,29 +180,103 @@ const openEmail = (emailid) => {
|
||||
})
|
||||
}
|
||||
|
||||
const fetchEmails = () => {
|
||||
emails.value = {}
|
||||
loading.value = true
|
||||
api
|
||||
.getMailboxEmails(props.mailbox, { page: page.value })
|
||||
.then((resp) => {
|
||||
emails.value = resp.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const toggleAllSelection = (value) => {
|
||||
if (!value) {
|
||||
selection.value = []
|
||||
} else {
|
||||
selection.value = emails.value.results.map((email) => email.imapid)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteSelection = () => {
|
||||
if (!selection.value.length) {
|
||||
return
|
||||
}
|
||||
working.value = true
|
||||
api.deleteSelection(route.query.mailbox, selection.value).then(() => {
|
||||
working.value = false
|
||||
displayNotification({ msg: $gettext('Message(s) deleted') })
|
||||
fetchEmails()
|
||||
})
|
||||
}
|
||||
|
||||
const markSelectionAsJunk = () => {
|
||||
if (!selection.value.length) {
|
||||
return
|
||||
}
|
||||
working.value = true
|
||||
api.markSelectionAsJunk(route.query.mailbox, selection.value).then(() => {
|
||||
working.value = false
|
||||
displayNotification({ msg: $gettext('Message(s) marked as junk') })
|
||||
fetchEmails()
|
||||
})
|
||||
}
|
||||
|
||||
const markSelectionAsNotJunk = () => {
|
||||
if (!selection.value.length) {
|
||||
return
|
||||
}
|
||||
working.value = true
|
||||
api.markSelectionAsNotJunk(route.query.mailbox, selection.value).then(() => {
|
||||
working.value = false
|
||||
displayNotification({ msg: $gettext('Message(s) marked as not junk') })
|
||||
fetchEmails()
|
||||
})
|
||||
}
|
||||
|
||||
const flagSelection = (status) => {
|
||||
if (!selection.value.length) {
|
||||
return
|
||||
}
|
||||
working.value = true
|
||||
api.flagSelection(route.query.mailbox, selection.value, status).then(() => {
|
||||
working.value = false
|
||||
selection.value = []
|
||||
displayNotification({ msg: $gettext('Message(s) flagged') })
|
||||
fetchEmails()
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.mailbox,
|
||||
(value) => {
|
||||
emails.value = []
|
||||
loading.value = true
|
||||
api
|
||||
.getMailboxEmails(value, {})
|
||||
.then((resp) => {
|
||||
emails.value = resp.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
() => {
|
||||
fetchEmails()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
watch(selection, () => {
|
||||
if (!selection.value.length) {
|
||||
selectAll.value = false
|
||||
} else {
|
||||
selectAll.value = true
|
||||
}
|
||||
})
|
||||
watch(page, () => {
|
||||
fetchEmails()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-card-text {
|
||||
padding: 0;
|
||||
}
|
||||
.emails {
|
||||
margin-top: 150px;
|
||||
}
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,14 @@
|
||||
@mouseleave="setHover(mailbox, false)"
|
||||
>
|
||||
<v-icon :icon="iconByMailboxType[mailbox.type]" class="mr-4" />
|
||||
{{ getMailboxLabel(mailbox) }}
|
||||
<template v-if="mailbox.unseen > 0">
|
||||
<span class="font-weight-bold">
|
||||
{{ getMailboxLabel(mailbox) }} ({{ mailbox.unseen }})
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ getMailboxLabel(mailbox) }}
|
||||
</template>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="mailbox.sub"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-show="loaded" class="bg-white rounded-lg pa-4 mt-12">
|
||||
<div v-show="loaded" class="bg-white rounded-lg pa-4 position-relative h-100">
|
||||
<v-toolbar color="white">
|
||||
<v-btn icon="mdi-arrow-left" size="small" variant="flat" @click="close" />
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
|
||||
<div v-if="email" class="bg-white pa-4">
|
||||
<div v-if="email" ref="headers" class="bg-white pa-4">
|
||||
<h2>{{ email.subject }}</h2>
|
||||
<div class="d-flex mt-2">
|
||||
<h3 v-if="email.from_address.fulladdress">
|
||||
@@ -97,13 +97,13 @@
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
<iframe class="email-frame" />
|
||||
</div>
|
||||
<iframe class="email-frame" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useBusStore } from '@/stores'
|
||||
@@ -116,6 +116,7 @@ const router = useRouter()
|
||||
|
||||
const enableLinks = ref(false)
|
||||
const email = ref(null)
|
||||
const headers = ref(null)
|
||||
const loaded = ref(false)
|
||||
const working = ref(false)
|
||||
|
||||
@@ -129,9 +130,14 @@ const recipients = computed(() => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', resizeEmailIframe)
|
||||
fetchMailContent()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resizeEmailIframe)
|
||||
})
|
||||
|
||||
watch(enableLinks, () => {
|
||||
fetchMailContent()
|
||||
})
|
||||
@@ -143,6 +149,14 @@ const close = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const resizeEmailIframe = () => {
|
||||
const iframe = document.querySelector('iframe')
|
||||
const rect = headers.value.getBoundingClientRect()
|
||||
iframe.style.top = `${rect.bottom}px`
|
||||
iframe.style.width = `${rect.width - 24}px`
|
||||
iframe.style.height = `${window.innerHeight - rect.bottom - 32}px`
|
||||
}
|
||||
|
||||
const fetchMailContent = () => {
|
||||
const options = {
|
||||
dformat: 'html',
|
||||
@@ -160,6 +174,7 @@ const fetchMailContent = () => {
|
||||
iframeDoc.write(email.value.body)
|
||||
iframeDoc.close()
|
||||
loaded.value = true
|
||||
nextTick(resizeEmailIframe)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -220,10 +235,10 @@ const markEmailAsNotJunk = () => {
|
||||
|
||||
<style>
|
||||
.email-frame {
|
||||
width: 100%;
|
||||
min-height: 1000px;
|
||||
position: absolute !important;
|
||||
left: 24px;
|
||||
overflow-y: auto;
|
||||
border: none;
|
||||
margin-top: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<template>
|
||||
<div class="text-h5 ml-4">
|
||||
{{ $gettext('Webmail') }}
|
||||
<div class="position-relative h-100">
|
||||
<div class="text-h5 ml-4">
|
||||
{{ $gettext('Webmail') }}
|
||||
</div>
|
||||
<EmailList :mailbox="$route.query.mailbox || 'INBOX'" />
|
||||
</div>
|
||||
<EmailList :mailbox="$route.query.mailbox || 'INBOX'" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
@@ -568,7 +568,7 @@ class IMAPconnector:
|
||||
mb["unseen"] = count
|
||||
return md_mailboxes
|
||||
|
||||
def _add_flag(self, mbox: str, msgset, flag: str) -> None:
|
||||
def _add_flag(self, mbox: str, msgset: list[str], flag: str) -> None:
|
||||
"""Add flag to a messages set.
|
||||
|
||||
:param mbox: the mailbox containing the messages
|
||||
@@ -576,9 +576,9 @@ class IMAPconnector:
|
||||
:param flag: the flag to add
|
||||
"""
|
||||
self.select_mailbox(mbox, False)
|
||||
self._cmd("STORE", msgset, "+FLAGS", flag)
|
||||
self._cmd("STORE", ",".join(msgset), "+FLAGS", flag)
|
||||
|
||||
def _remove_flag(self, mbox: str, msgset, flag: str) -> None:
|
||||
def _remove_flag(self, mbox: str, msgset: list[str], flag: str) -> None:
|
||||
"""Remove flag from a message set.
|
||||
|
||||
:param mbox: the mailbox containing the messages
|
||||
@@ -586,9 +586,9 @@ class IMAPconnector:
|
||||
:param flag: the flag to remove
|
||||
"""
|
||||
self.select_mailbox(mbox, False)
|
||||
self._cmd("STORE", msgset, "-FLAGS", flag)
|
||||
self._cmd("STORE", ",".join(msgset), "-FLAGS", flag)
|
||||
|
||||
def mark_messages_unread(self, mbox: str, msgset) -> None:
|
||||
def mark_messages_unread(self, mbox: str, msgset: list[str]) -> None:
|
||||
"""Mark a set of messages as unread.
|
||||
|
||||
:param mbox: the mailbox containing the messages
|
||||
@@ -596,7 +596,7 @@ class IMAPconnector:
|
||||
"""
|
||||
self._remove_flag(mbox, msgset, r"(\Seen)")
|
||||
|
||||
def mark_messages_read(self, mbox: str, msgset) -> None:
|
||||
def mark_messages_read(self, mbox: str, msgset: list[str]) -> None:
|
||||
"""Mark a set of messages as unread.
|
||||
|
||||
:param mbox: the mailbox containing the messages
|
||||
@@ -604,7 +604,7 @@ class IMAPconnector:
|
||||
"""
|
||||
self._add_flag(mbox, msgset, r"(\Seen)")
|
||||
|
||||
def mark_messages_flagged(self, mbox: str, msgset) -> None:
|
||||
def mark_messages_flagged(self, mbox: str, msgset: list[str]) -> None:
|
||||
"""Mark a set of messages as flagged.
|
||||
|
||||
:param mbox: the mailbox containing the messages
|
||||
@@ -612,7 +612,7 @@ class IMAPconnector:
|
||||
"""
|
||||
self._add_flag(mbox, msgset, r"(\Flagged)")
|
||||
|
||||
def mark_messages_unflagged(self, mbox: str, msgset) -> None:
|
||||
def mark_messages_unflagged(self, mbox: str, msgset: list[str]) -> None:
|
||||
"""Mark a set of messages as unflagged.
|
||||
|
||||
:param mbox: the mailbox containing the messages
|
||||
@@ -761,7 +761,6 @@ class IMAPconnector:
|
||||
msg = email.message_from_string(
|
||||
msg_data[f"BODY[HEADER.FIELDS ({headers})]"]
|
||||
)
|
||||
print(type(msg))
|
||||
msg["imapid"] = uid
|
||||
msg["size"] = msg_data["RFC822.SIZE"]
|
||||
if r"\Seen" not in msg_data["FLAGS"]:
|
||||
|
||||
@@ -92,6 +92,16 @@ class EmailHeadersSerializer(serializers.Serializer):
|
||||
return imapheader.parse_date(obj["Date"])
|
||||
|
||||
|
||||
class PaginatedEmailListSerializer(serializers.Serializer):
|
||||
|
||||
count = serializers.IntegerField()
|
||||
first_index = serializers.IntegerField()
|
||||
last_index = serializers.IntegerField()
|
||||
prev_page = serializers.IntegerField()
|
||||
next_page = serializers.IntegerField()
|
||||
results = EmailHeadersSerializer(many=True)
|
||||
|
||||
|
||||
class EmailSerializer(serializers.Serializer):
|
||||
|
||||
subject = serializers.CharField(source="Subject")
|
||||
@@ -117,3 +127,20 @@ class MoveSelectionSerializer(serializers.Serializer):
|
||||
|
||||
def validate_selection(self, value):
|
||||
return [item for item in value if item.isdigit()]
|
||||
|
||||
|
||||
class FlagSelectionSerializer(serializers.Serializer):
|
||||
|
||||
mailbox = serializers.CharField()
|
||||
selection = serializers.ListField(child=serializers.CharField())
|
||||
status = serializers.ChoiceField(
|
||||
choices=[
|
||||
("read", "Read"),
|
||||
("unread", "Unread"),
|
||||
("flagged", "Flagged"),
|
||||
("unflagged", "Unflagged"),
|
||||
]
|
||||
)
|
||||
|
||||
def validate_selection(self, value):
|
||||
return [item for item in value if item.isdigit()]
|
||||
|
||||
@@ -15,7 +15,7 @@ class UserMailboxViewSet(viewsets.GenericViewSet):
|
||||
serializer_class = serializers.UserMailboxSerializer
|
||||
|
||||
def list(self, request):
|
||||
parent_mailbox = request.GET.get("mailbox", "")
|
||||
parent_mailbox = request.GET.get("mailbox")
|
||||
imapc = lib.get_imapconnector(request)
|
||||
mboxes = imapc.getmboxes(request.user, parent_mailbox)
|
||||
serializer = serializers.UserMailboxSerializer(mboxes, many=True)
|
||||
@@ -24,18 +24,36 @@ class UserMailboxViewSet(viewsets.GenericViewSet):
|
||||
|
||||
class UserEmailViewSet(viewsets.GenericViewSet):
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "list":
|
||||
return serializers.PaginatedEmailListSerializer
|
||||
if self.action in ["delete", "mark_as_junk", "mark_as_not_junk"]:
|
||||
return serializers.MoveSelectionSerializer
|
||||
if self.action == "flag":
|
||||
return serializers.FlagSelectionSerializer
|
||||
return serializers.EmailSerializer
|
||||
|
||||
def list(self, request):
|
||||
mailbox = request.GET.get("mailbox", "INBOX")
|
||||
imapc = lib.get_imapconnector(request)
|
||||
paginator = Paginator(
|
||||
imapc.messages_count(mbox=mailbox),
|
||||
request.user.parameters.get_value("messages_per_page"),
|
||||
)
|
||||
page = paginator.getpage(1)
|
||||
total = imapc.messages_count(mbox=mailbox)
|
||||
messages_per_page = request.user.parameters.get_value("messages_per_page")
|
||||
paginator = Paginator(total, messages_per_page)
|
||||
page_num = int(request.GET.get("page", 1))
|
||||
page = paginator.getpage(int(request.GET.get("page", 1)))
|
||||
content = imapc.fetch(page.id_start, page.id_stop, mbox=mailbox)
|
||||
content = [dict(msg) for msg in content]
|
||||
serializer = serializers.EmailHeadersSerializer(content, many=True)
|
||||
return response.Response(serializer.data)
|
||||
return response.Response(
|
||||
{
|
||||
"count": total,
|
||||
"first_index": page_num * messages_per_page,
|
||||
"last_index": (page_num * messages_per_page) + len(content),
|
||||
"prev_page": page.previous_page_number if page.has_previous else None,
|
||||
"next_page": page.next_page_number if page.has_next else None,
|
||||
"results": serializer.data,
|
||||
}
|
||||
)
|
||||
|
||||
def move_selection(self, request, destination: str) -> int:
|
||||
"""Move selected messages to the given mailbox."""
|
||||
@@ -80,6 +98,20 @@ class UserEmailViewSet(viewsets.GenericViewSet):
|
||||
count = self.move_selection(request, "INBOX")
|
||||
return response.Response({"count": count})
|
||||
|
||||
@action(
|
||||
methods=["post"],
|
||||
detail=False,
|
||||
serializer_class=serializers.FlagSelectionSerializer,
|
||||
)
|
||||
def flag(self, request):
|
||||
serializer = serializers.FlagSelectionSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
imapc = lib.get_imapconnector(request)
|
||||
getattr(imapc, f"mark_messages_{serializer.validated_data['status']}")(
|
||||
serializer.validated_data["mailbox"], serializer.validated_data["selection"]
|
||||
)
|
||||
return response.Response({"count": len(serializer.validated_data["selection"])})
|
||||
|
||||
@action(methods=["get"], detail=False)
|
||||
def content(self, request):
|
||||
mailbox = request.GET.get("mailbox", "INBOX")
|
||||
|
||||
Reference in New Issue
Block a user