From 4f3b474e2c03e70846fe5fa148c2f56f17086b13 Mon Sep 17 00:00:00 2001 From: tammi-23 Date: Mon, 9 Feb 2026 21:50:07 +0100 Subject: [PATCH 01/15] Added draft state handling --- .../src/components/MailComposeForm.vue | 23 +++-- .../src/components/MailWidget.vue | 1 - .../src/components/MailboxTree.vue | 8 +- .../src/composables/piniaStores/accounts.ts | 77 ++++------------- .../src/composables/piniaStores/helpers.ts | 20 +++++ .../src/composables/piniaStores/mailboxes.ts | 82 ++++++------------ .../src/composables/piniaStores/mails.ts | 83 ++++++++----------- .../src/helpers/mailDraftConnector.ts | 42 ++++++++++ 8 files changed, 160 insertions(+), 176 deletions(-) create mode 100644 packages/web-app-mail/src/composables/piniaStores/helpers.ts create mode 100644 packages/web-app-mail/src/helpers/mailDraftConnector.ts diff --git a/packages/web-app-mail/src/components/MailComposeForm.vue b/packages/web-app-mail/src/components/MailComposeForm.vue index 04e8a1ce72..8481c02b72 100644 --- a/packages/web-app-mail/src/components/MailComposeForm.vue +++ b/packages/web-app-mail/src/components/MailComposeForm.vue @@ -65,7 +65,7 @@ diff --git a/packages/web-app-mail/src/composables/piniaStores/accounts.ts b/packages/web-app-mail/src/composables/piniaStores/accounts.ts index ff160298d2..f2b45da730 100644 --- a/packages/web-app-mail/src/composables/piniaStores/accounts.ts +++ b/packages/web-app-mail/src/composables/piniaStores/accounts.ts @@ -1,78 +1,35 @@ import { defineStore } from 'pinia' -import { computed, ref, unref } from 'vue' -import { MailAccount } from '../../types' -import { useRouteQuery } from '@opencloud-eu/web-pkg/src' - -export const useAccountsStore = defineStore('accounts', () => { - const currentAccountIdQuery = useRouteQuery('accountId') +import { computed, ref } from 'vue' +import type { MailAccount } from '../../types' +import { useRouteQueryId } from './helpers' +export const useAccountsStore = defineStore('mail-accounts', () => { const accounts = ref([]) - const currentAccountId = ref() - - const currentAccount = computed(() => - unref(accounts).find((account) => account.accountId === unref(currentAccountId)) - ) - const setAccounts = (data: MailAccount[]) => { - accounts.value = data - } + const currentAccountId = useRouteQueryId('accountId') - const upsertAccount = (data: MailAccount) => { - const existing = unref(accounts).find(({ accountId }) => accountId === data.accountId) - if (existing) { - Object.assign(existing, data) - return - } - unref(accounts).push(data) - } + const currentAccount = computed(() => { + const id = currentAccountId.value + return accounts.value.find((a) => a.accountId === id) + }) - const removeAccounts = (values: MailAccount[]) => { - accounts.value = unref(accounts).filter( - (account) => !values.find(({ accountId }) => accountId === account.accountId) - ) + const setAccounts = (list: MailAccount[]) => { + accounts.value = list ?? [] - if (values.some((v) => v.accountId === unref(currentAccountId))) { - currentAccountId.value = null - currentAccountIdQuery.value = null + if (!currentAccountId.value && accounts.value.length) { + currentAccountId.value = accounts.value[0].accountId } } - const setCurrentAccount = (data: MailAccount) => { - currentAccountId.value = data.accountId - currentAccountIdQuery.value = data?.accountId - } - - const updateAccountField = ({ - id, - field, - value - }: { - id: T['accountId'] - field: keyof T - value: T[keyof T] - }) => { - const account = unref(accounts).find((account) => id === account.accountId) as T - if (account) { - account[field] = value - } - } - - const reset = () => { - accounts.value = [] - currentAccountId.value = null - currentAccountIdQuery.value = null + const setCurrentAccount = (account: MailAccount | null) => { + currentAccountId.value = account?.accountId ?? '' } return { accounts, + currentAccountId, currentAccount, - updateAccountField, setAccounts, - upsertAccount, - removeAccounts, - setCurrentAccount, - reset + setCurrentAccount } }) - -export type AccountsStore = ReturnType diff --git a/packages/web-app-mail/src/composables/piniaStores/helpers.ts b/packages/web-app-mail/src/composables/piniaStores/helpers.ts new file mode 100644 index 0000000000..d4738f0daf --- /dev/null +++ b/packages/web-app-mail/src/composables/piniaStores/helpers.ts @@ -0,0 +1,20 @@ +import { computed, type Ref } from 'vue' +import { useRouteQuery } from '@opencloud-eu/web-pkg/src' + +export type RouteQueryValue = string | string[] | null | undefined + +export const toSingleString = (v: RouteQueryValue): string => { + if (Array.isArray(v)) return v[0] ?? '' + return v ?? '' +} + +export const useRouteQueryId = (queryParamName: string) => { + const query = useRouteQuery(queryParamName) as unknown as Ref + + return computed({ + get: () => toSingleString(query.value), + set: (value: string) => { + query.value = value || undefined + } + }) +} diff --git a/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts b/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts index b512825158..0dd16a40e4 100644 --- a/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts +++ b/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts @@ -1,78 +1,46 @@ import { defineStore } from 'pinia' -import { computed, ref, unref } from 'vue' -import { Mailbox } from '../../types' -import { useRouteQuery } from '@opencloud-eu/web-pkg/src' - -export const useMailboxesStore = defineStore('mailboxes', () => { - const currentMailboxIdQuery = useRouteQuery('mailboxId') +import { computed, ref } from 'vue' +import type { Mailbox } from '../../types' +import { useRouteQueryId } from './helpers' +export const useMailboxesStore = defineStore('mail-mailboxes', () => { const mailboxes = ref([]) - const currentMailboxId = ref() - const currentMailbox = computed(() => - unref(mailboxes).find((mailbox) => mailbox.id === unref(currentMailboxId)) - ) + const currentMailboxId = useRouteQueryId('mailboxId') - const setMailboxes = (data: Mailbox[]) => { - mailboxes.value = data - } + const currentMailbox = computed(() => { + const id = currentMailboxId.value + return mailboxes.value.find((m) => m.id === id) + }) - const upsertMailbox = (data: Mailbox) => { - const existing = unref(mailboxes).find(({ id }) => id === data.id) - if (existing) { - Object.assign(existing, data) - return + const draftsMailboxId = computed(() => { + const byRole = mailboxes.value.find((m: any) => m.role === 'drafts')?.id + if (byRole) { + return byRole } - unref(mailboxes).push(data) - } - - const removeMailboxes = (values: Mailbox[]) => { - mailboxes.value = unref(mailboxes).filter( - (mailbox) => !values.find(({ id }) => id === mailbox.id) - ) - if (values.some((v) => v.id === unref(currentMailboxId))) { - currentMailboxId.value = null - currentMailboxIdQuery.value = null - } - } + const byName = mailboxes.value.find((m) => (m.name ?? '').toLowerCase() === 'drafts')?.id + return byName ?? '' + }) - const setCurrentMailbox = (data: Mailbox) => { - currentMailboxId.value = data?.id - currentMailboxIdQuery.value = data?.id - } + const setMailboxes = (list: Mailbox[]) => { + mailboxes.value = list ?? [] - const updateMailboxField = ({ - id, - field, - value - }: { - id: T['id'] - field: keyof T - value: T[keyof T] - }) => { - const mailbox = unref(mailboxes).find((mailbox) => id === mailbox.id) as T - if (mailbox) { - mailbox[field] = value + if (!currentMailboxId.value && mailboxes.value.length) { + currentMailboxId.value = mailboxes.value[0].id } } - const reset = () => { - mailboxes.value = [] - currentMailboxId.value = null - currentMailboxIdQuery.value = null + const setCurrentMailbox = (mailbox: Mailbox | null) => { + currentMailboxId.value = mailbox?.id ?? '' } return { mailboxes, + currentMailboxId, currentMailbox, - updateMailboxField, + draftsMailboxId, setMailboxes, - upsertMailbox, - removeMailboxes, - setCurrentMailbox, - reset + setCurrentMailbox } }) - -export type MailboxesStore = ReturnType diff --git a/packages/web-app-mail/src/composables/piniaStores/mails.ts b/packages/web-app-mail/src/composables/piniaStores/mails.ts index cdedee8af1..497f66f4ad 100644 --- a/packages/web-app-mail/src/composables/piniaStores/mails.ts +++ b/packages/web-app-mail/src/composables/piniaStores/mails.ts @@ -1,74 +1,59 @@ import { defineStore } from 'pinia' -import { computed, ref, unref } from 'vue' -import { Mail } from '../../types' -import { useRouteQuery } from '@opencloud-eu/web-pkg/src' - -export const useMailsStore = defineStore('mails', () => { - const currentMailIdQuery = useRouteQuery('mailId') +import { computed, ref } from 'vue' +import type { Mail } from '../../types' +import { useRouteQueryId } from './helpers' +export const useMailsStore = defineStore('mail-mails', () => { const mails = ref([]) - const currentMailId = ref() - - const currentMail = computed(() => unref(mails).find((mail) => mail.id === unref(currentMailId))) - const setMails = (data: Mail[]) => { - mails.value = data - } + const currentMailId = useRouteQueryId('mailId') - const upsertMail = (data: Mail) => { - const existing = unref(mails).find(({ id }) => id === data.id) - if (existing) { - Object.assign(existing, data) - return - } - unref(mails).push(data) - } + const currentMail = computed(() => { + const id = currentMailId.value + return mails.value.find((m) => m.id === id) + }) - const removeMails = (values: Mail[]) => { - mails.value = unref(mails).filter((mail) => !values.find(({ id }) => id === mail.id)) + const setMails = (list: Mail[]) => { + mails.value = list ?? [] - if (values.some((v) => v.accountId === unref(currentMailId))) { - currentMailId.value = null - currentMailIdQuery.value = null + if (currentMailId.value && !currentMail.value) { + currentMailId.value = '' } } - const setCurrentMail = (data: Mail) => { - currentMailId.value = data?.id - currentMailIdQuery.value = data?.id + const upsertMail = (mail: Mail) => { + const idx = mails.value.findIndex((m) => m.id === mail.id) + if (idx === -1) { + mails.value.unshift(mail) + } else { + mails.value[idx] = { ...mails.value[idx], ...mail } + } } - const updateMailField = ({ - id, - field, - value - }: { - id: T['id'] - field: keyof T - value: T[keyof T] - }) => { - const mail = unref(mails).find((mail) => id === mail.id) as T - if (mail) { - mail[field] = value + const setCurrentMail = (mail: Mail | null) => { + if (!mail) { + currentMailId.value = '' + return } + upsertMail(mail) + currentMailId.value = mail.id } - const reset = () => { - mails.value = [] - currentMailId.value = null - currentMailIdQuery.value = null + const updateMailField = (opts: { id: string; field: keyof Mail; value: any }) => { + const idx = mails.value.findIndex((m) => m.id === opts.id) + if (idx === -1) { + return + } + mails.value[idx] = { ...mails.value[idx], [opts.field]: opts.value } } return { mails, + currentMailId, currentMail, - updateMailField, setMails, upsertMail, - removeMails, setCurrentMail, - reset + updateMailField } }) - -export type MailsStore = ReturnType diff --git a/packages/web-app-mail/src/helpers/mailDraftConnector.ts b/packages/web-app-mail/src/helpers/mailDraftConnector.ts new file mode 100644 index 0000000000..164c7e7328 --- /dev/null +++ b/packages/web-app-mail/src/helpers/mailDraftConnector.ts @@ -0,0 +1,42 @@ +import type { + DraftEmailPayload, + DraftEmailResponse, + MailDraftApi +} from '../composables/useSaveAsDraft' + +type HttpLike = { + post: (url: string, body: unknown) => Promise<{ data?: T } | T> + put: (url: string, body: unknown) => Promise<{ data?: T } | T> + delete?: (url: string) => Promise<{ data?: T } | T | void> +} + +const unwrapData = (res: any): T => { + if (res && typeof res === 'object' && 'data' in res) { + return res.data as T + } + return res as T +} + +export function createMailDraftConnector(http: HttpLike, groupwareUrl: string): MailDraftApi { + const base = (groupwareUrl ?? '').replace(/\/+$/, '') + const emailsUrl = (accountId: string) => `${base}/accounts/${accountId}/emails` + + return { + async createDraft(accountId: string, payload: DraftEmailPayload) { + const res = await http.post(emailsUrl(accountId), payload) + return unwrapData(res) + }, + + async replaceDraft(accountId: string, emailId: string, payload: DraftEmailPayload) { + const res = await http.put(`${emailsUrl(accountId)}/${emailId}`, payload) + return unwrapData(res) + }, + + async deleteDraft(accountId: string, emailId: string) { + if (!http.delete) { + return + } + await http.delete(`${emailsUrl(accountId)}/${emailId}`) + } + } +} From 8b790a8f1d91bcde2d7b3e260d877e98a65a0c42 Mon Sep 17 00:00:00 2001 From: tammi-23 Date: Tue, 10 Feb 2026 08:56:05 +0100 Subject: [PATCH 02/15] wip: only adding name --- .../src/components/MailComposeForm.vue | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/web-app-mail/src/components/MailComposeForm.vue b/packages/web-app-mail/src/components/MailComposeForm.vue index 8481c02b72..04e8a1ce72 100644 --- a/packages/web-app-mail/src/components/MailComposeForm.vue +++ b/packages/web-app-mail/src/components/MailComposeForm.vue @@ -65,7 +65,7 @@ diff --git a/packages/web-app-mail/src/components/MailboxTree.vue b/packages/web-app-mail/src/components/MailboxTree.vue index 0af52e99d9..0275758f2d 100644 --- a/packages/web-app-mail/src/components/MailboxTree.vue +++ b/packages/web-app-mail/src/components/MailboxTree.vue @@ -83,14 +83,8 @@ const { loadMails } = useLoadMails() const { isLoading } = useLoadMailboxes() const onSelectMailbox = async (mailbox: Mailbox) => { - const accountId = unref(currentAccount)?.accountId - if (!accountId) { - return - } - setCurrentMailbox(mailbox) setCurrentMail(null) - - await loadMails(accountId, mailbox.id) + await loadMails(unref(currentAccount).accountId, mailbox.id) } diff --git a/packages/web-app-mail/src/composables/piniaStores/accounts.ts b/packages/web-app-mail/src/composables/piniaStores/accounts.ts index 584c57547e..159a527522 100644 --- a/packages/web-app-mail/src/composables/piniaStores/accounts.ts +++ b/packages/web-app-mail/src/composables/piniaStores/accounts.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import { computed, ref } from 'vue' import type { MailAccount } from '../../types' -import { useRouteQueryId } from './helpers' +import { useRouteQueryId } from '../useRouterQueryId' export const useAccountsStore = defineStore('mail-accounts', () => { const accounts = ref([]) @@ -15,17 +15,6 @@ export const useAccountsStore = defineStore('mail-accounts', () => { const setAccounts = (list: MailAccount[]) => { accounts.value = list ?? [] - - const hasAccounts = accounts.value.length > 0 - const hasValidCurrent = !!currentAccount.value - - if (!hasValidCurrent) { - if (hasAccounts) { - currentAccountId.value = accounts.value[0].accountId - } else { - currentAccountId.value = '' - } - } } const setCurrentAccount = (account: MailAccount | null) => { diff --git a/packages/web-app-mail/src/composables/piniaStores/helpers.ts b/packages/web-app-mail/src/composables/piniaStores/helpers.ts deleted file mode 100644 index d719a3496d..0000000000 --- a/packages/web-app-mail/src/composables/piniaStores/helpers.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { computed, type Ref } from 'vue' -import { useRouteQuery } from '@opencloud-eu/web-pkg/src' - -export type RouteQueryValue = string | string[] | null | undefined - -export const toSingleString = (v: RouteQueryValue): string => { - if (Array.isArray(v)) return v[0] ?? '' - return v ?? '' -} - -export const useRouteQueryId = (queryParamName: string) => { - const query = useRouteQuery(queryParamName) as unknown as Ref - - return computed({ - get: () => toSingleString(query.value), - set: (value: string) => { - query.value = value || null - } - }) -} diff --git a/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts b/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts index 97230e78f4..66fc45c273 100644 --- a/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts +++ b/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import { computed, ref } from 'vue' import type { Mailbox } from '../../types' -import { useRouteQueryId } from './helpers' +import { useRouteQueryId } from '../useRouterQueryId' export const useMailboxesStore = defineStore('mail-mailboxes', () => { const mailboxes = ref([]) @@ -25,12 +25,6 @@ export const useMailboxesStore = defineStore('mail-mailboxes', () => { const setMailboxes = (list: Mailbox[]) => { mailboxes.value = list ?? [] - - if (!currentMailboxId.value && mailboxes.value.length) { - currentMailboxId.value = mailboxes.value[0].id - } else if (currentMailboxId.value && !currentMailbox.value) { - currentMailboxId.value = mailboxes.value[0]?.id ?? '' - } } const setCurrentMailbox = (mailbox: Mailbox | null) => { diff --git a/packages/web-app-mail/src/composables/piniaStores/mails.ts b/packages/web-app-mail/src/composables/piniaStores/mails.ts index 2574460de2..26e6b348d2 100644 --- a/packages/web-app-mail/src/composables/piniaStores/mails.ts +++ b/packages/web-app-mail/src/composables/piniaStores/mails.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import { computed, ref } from 'vue' import type { Mail } from '../../types' -import { useRouteQueryId } from './helpers' +import { useRouteQueryId } from '../useRouterQueryId' export const useMailsStore = defineStore('mails', () => { const mails = ref([]) @@ -15,10 +15,6 @@ export const useMailsStore = defineStore('mails', () => { const setMails = (list: Mail[]) => { mails.value = list ?? [] - - if (currentMailId.value && !currentMail.value) { - currentMailId.value = '' - } } const upsertMail = (mail: Mail) => { diff --git a/packages/web-app-mail/src/composables/useRouterQueryId.ts b/packages/web-app-mail/src/composables/useRouterQueryId.ts new file mode 100644 index 0000000000..39719107c6 --- /dev/null +++ b/packages/web-app-mail/src/composables/useRouterQueryId.ts @@ -0,0 +1,13 @@ +import { computed } from 'vue' +import { useRouteQuery, queryItemAsString } from '@opencloud-eu/web-pkg' + +export const useRouteQueryId = (queryParamName: string) => { + const query = useRouteQuery(queryParamName) + + return computed({ + get: () => queryItemAsString(query.value), + set: (value: string) => { + query.value = value || null + } + }) +} diff --git a/packages/web-app-mail/src/helpers/mailDraftConnector.ts b/packages/web-app-mail/src/helpers/mailDraftConnector.ts deleted file mode 100644 index ea4fa2fc30..0000000000 --- a/packages/web-app-mail/src/helpers/mailDraftConnector.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { - DraftEmailPayload, - DraftEmailResponse, - MailDraftApi -} from '../composables/useSaveAsDraft' - -type HttpLike = { - post: (url: string, body: unknown) => Promise<{ data?: T } | T> - put: (url: string, body: unknown) => Promise<{ data?: T } | T> - delete?: (url: string) => Promise<{ data?: T } | T | void> -} - -const unwrapData = (res: any): T => { - if (res && typeof res === 'object' && 'data' in res) { - return res.data as T - } - return res as T -} - -const buildPath = (...segments: string[]): string => { - return segments.map((segment) => encodeURIComponent(segment)).join('/') -} - -export function createMailDraftConnector(http: HttpLike, groupwareUrl: string): MailDraftApi { - const base = (groupwareUrl ?? '').replace(/\/+$/, '') - - return { - async createDraft(accountId: string, payload: DraftEmailPayload) { - const url = `${base}/accounts/${buildPath(accountId)}/emails` - const res = await http.post(url, payload) - return unwrapData(res) - }, - - async replaceDraft(accountId: string, emailId: string, payload: DraftEmailPayload) { - const url = `${base}/accounts/${buildPath(accountId)}/emails/${buildPath(emailId)}` - const res = await http.put(url, payload) - return unwrapData(res) - }, - - async deleteDraft(accountId: string, emailId: string) { - if (!http.delete) { - return - } - const url = `${base}/accounts/${buildPath(accountId)}/emails/${buildPath(emailId)}` - await http.delete(url) - } - } -} From 106af7ca8ad76ea7284681662a646f5d9dbf334d Mon Sep 17 00:00:00 2001 From: tammi-23 Date: Tue, 24 Feb 2026 11:39:27 +0100 Subject: [PATCH 07/15] wip: rolled back Pinia Stores --- .../src/composables/piniaStores/accounts.ts | 77 +++++++++++++---- .../src/composables/piniaStores/mailboxes.ts | 82 +++++++++++++------ .../src/composables/piniaStores/mails.ts | 79 +++++++++++------- 3 files changed, 170 insertions(+), 68 deletions(-) diff --git a/packages/web-app-mail/src/composables/piniaStores/accounts.ts b/packages/web-app-mail/src/composables/piniaStores/accounts.ts index 159a527522..ff160298d2 100644 --- a/packages/web-app-mail/src/composables/piniaStores/accounts.ts +++ b/packages/web-app-mail/src/composables/piniaStores/accounts.ts @@ -1,31 +1,78 @@ import { defineStore } from 'pinia' -import { computed, ref } from 'vue' -import type { MailAccount } from '../../types' -import { useRouteQueryId } from '../useRouterQueryId' +import { computed, ref, unref } from 'vue' +import { MailAccount } from '../../types' +import { useRouteQuery } from '@opencloud-eu/web-pkg/src' + +export const useAccountsStore = defineStore('accounts', () => { + const currentAccountIdQuery = useRouteQuery('accountId') -export const useAccountsStore = defineStore('mail-accounts', () => { const accounts = ref([]) + const currentAccountId = ref() + + const currentAccount = computed(() => + unref(accounts).find((account) => account.accountId === unref(currentAccountId)) + ) + + const setAccounts = (data: MailAccount[]) => { + accounts.value = data + } + + const upsertAccount = (data: MailAccount) => { + const existing = unref(accounts).find(({ accountId }) => accountId === data.accountId) + if (existing) { + Object.assign(existing, data) + return + } + unref(accounts).push(data) + } - const currentAccountId = useRouteQueryId('accountId') + const removeAccounts = (values: MailAccount[]) => { + accounts.value = unref(accounts).filter( + (account) => !values.find(({ accountId }) => accountId === account.accountId) + ) - const currentAccount = computed(() => { - const id = currentAccountId.value - return accounts.value.find((a) => a.accountId === id) - }) + if (values.some((v) => v.accountId === unref(currentAccountId))) { + currentAccountId.value = null + currentAccountIdQuery.value = null + } + } - const setAccounts = (list: MailAccount[]) => { - accounts.value = list ?? [] + const setCurrentAccount = (data: MailAccount) => { + currentAccountId.value = data.accountId + currentAccountIdQuery.value = data?.accountId } - const setCurrentAccount = (account: MailAccount | null) => { - currentAccountId.value = account?.accountId ?? '' + const updateAccountField = ({ + id, + field, + value + }: { + id: T['accountId'] + field: keyof T + value: T[keyof T] + }) => { + const account = unref(accounts).find((account) => id === account.accountId) as T + if (account) { + account[field] = value + } + } + + const reset = () => { + accounts.value = [] + currentAccountId.value = null + currentAccountIdQuery.value = null } return { accounts, - currentAccountId, currentAccount, + updateAccountField, setAccounts, - setCurrentAccount + upsertAccount, + removeAccounts, + setCurrentAccount, + reset } }) + +export type AccountsStore = ReturnType diff --git a/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts b/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts index 66fc45c273..b512825158 100644 --- a/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts +++ b/packages/web-app-mail/src/composables/piniaStores/mailboxes.ts @@ -1,42 +1,78 @@ import { defineStore } from 'pinia' -import { computed, ref } from 'vue' -import type { Mailbox } from '../../types' -import { useRouteQueryId } from '../useRouterQueryId' +import { computed, ref, unref } from 'vue' +import { Mailbox } from '../../types' +import { useRouteQuery } from '@opencloud-eu/web-pkg/src' + +export const useMailboxesStore = defineStore('mailboxes', () => { + const currentMailboxIdQuery = useRouteQuery('mailboxId') -export const useMailboxesStore = defineStore('mail-mailboxes', () => { const mailboxes = ref([]) + const currentMailboxId = ref() + + const currentMailbox = computed(() => + unref(mailboxes).find((mailbox) => mailbox.id === unref(currentMailboxId)) + ) - const currentMailboxId = useRouteQueryId('mailboxId') + const setMailboxes = (data: Mailbox[]) => { + mailboxes.value = data + } + + const upsertMailbox = (data: Mailbox) => { + const existing = unref(mailboxes).find(({ id }) => id === data.id) + if (existing) { + Object.assign(existing, data) + return + } + unref(mailboxes).push(data) + } - const currentMailbox = computed(() => { - const id = currentMailboxId.value - return mailboxes.value.find((m) => m.id === id) - }) + const removeMailboxes = (values: Mailbox[]) => { + mailboxes.value = unref(mailboxes).filter( + (mailbox) => !values.find(({ id }) => id === mailbox.id) + ) - const draftsMailboxId = computed(() => { - const byRole = mailboxes.value.find((m: Mailbox & { role?: string }) => m.role === 'drafts')?.id - if (byRole) { - return byRole + if (values.some((v) => v.id === unref(currentMailboxId))) { + currentMailboxId.value = null + currentMailboxIdQuery.value = null } + } - const byName = mailboxes.value.find((m) => (m.name ?? '').toLowerCase() === 'drafts')?.id - return byName ?? '' - }) + const setCurrentMailbox = (data: Mailbox) => { + currentMailboxId.value = data?.id + currentMailboxIdQuery.value = data?.id + } - const setMailboxes = (list: Mailbox[]) => { - mailboxes.value = list ?? [] + const updateMailboxField = ({ + id, + field, + value + }: { + id: T['id'] + field: keyof T + value: T[keyof T] + }) => { + const mailbox = unref(mailboxes).find((mailbox) => id === mailbox.id) as T + if (mailbox) { + mailbox[field] = value + } } - const setCurrentMailbox = (mailbox: Mailbox | null) => { - currentMailboxId.value = mailbox?.id ?? '' + const reset = () => { + mailboxes.value = [] + currentMailboxId.value = null + currentMailboxIdQuery.value = null } return { mailboxes, - currentMailboxId, currentMailbox, - draftsMailboxId, + updateMailboxField, setMailboxes, - setCurrentMailbox + upsertMailbox, + removeMailboxes, + setCurrentMailbox, + reset } }) + +export type MailboxesStore = ReturnType diff --git a/packages/web-app-mail/src/composables/piniaStores/mails.ts b/packages/web-app-mail/src/composables/piniaStores/mails.ts index 26e6b348d2..cdedee8af1 100644 --- a/packages/web-app-mail/src/composables/piniaStores/mails.ts +++ b/packages/web-app-mail/src/composables/piniaStores/mails.ts @@ -1,55 +1,74 @@ import { defineStore } from 'pinia' -import { computed, ref } from 'vue' -import type { Mail } from '../../types' -import { useRouteQueryId } from '../useRouterQueryId' +import { computed, ref, unref } from 'vue' +import { Mail } from '../../types' +import { useRouteQuery } from '@opencloud-eu/web-pkg/src' export const useMailsStore = defineStore('mails', () => { - const mails = ref([]) + const currentMailIdQuery = useRouteQuery('mailId') - const currentMailId = useRouteQueryId('mailId') + const mails = ref([]) + const currentMailId = ref() - const currentMail = computed(() => { - const id = currentMailId.value - return mails.value.find((m) => m.id === id) - }) + const currentMail = computed(() => unref(mails).find((mail) => mail.id === unref(currentMailId))) - const setMails = (list: Mail[]) => { - mails.value = list ?? [] + const setMails = (data: Mail[]) => { + mails.value = data } - const upsertMail = (mail: Mail) => { - const idx = mails.value.findIndex((m) => m.id === mail.id) - if (idx === -1) { - mails.value.unshift(mail) - } else { - mails.value[idx] = { ...mails.value[idx], ...mail } + const upsertMail = (data: Mail) => { + const existing = unref(mails).find(({ id }) => id === data.id) + if (existing) { + Object.assign(existing, data) + return } + unref(mails).push(data) } - const setCurrentMail = (mail: Mail | null) => { - if (!mail) { - currentMailId.value = '' - return + const removeMails = (values: Mail[]) => { + mails.value = unref(mails).filter((mail) => !values.find(({ id }) => id === mail.id)) + + if (values.some((v) => v.accountId === unref(currentMailId))) { + currentMailId.value = null + currentMailIdQuery.value = null } - upsertMail(mail) - currentMailId.value = mail.id } - const updateMailField = (opts: { id: string; field: keyof Mail; value: any }) => { - const idx = mails.value.findIndex((m) => m.id === opts.id) - if (idx === -1) { - return + const setCurrentMail = (data: Mail) => { + currentMailId.value = data?.id + currentMailIdQuery.value = data?.id + } + + const updateMailField = ({ + id, + field, + value + }: { + id: T['id'] + field: keyof T + value: T[keyof T] + }) => { + const mail = unref(mails).find((mail) => id === mail.id) as T + if (mail) { + mail[field] = value } - mails.value[idx] = { ...mails.value[idx], [opts.field]: opts.value } + } + + const reset = () => { + mails.value = [] + currentMailId.value = null + currentMailIdQuery.value = null } return { mails, - currentMailId, currentMail, + updateMailField, setMails, upsertMail, + removeMails, setCurrentMail, - updateMailField + reset } }) + +export type MailsStore = ReturnType From 96fa9bed41b315f2588992fb2b68cfc5d877520b Mon Sep 17 00:00:00 2001 From: tammi-23 Date: Thu, 26 Feb 2026 14:59:45 +0100 Subject: [PATCH 08/15] wip: review adjustments --- .../src/components/MailLeaveModal.vue | 43 +++++++------------ 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/packages/web-app-mail/src/components/MailLeaveModal.vue b/packages/web-app-mail/src/components/MailLeaveModal.vue index 500975a873..e50e1dec06 100644 --- a/packages/web-app-mail/src/components/MailLeaveModal.vue +++ b/packages/web-app-mail/src/components/MailLeaveModal.vue @@ -2,40 +2,27 @@ From 1ebb7cb95849ca60167150e8ddd7973ed4c165d4 Mon Sep 17 00:00:00 2001 From: tammi-23 Date: Tue, 3 Mar 2026 13:45:28 +0100 Subject: [PATCH 09/15] delete composable --- .../src/composables/useRouterQueryId.ts | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 packages/web-app-mail/src/composables/useRouterQueryId.ts diff --git a/packages/web-app-mail/src/composables/useRouterQueryId.ts b/packages/web-app-mail/src/composables/useRouterQueryId.ts deleted file mode 100644 index 39719107c6..0000000000 --- a/packages/web-app-mail/src/composables/useRouterQueryId.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { computed } from 'vue' -import { useRouteQuery, queryItemAsString } from '@opencloud-eu/web-pkg' - -export const useRouteQueryId = (queryParamName: string) => { - const query = useRouteQuery(queryParamName) - - return computed({ - get: () => queryItemAsString(query.value), - set: (value: string) => { - query.value = value || null - } - }) -} From 2ccda4c0b60141cc4efb3cad9ac5bf17c031a38b Mon Sep 17 00:00:00 2001 From: tammi-23 Date: Wed, 11 Mar 2026 14:30:32 +0100 Subject: [PATCH 10/15] feat: add mailactionbar with edit draft --- .../src/components/MailActionBar.vue | 23 ++++++ .../src/components/MailDetails.vue | 12 +++ .../web-app-mail/src/components/MailList.vue | 24 +++--- .../src/components/MailWidget.vue | 73 ++++++++++++++++++- .../src/composables/useMailCompose.ts | 30 ++++++++ 5 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 packages/web-app-mail/src/components/MailActionBar.vue create mode 100644 packages/web-app-mail/src/composables/useMailCompose.ts diff --git a/packages/web-app-mail/src/components/MailActionBar.vue b/packages/web-app-mail/src/components/MailActionBar.vue new file mode 100644 index 0000000000..c8380aa6da --- /dev/null +++ b/packages/web-app-mail/src/components/MailActionBar.vue @@ -0,0 +1,23 @@ + + + diff --git a/packages/web-app-mail/src/components/MailDetails.vue b/packages/web-app-mail/src/components/MailDetails.vue index a569b55118..5acd4e85f2 100644 --- a/packages/web-app-mail/src/components/MailDetails.vue +++ b/packages/web-app-mail/src/components/MailDetails.vue @@ -16,6 +16,7 @@ > +

@@ -59,6 +60,8 @@ import MailAttachmentList from './MailAttachmentList.vue' import MailIndicators from './MailIndicators.vue' import { AppLoadingSpinner } from '@opencloud-eu/web-pkg' import MailAppointmentList from './MailAppointmentList.vue' +import MailActionBar from './MailActionBar.vue' +import { useMailCompose } from '../composables/useMailCompose' import { useLoadMail } from '../composables/useLoadMail' import { useAccountsStore } from '../composables/piniaStores/accounts' import { useMailsStore } from '../composables/piniaStores/mails' @@ -71,6 +74,7 @@ const mailsStore = useMailsStore() const { currentAccount } = storeToRefs(accountsStore) const { currentMail } = storeToRefs(mailsStore) const { setCurrentMail } = mailsStore +const { openDraftCompose } = useMailCompose() const fromEmail = computed(() => { return unref(currentMail)?.from[0]?.email || unref(currentMail)?.sender[0]?.email @@ -99,6 +103,14 @@ const appointments = computed(() => { return unref(currentMail).attachments?.filter((attachment) => attachment.type === 'text/calendar') }) +const onEditDraft = () => { + if (!unref(currentMail)) { + return + } + + openDraftCompose(unref(currentMail)) +} + const onNavigateBack = () => { setCurrentMail(null) } diff --git a/packages/web-app-mail/src/components/MailList.vue b/packages/web-app-mail/src/components/MailList.vue index 9a1e253955..dfc95628c7 100644 --- a/packages/web-app-mail/src/components/MailList.vue +++ b/packages/web-app-mail/src/components/MailList.vue @@ -5,7 +5,7 @@ class="md:hidden" mode="action" :aria-label="$gettext('Write new Email')" - @click="openCompose" + @click="openNewCompose" /> +