diff --git a/src/components/EnvelopeList.vue b/src/components/EnvelopeList.vue
index b83f742ad9..b530c80a5f 100644
--- a/src/components/EnvelopeList.vue
+++ b/src/components/EnvelopeList.vue
@@ -188,7 +188,6 @@ import { matchError } from '../errors/match.js'
import NoTrashMailboxConfiguredError
from '../errors/NoTrashMailboxConfiguredError.js'
import logger from '../logger.js'
-import useMainStore from '../store/mainStore.js'
export default {
name: 'EnvelopeList',
@@ -261,95 +260,12 @@ export default {
data() {
return {
- selection: [],
- showMoveModal: false,
- showTagModal: false,
- lastToggledIndex: undefined,
- defaultView: false,
- showQuickActionsSettings: false,
+
}
},
computed: {
- ...mapStores(useMainStore),
- sortOrder() {
- return this.mainStore.getPreference('sort-order', 'newest')
- },
-
- sortedEnvelops() {
- if (this.sortOrder === 'oldest') {
- return [...this.envelopes].sort((a, b) => {
- return a.dateInt < b.dateInt ? -1 : 1
- })
- }
- return [...this.envelopes]
- },
-
- selectMode() {
- // returns true when in selection mode (where the user selects several emails at once)
- return this.selection.length > 0
- },
-
- isAtLeastOneSelectedRead() {
- return this.selectedEnvelopes.some((env) => env.flags.seen === true)
- },
-
- isAtLeastOneSelectedUnread() {
- return this.selectedEnvelopes.some((env) => env.flags.seen === false)
- },
-
- isAtLeastOneSelectedImportant() {
- // returns true if at least one selected message is marked as important
- return this.selectedEnvelopes.some((env) => {
- return this.mainStore
- .getEnvelopeTags(env.databaseId)
- .some((tag) => tag.imapLabel === '$label1')
- })
- },
-
- isAtLeastOneSelectedUnimportant() {
- // returns true if at least one selected message is not marked as important
- return this.selectedEnvelopes.some((env) => {
- return !this.mainStore
- .getEnvelopeTags(env.databaseId)
- .some((tag) => tag.imapLabel === '$label1')
- })
- },
- isAtLeastOneSelectedJunk() {
- // returns true if at least one selected message is marked as junk
- return this.selectedEnvelopes.some((env) => {
- return env.flags.$junk
- })
- },
-
- isAtLeastOneSelectedNotJunk() {
- // returns true if at least one selected message is not marked as not junk
- return this.selectedEnvelopes.some((env) => {
- return !env.flags.$junk
- })
- },
-
- isAtLeastOneSelectedFavorite() {
- return this.selectedEnvelopes.some((env) => env.flags.flagged)
- },
-
- isAtLeastOneSelectedUnFavorite() {
- return this.selectedEnvelopes.some((env) => !env.flags.flagged)
- },
-
- selectedEnvelopes() {
- return this.sortedEnvelops.filter((env) => this.selection.includes(env.databaseId))
- },
-
- hasMultipleAccounts() {
- const mailboxIds = this.sortedEnvelops.map((envelope) => envelope.mailboxId)
- return Array.from(new Set(mailboxIds)).length > 1
- },
-
- listTransitionName() {
- return this.skipTransition ? 'disabled' : 'list'
- },
},
watch: {
@@ -373,239 +289,7 @@ export default {
},
methods: {
- isEnvelopeSelected(idx) {
- if (this.selection.length === 0) {
- return false
- }
-
- return this.selection.includes(idx)
- },
-
- markSelectedRead() {
- this.selectedEnvelopes.forEach((envelope) => {
- this.mainStore.toggleEnvelopeSeen({
- envelope,
- seen: true,
- })
- })
- this.unselectAll()
- },
-
- markSelectedUnread() {
- this.selectedEnvelopes.forEach((envelope) => {
- this.mainStore.toggleEnvelopeSeen({
- envelope,
- seen: false,
- })
- })
- this.unselectAll()
- },
-
- markSelectionImportant() {
- this.selectedEnvelopes.forEach((envelope) => {
- this.mainStore.markEnvelopeImportantOrUnimportant({
- envelope,
- addTag: true,
- })
- })
- this.unselectAll()
- },
- markSelectionUnimportant() {
- this.selectedEnvelopes.forEach((envelope) => {
- this.mainStore.markEnvelopeImportantOrUnimportant({
- envelope,
- addTag: false,
- })
- })
- this.unselectAll()
- },
-
- async markSelectionJunk() {
- for (const envelope of this.selectedEnvelopes) {
- if (!envelope.flags.$junk) {
- await this.mainStore.toggleEnvelopeJunk({
- envelope,
- removeEnvelope: await this.mainStore.moveEnvelopeToJunk(envelope),
- })
- }
- }
- this.unselectAll()
- },
-
- async markSelectionNotJunk() {
- for (const envelope of this.selectedEnvelopes) {
- if (envelope.flags.$junk) {
- await this.mainStore.toggleEnvelopeJunk({
- envelope,
- removeEnvelope: await this.mainStore.moveEnvelopeToJunk(envelope),
- })
- }
- }
- this.unselectAll()
- },
-
- favoriteAll() {
- const favFlag = !this.isAtLeastOneSelectedUnFavorite
- this.selectedEnvelopes.forEach((envelope) => {
- this.mainStore.markEnvelopeFavoriteOrUnfavorite({
- envelope,
- favFlag,
- })
- })
- this.unselectAll()
- },
-
- unFavoriteAll() {
- const favFlag = !this.isAtLeastOneSelectedFavorite
- this.selectedEnvelopes.forEach((envelope) => {
- this.mainStore.markEnvelopeFavoriteOrUnfavorite({
- envelope,
- favFlag,
- })
- })
- this.unselectAll()
- },
-
- async deleteAllSelected() {
- let nextEnvelopeToNavigate
- let isAllSelected
-
- if (this.selectedEnvelopes.length === this.sortedEnvelops.length) {
- isAllSelected = true
- } else {
- const indexSelectedEnvelope = this.selectedEnvelopes.findIndex((selectedEnvelope) => selectedEnvelope.databaseId === this.$route.params.threadId)
-
- // one of threads is selected
- if (indexSelectedEnvelope !== -1) {
- const lastSelectedEnvelope = this.selectedEnvelopes[this.selectedEnvelopes.length - 1]
- const diff = this.sortedEnvelops.filter((envelope) => envelope === lastSelectedEnvelope || !this.selectedEnvelopes.includes(envelope))
- const lastIndex = diff.indexOf(lastSelectedEnvelope)
- nextEnvelopeToNavigate = diff[lastIndex === 0 ? 1 : lastIndex - 1]
- }
- }
-
- await Promise.all(this.selectedEnvelopes.map(async (envelope) => {
- logger.info(`deleting thread ${envelope.threadRootId}`)
- await this.mainStore.deleteThread({
- envelope,
- })
- })).catch(async (error) => {
- showError(await matchError(error, {
- [NoTrashMailboxConfiguredError.getName()]() {
- return t('mail', 'No trash folder configured')
- },
- default(error) {
- logger.error('could not delete message', error)
- return t('mail', 'Could not delete message')
- },
- }))
- })
- if (nextEnvelopeToNavigate) {
- await this.$router.push({
- name: 'message',
- params: {
- mailboxId: this.$route.params.mailboxId,
- threadId: nextEnvelopeToNavigate.databaseId,
- },
- })
-
- // Get new messages
- await this.mainStore.fetchNextEnvelopes({
- mailboxId: this.mailbox.databaseId,
- query: this.searchQuery,
- quantity: this.selectedEnvelopes.length,
- })
- } else if (isAllSelected) {
- await this.$router.push({
- name: 'mailbox',
- params: {
- mailboxId: this.$route.params.mailboxId,
- },
- })
- }
- this.unselectAll()
- },
-
- setEnvelopeSelected(envelope, selected) {
- const alreadySelected = this.selection.includes(envelope.databaseId)
- if (selected && !alreadySelected) {
- envelope.flags.selected = true
- this.selection.push(envelope.databaseId)
- } else if (!selected && alreadySelected) {
- envelope.flags.selected = false
- this.selection.splice(this.selection.indexOf(envelope.databaseId), 1)
- }
- },
-
- onEnvelopeSelectToggle(envelope, index, selected) {
- this.lastToggledIndex = index
- this.setEnvelopeSelected(envelope, selected)
- },
-
- onEnvelopeSelectMultiple(envelope, index) {
- const lastToggledIndex = this.lastToggledIndex
- ?? this.findSelectionIndex(parseInt(this.$route.params.threadId))
- ?? undefined
- if (lastToggledIndex === undefined) {
- return
- }
-
- const start = Math.min(lastToggledIndex, index)
- const end = Math.max(lastToggledIndex, index)
- const selected = this.selection.includes(envelope.databaseId)
- for (let i = start; i <= end; i++) {
- this.setEnvelopeSelected(this.sortedEnvelops[i], !selected)
- }
- this.lastToggledIndex = index
- },
-
- unselectAll() {
- this.sortedEnvelops.forEach((env) => {
- env.flags.selected = false
- })
- this.selection = []
- },
-
- onOpenMoveModal() {
- this.showMoveModal = true
- },
-
- onOpenTagModal() {
- this.showTagModal = true
- },
-
- onCloseTagModal() {
- this.showTagModal = false
- },
-
- async forwardSelectedAsAttachment() {
- await this.mainStore.startComposerSession({
- forwardedMessages: [...this.selection],
- })
- this.unselectAll()
- },
-
- onCloseMoveModal() {
- this.showMoveModal = false
- this.unselectAll()
- },
-
- /**
- * Find the envelope list index of a given envelope's database id.
- *
- * @param {number} databaseId of the given envelope
- * @return {number|undefined} Index or undefined if not found in the envelope list
- */
- findSelectionIndex(databaseId) {
- for (const [index, envelope] of this.sortedEnvelops.entries()) {
- if (envelope.databaseId === databaseId) {
- return index
- }
- }
-
- return undefined
- },
},
}
diff --git a/src/components/MultiselectHeader.vue b/src/components/MultiselectHeader.vue
new file mode 100644
index 0000000000..abbfbc148d
--- /dev/null
+++ b/src/components/MultiselectHeader.vue
@@ -0,0 +1,521 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/store/envelopeSelectionStore.js b/src/store/envelopeSelectionStore.js
new file mode 100644
index 0000000000..3aba9a8b86
--- /dev/null
+++ b/src/store/envelopeSelectionStore.js
@@ -0,0 +1,345 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import { showError, showSuccess, showUndo } from '@nextcloud/dialogs'
+import { translate as t } from '@nextcloud/l10n'
+import { defineStore } from 'pinia'
+import Vue from 'vue'
+import logger from '../logger.js'
+import * as OutboxService from '../service/OutboxService.js'
+import { UNDO_DELAY } from './constants.js'
+import useMainStore from './mainStore.js'
+
+export default defineStore('outbox', {
+ state: () => {
+ return {
+ selection: [],
+ showMoveModal: false,
+ showTagModal: false,
+ lastToggledIndex: undefined,
+ defaultView: false,
+ showQuickActionsSettings: false,
+ mainStore: useMainStore(),
+ envelopes: [],
+ skipTransition: false,
+ searchQuery: '',
+ }
+ },
+ getters: {
+ sortOrder() {
+ return this.mainStore.getPreference('sort-order', 'newest')
+ },
+
+ sortedEnvelops() {
+ if (this.sortOrder === 'oldest') {
+ return [...this.envelopes].sort((a, b) => {
+ return a.dateInt < b.dateInt ? -1 : 1
+ })
+ }
+ return [...this.envelopes]
+ },
+
+ selectMode() {
+ // returns true when in selection mode (where the user selects several emails at once)
+ return this.selection.length > 0
+ },
+
+ isAtLeastOneSelectedRead() {
+ return this.selectedEnvelopes.some((env) => env.flags.seen === true)
+ },
+
+ isAtLeastOneSelectedUnread() {
+ return this.selectedEnvelopes.some((env) => env.flags.seen === false)
+ },
+
+ isAtLeastOneSelectedImportant() {
+ // returns true if at least one selected message is marked as important
+ return this.selectedEnvelopes.some((env) => {
+ return this.mainStore
+ .getEnvelopeTags(env.databaseId)
+ .some((tag) => tag.imapLabel === '$label1')
+ })
+ },
+
+ isAtLeastOneSelectedUnimportant() {
+ // returns true if at least one selected message is not marked as important
+ return this.selectedEnvelopes.some((env) => {
+ return !this.mainStore
+ .getEnvelopeTags(env.databaseId)
+ .some((tag) => tag.imapLabel === '$label1')
+ })
+ },
+
+ isAtLeastOneSelectedJunk() {
+ // returns true if at least one selected message is marked as junk
+ return this.selectedEnvelopes.some((env) => {
+ return env.flags.$junk
+ })
+ },
+
+ isAtLeastOneSelectedNotJunk() {
+ // returns true if at least one selected message is not marked as not junk
+ return this.selectedEnvelopes.some((env) => {
+ return !env.flags.$junk
+ })
+ },
+
+ isAtLeastOneSelectedFavorite() {
+ return this.selectedEnvelopes.some((env) => env.flags.flagged)
+ },
+
+ isAtLeastOneSelectedUnFavorite() {
+ return this.selectedEnvelopes.some((env) => !env.flags.flagged)
+ },
+
+ selectedEnvelopes() {
+ return this.sortedEnvelops.filter((env) => this.selection.includes(env.databaseId))
+ },
+
+ hasMultipleAccounts() {
+ const mailboxIds = this.sortedEnvelops.map((envelope) => envelope.mailboxId)
+ return Array.from(new Set(mailboxIds)).length > 1
+ },
+
+ listTransitionName() {
+ return this.skipTransition ? 'disabled' : 'list'
+ },
+ },
+ actions: {
+ isEnvelopeSelected(idx) {
+ if (this.selection.length === 0) {
+ return false
+ }
+
+ return this.selection.includes(idx)
+ },
+
+ markSelectedRead() {
+ this.selectedEnvelopes.forEach((envelope) => {
+ this.mainStore.toggleEnvelopeSeen({
+ envelope,
+ seen: true,
+ })
+ })
+ this.unselectAll()
+ },
+
+ markSelectedUnread() {
+ this.selectedEnvelopes.forEach((envelope) => {
+ this.mainStore.toggleEnvelopeSeen({
+ envelope,
+ seen: false,
+ })
+ })
+ this.unselectAll()
+ },
+
+ markSelectionImportant() {
+ this.selectedEnvelopes.forEach((envelope) => {
+ this.mainStore.markEnvelopeImportantOrUnimportant({
+ envelope,
+ addTag: true,
+ })
+ })
+ this.unselectAll()
+ },
+
+ markSelectionUnimportant() {
+ this.selectedEnvelopes.forEach((envelope) => {
+ this.mainStore.markEnvelopeImportantOrUnimportant({
+ envelope,
+ addTag: false,
+ })
+ })
+ this.unselectAll()
+ },
+
+ async markSelectionJunk() {
+ for (const envelope of this.selectedEnvelopes) {
+ if (!envelope.flags.$junk) {
+ await this.mainStore.toggleEnvelopeJunk({
+ envelope,
+ removeEnvelope: await this.mainStore.moveEnvelopeToJunk(envelope),
+ })
+ }
+ }
+ this.unselectAll()
+ },
+
+ async markSelectionNotJunk() {
+ for (const envelope of this.selectedEnvelopes) {
+ if (envelope.flags.$junk) {
+ await this.mainStore.toggleEnvelopeJunk({
+ envelope,
+ removeEnvelope: await this.mainStore.moveEnvelopeToJunk(envelope),
+ })
+ }
+ }
+ this.unselectAll()
+ },
+
+ favoriteAll() {
+ const favFlag = !this.isAtLeastOneSelectedUnFavorite
+ this.selectedEnvelopes.forEach((envelope) => {
+ this.mainStore.markEnvelopeFavoriteOrUnfavorite({
+ envelope,
+ favFlag,
+ })
+ })
+ this.unselectAll()
+ },
+
+ unFavoriteAll() {
+ const favFlag = !this.isAtLeastOneSelectedFavorite
+ this.selectedEnvelopes.forEach((envelope) => {
+ this.mainStore.markEnvelopeFavoriteOrUnfavorite({
+ envelope,
+ favFlag,
+ })
+ })
+ this.unselectAll()
+ },
+
+ async deleteAllSelected() {
+ let nextEnvelopeToNavigate
+ let isAllSelected
+
+ if (this.selectedEnvelopes.length === this.sortedEnvelops.length) {
+ isAllSelected = true
+ } else {
+ const indexSelectedEnvelope = this.selectedEnvelopes.findIndex((selectedEnvelope) => selectedEnvelope.databaseId === this.$route.params.threadId)
+
+ // one of threads is selected
+ if (indexSelectedEnvelope !== -1) {
+ const lastSelectedEnvelope = this.selectedEnvelopes[this.selectedEnvelopes.length - 1]
+ const diff = this.sortedEnvelops.filter((envelope) => envelope === lastSelectedEnvelope || !this.selectedEnvelopes.includes(envelope))
+ const lastIndex = diff.indexOf(lastSelectedEnvelope)
+ nextEnvelopeToNavigate = diff[lastIndex === 0 ? 1 : lastIndex - 1]
+ }
+ }
+
+ await Promise.all(this.selectedEnvelopes.map(async (envelope) => {
+ logger.info(`deleting thread ${envelope.threadRootId}`)
+ await this.mainStore.deleteThread({
+ envelope,
+ })
+ })).catch(async (error) => {
+ showError(await matchError(error, {
+ [NoTrashMailboxConfiguredError.getName()]() {
+ return t('mail', 'No trash folder configured')
+ },
+ default(error) {
+ logger.error('could not delete message', error)
+ return t('mail', 'Could not delete message')
+ },
+ }))
+ })
+ if (nextEnvelopeToNavigate) {
+ await this.$router.push({
+ name: 'message',
+ params: {
+ mailboxId: this.$route.params.mailboxId,
+ threadId: nextEnvelopeToNavigate.databaseId,
+ },
+ })
+
+ // Get new messages
+ await this.mainStore.fetchNextEnvelopes({
+ mailboxId: this.mailbox.databaseId,
+ query: this.searchQuery,
+ quantity: this.selectedEnvelopes.length,
+ })
+ } else if (isAllSelected) {
+ await this.$router.push({
+ name: 'mailbox',
+ params: {
+ mailboxId: this.$route.params.mailboxId,
+ },
+ })
+ }
+ this.unselectAll()
+ },
+
+ setEnvelopeSelected(envelope, selected) {
+ const alreadySelected = this.selection.includes(envelope.databaseId)
+ if (selected && !alreadySelected) {
+ envelope.flags.selected = true
+ this.selection.push(envelope.databaseId)
+ } else if (!selected && alreadySelected) {
+ envelope.flags.selected = false
+ this.selection.splice(this.selection.indexOf(envelope.databaseId), 1)
+ }
+ },
+
+ onEnvelopeSelectToggle(envelope, index, selected) {
+ this.lastToggledIndex = index
+ this.setEnvelopeSelected(envelope, selected)
+ },
+
+ onEnvelopeSelectMultiple(envelope, index) {
+ const lastToggledIndex = this.lastToggledIndex
+ ?? this.findSelectionIndex(parseInt(this.$route.params.threadId))
+ ?? undefined
+ if (lastToggledIndex === undefined) {
+ return
+ }
+
+ const start = Math.min(lastToggledIndex, index)
+ const end = Math.max(lastToggledIndex, index)
+ const selected = this.selection.includes(envelope.databaseId)
+ for (let i = start; i <= end; i++) {
+ this.setEnvelopeSelected(this.sortedEnvelops[i], !selected)
+ }
+ this.lastToggledIndex = index
+ },
+
+ unselectAll() {
+ this.sortedEnvelops.forEach((env) => {
+ env.flags.selected = false
+ })
+ this.selection = []
+ },
+
+ onOpenMoveModal() {
+ this.showMoveModal = true
+ },
+
+ onOpenTagModal() {
+ this.showTagModal = true
+ },
+
+ onCloseTagModal() {
+ this.showTagModal = false
+ },
+
+ async forwardSelectedAsAttachment() {
+ await this.mainStore.startComposerSession({
+ forwardedMessages: [...this.selection],
+ })
+ this.unselectAll()
+ },
+
+ onCloseMoveModal() {
+ this.showMoveModal = false
+ this.unselectAll()
+ },
+
+ /**
+ * Find the envelope list index of a given envelope's database id.
+ *
+ * @param {number} databaseId of the given envelope
+ * @return {number|undefined} Index or undefined if not found in the envelope list
+ */
+ findSelectionIndex(databaseId) {
+ for (const [index, envelope] of this.sortedEnvelops.entries()) {
+ if (envelope.databaseId === databaseId) {
+ return index
+ }
+ }
+
+ return undefined
+ },
+ },
+})