From ce8968edfcef32ff835604ee7f4cf369d651b21d Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Thu, 3 Sep 2020 20:43:39 +0200 Subject: Make it possible to view messages of the same thread directly Signed-off-by: Christoph Wurst --- css/mail.scss | 13 -- css/mobile.scss | 2 +- src/components/AddressList.vue | 3 +- src/components/MailboxThread.vue | 25 +-- src/components/Message.vue | 99 ++++++++++ src/components/Thread.vue | 351 +++++++-------------------------- src/components/ThreadEnvelope.vue | 325 ++++++++++++++++++++++++++++++ src/components/ThreadMessage.vue | 90 --------- src/store/actions.js | 38 ++-- src/store/getters.js | 6 +- src/store/mutations.js | 14 +- src/tests/unit/store/getters.spec.js | 61 ++++++ src/tests/unit/store/mutations.spec.js | 74 +++++++ 13 files changed, 674 insertions(+), 427 deletions(-) create mode 100644 src/components/Message.vue create mode 100644 src/components/ThreadEnvelope.vue delete mode 100644 src/components/ThreadMessage.vue diff --git a/css/mail.scss b/css/mail.scss index 529039edb..70a3d659e 100755 --- a/css/mail.scss +++ b/css/mail.scss @@ -280,19 +280,6 @@ vertical-align: text-top; } -#mail-message-header { - height: 90px; - z-index: 100; - position: fixed; // ie fallback - position: -webkit-sticky; // ios/safari fallback - position: sticky; - top: $header-height; - background: -webkit-linear-gradient(var(--color-main-background), var(--color-main-background) 80%, rgba(255,255,255,0)); - background: -o-linear-gradient(var(--color-main-background), var(--color-main-background) 80%, rgba(255,255,255,0)); - background: -moz-linear-gradient(var(--color-main-background), var(--color-main-background) 80%, rgba(255,255,255,0)); - background: linear-gradient(var(--color-main-background), var(--color-main-background) 80%, rgba(255,255,255,0)); -} - .transparency { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; opacity: .6; diff --git a/css/mobile.scss b/css/mobile.scss index 5699a9916..8866e76da 100644 --- a/css/mobile.scss +++ b/css/mobile.scss @@ -50,7 +50,7 @@ } /* reply-forward actions align to the far right */ - #mail-message-header #mail-message-actions { + #mail-thread-header .mail-message-actions { margin-right: 5px; } } diff --git a/src/components/AddressList.vue b/src/components/AddressList.vue index d0d08fe84..963dfe2b9 100644 --- a/src/components/AddressList.vue +++ b/src/components/AddressList.vue @@ -2,7 +2,8 @@ diff --git a/src/components/MailboxThread.vue b/src/components/MailboxThread.vue index ac74a34f5..aed78fb96 100644 --- a/src/components/MailboxThread.vue +++ b/src/components/MailboxThread.vue @@ -1,12 +1,12 @@ - - + + @@ -76,7 +76,6 @@ import logger from '../logger' import Mailbox from './Mailbox' import NewMessageDetail from './NewMessageDetail' import NoMessageSelected from './NoMessageSelected' -import { normalizedEnvelopeListId } from '../store/normalization' import Thread from './Thread' import { UNIFIED_ACCOUNT_ID, UNIFIED_INBOX_ID } from '../store/constants' @@ -131,19 +130,11 @@ export default { unifiedInbox() { return this.$store.getters.getMailbox(UNIFIED_INBOX_ID) }, - hasMessages() { - // it actually should be `return this.$store.getters.getEnvelopes(this.account.id, this.mailbox.databaseId).length > 0` - // but for some reason Vue doesn't track the dependencies on reactive data then and messages in submailboxes can't - // be opened then - const list = this.mailbox.envelopeLists[normalizedEnvelopeListId(this.searchQuery)] - - if (list === undefined) { - return false - } - return list.length > 0 + hasEnvelopes() { + return this.$store.getters.getEnvelopes(this.mailbox.databaseId, this.searchQuery).length > 0 }, - showMessage() { - return (this.mailbox.isPriorityInbox === true || this.hasMessages) && this.$route.name === 'message' + showThread() { + return (this.mailbox.isPriorityInbox === true || this.hasEnvelopes) && this.$route.name === 'message' }, query() { if (this.$route.params.filter === 'starred') { diff --git a/src/components/Message.vue b/src/components/Message.vue new file mode 100644 index 000000000..4feea802d --- /dev/null +++ b/src/components/Message.vue @@ -0,0 +1,99 @@ + + + + + + + diff --git a/src/components/Thread.vue b/src/components/Thread.vue index 2679cdace..c905b0c39 100644 --- a/src/components/Thread.vue +++ b/src/components/Thread.vue @@ -1,188 +1,71 @@ - diff --git a/src/components/ThreadMessage.vue b/src/components/ThreadMessage.vue deleted file mode 100644 index 66966dd62..000000000 --- a/src/components/ThreadMessage.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - diff --git a/src/store/actions.js b/src/store/actions.js index 76da406e6..4a5c441a4 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -227,24 +227,23 @@ export default { updated, }) }, - fetchEnvelope({ commit, getters }, id) { + async fetchEnvelope({ commit, getters }, id) { const cached = getters.getEnvelope(id) if (cached) { logger.debug(`using cached value for envelope ${id}`) return cached } - return fetchEnvelope(id).then((envelope) => { - // Only commit if not undefined (not found) - if (envelope) { - commit('addEnvelope', { - envelope, - }) - } + const envelope = await fetchEnvelope(id) + // Only commit if not undefined (not found) + if (envelope) { + commit('addEnvelope', { + envelope, + }) + } - // Always use the object from the store - return getters.getEnvelope(id) - }) + // Always use the object from the store + return getters.getEnvelope(id) }, fetchEnvelopes({ state, commit, getters, dispatch }, { mailboxId, query }) { const mailbox = getters.getMailbox(mailboxId) @@ -392,7 +391,6 @@ export default { }) }, syncEnvelopes({ commit, getters, dispatch }, { mailboxId, query, init = false }) { - // TODO: use mailboxId const mailbox = getters.getMailbox(mailboxId) if (mailbox.isUnified) { @@ -438,6 +436,8 @@ export default { const ids = getters.getEnvelopes(mailboxId, query).map((env) => env.databaseId) return syncEnvelopes(mailbox.accountId, mailboxId, ids, query, init) .then((syncData) => { + logger.info(`mailbox ${mailboxId} synchronized, ${syncData.newMessages.length} new, ${syncData.changedMessages.length} changed and ${syncData.vanishedMessages.length} vanished messages`) + const unifiedMailbox = getters.getUnifiedMailbox(mailbox.specialRole) syncData.newMessages.forEach((envelope) => { @@ -654,6 +654,14 @@ export default { }) }) }, + async fetchThread({ getters, commit }, id) { + const thread = await fetchThread(id) + commit('addEnvelopeThread', { + id, + thread, + }) + return thread + }, async fetchMessage({ getters, commit }, id) { const message = await fetchMessage(id) // Only commit if not undefined (not found) @@ -661,12 +669,6 @@ export default { commit('addMessage', { message, }) - - const thread = await fetchThread(message.databaseId) - commit('addMessageThread', { - message, - thread, - }) } return message }, diff --git a/src/store/getters.js b/src/store/getters.js index cad479d03..99bb27e3c 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -61,7 +61,9 @@ export const getters = { getMessage: (state) => (id) => { return state.messages[id] }, - getMessageThread: (state) => (id) => { - return sortBy(prop('dateInt'), state.messages[id]?.thread ?? []) + getEnvelopeThread: (state) => (id) => { + const thread = state.envelopes[id]?.thread ?? [] + const envelopes = thread.map(id => state.envelopes[id]) + return sortBy(prop('dateInt'), envelopes) }, } diff --git a/src/store/mutations.js b/src/store/mutations.js index 0e893d0f9..69d2e15ee 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -32,7 +32,7 @@ import { UNIFIED_ACCOUNT_ID } from './constants' const addMailboxToState = curry((state, account, mailbox) => { mailbox.accountId = account.id mailbox.mailboxes = [] - mailbox.envelopeLists = {} + Vue.set(mailbox, 'envelopeLists', {}) // Add all mailboxes (including submailboxes to state, but only toplevel to account const nameWithoutPrefix = account.personalNamespace @@ -216,8 +216,16 @@ export default { addMessage(state, { message }) { Vue.set(state.messages, message.databaseId, message) }, - addMessageThread(state, { message, thread }) { - Vue.set(message, 'thread', thread) + addEnvelopeThread(state, { id, thread }) { + // Store the envelopes, merge into any existing object if one exists + thread.map(e => { + const mailbox = state.mailboxes[e.mailboxId] + Vue.set(e, 'accountId', mailbox.accountId) + Vue.set(state.envelopes, e.databaseId, Object.assign({}, state.envelopes[e.databaseId] || {}, e)) + }) + + // Store the references + Vue.set(state.envelopes[id], 'thread', thread.map(e => e.databaseId)) }, removeMessage(state, { id }) { Vue.delete(state.messages, id) diff --git a/src/tests/unit/store/getters.spec.js b/src/tests/unit/store/getters.spec.js index b6e438f81..420ee686e 100644 --- a/src/tests/unit/store/getters.spec.js +++ b/src/tests/unit/store/getters.spec.js @@ -68,4 +68,65 @@ describe('Vuex store getters', () => { accountId: 13, }) }) + it('returns an envelope\'s empty thread', () => { + state.envelopes[1] = { + databaseId: 1, + uid: 101, + mailboxId: 13, + } + const getters = bindGetters() + + const thread = getters.getEnvelopeThread(1) + + expect(thread).to.be.empty + }) + it('returns an envelope\'s empty thread', () => { + state.envelopes[1] = { + databaseId: 1, + uid: 101, + mailboxId: 13, + thread: [ + 1, + 2, + 3, + ] + } + state.envelopes[2] = { + databaseId: 1, + uid: 101, + mailboxId: 13, + } + state.envelopes[3] = { + databaseId: 1, + uid: 101, + mailboxId: 13, + } + const getters = bindGetters() + + const thread = getters.getEnvelopeThread(1) + + expect(thread).to.not.be.empty + expect(thread).to.deep.equal([ + { + databaseId: 1, + uid: 101, + mailboxId: 13, + thread: [ + 1, + 2, + 3, + ] + }, + { + databaseId: 1, + uid: 101, + mailboxId: 13, + }, + { + databaseId: 1, + uid: 101, + mailboxId: 13, + }, + ]) + }) }) diff --git a/src/tests/unit/store/mutations.spec.js b/src/tests/unit/store/mutations.spec.js index 7d7a6eb49..73471f03e 100644 --- a/src/tests/unit/store/mutations.spec.js +++ b/src/tests/unit/store/mutations.spec.js @@ -824,4 +824,78 @@ describe('Vuex store mutations', () => { }, }) }) + + it('adds a thread', () => { + const envelope = { + databaseId: 123, + mailboxId: 27, + uid: 12345, + } + const state = { + mailboxes: { + 27: { + databaseId: 27, + accountId: 1, + }, + }, + envelopes: { + [envelope.databaseId]: envelope, + }, + } + + mutations.addEnvelopeThread(state, { + id: 123, + thread: [ + { + databaseId: 122, + mailboxId: 27, + uid: 12344, + }, + { + databaseId: 123, + mailboxId: 27, + uid: 12345, + }, + { + databaseId: 124, + mailboxId: 27, + uid: 12346, + } + ], + }) + + expect(state).to.deep.equal({ + mailboxes: { + 27: { + databaseId: 27, + accountId: 1, + }, + }, + envelopes: { + 122: { + databaseId: 122, + mailboxId: 27, + accountId: 1, + uid: 12344, + }, + 123: { + databaseId: 123, + mailboxId: 27, + accountId: 1, + uid: 12345, + thread: [ + 122, + 123, + 124, + ] + }, + 124: { + databaseId: 124, + mailboxId: 27, + accountId: 1, + uid: 12346, + }, + }, + }) + }) }) -- cgit v1.2.3