diff --git a/lib/Service/QuickActionsService.php b/lib/Service/QuickActionsService.php index 7f11e9a5db..00931c9cf7 100644 --- a/lib/Service/QuickActionsService.php +++ b/lib/Service/QuickActionsService.php @@ -21,6 +21,7 @@ class QuickActionsService { public const AVAILABLE_ACTION_STEPS = [ 'markAsSpam', 'applyTag', + 'removeTags', 'snooze', 'moveThread', 'deleteThread', diff --git a/src/components/Envelope.vue b/src/components/Envelope.vue index f10c62ba6c..2e8870f0c0 100644 --- a/src/components/Envelope.vue +++ b/src/components/Envelope.vue @@ -602,7 +602,6 @@ import useMainStore from '../store/mainStore.js' import { mailboxHasRights } from '../util/acl.js' import { flatRelativeDatetime, groupedRelativeDatetime, messageDateTime } from '../util/relativeDatetime.js' import { translateTagDisplayName } from '../util/tag.js' -import { hiddenTags } from './tags.js' export default { name: 'Envelope', @@ -852,7 +851,7 @@ export default { }, tags() { - let tags = this.mainStore.getEnvelopeTags(this.data.databaseId).filter((tag) => tag.imapLabel && tag.imapLabel !== '$label1' && !(tag.displayName.toLowerCase() in hiddenTags)) + let tags = this.mainStore.getEnvelopeExposedTags(this.data.databaseId) // Don't show follow-up tag in unified mailbox as it has its own section at the top if (this.mailbox.isUnified) { @@ -1005,7 +1004,7 @@ export default { const quickActions = this.mainStore.getQuickActions().filter((action) => action.accountId === this.data.accountId) for (const action of quickActions) { const check = action.actionSteps.every((step) => { - if (['markAsSpam', 'applyTag', 'markAsImportant', 'markAsFavorite'].includes(step.name) && !this.hasWriteAcl) { + if (['markAsSpam', 'applyTag', 'removeTags', 'markAsImportant', 'markAsFavorite'].includes(step.name) && !this.hasWriteAcl) { return false } if (['markAsRead', 'markAsUnread'].includes(step.name) && !this.hasSeenAcl) { @@ -1087,6 +1086,9 @@ export default { showWarning(t('mail', 'Could not apply tag, configured tag not found')) } break + case 'removeTags': + await this.removeTags() + break case 'markAsImportant': if (!this.isImportant) { if (this.layoutMessageViewThreaded) { @@ -1154,6 +1156,21 @@ export default { } }, + async removeTags() { + const threadEnvelopes = this.layoutMessageViewThreaded + ? this.mainStore.getEnvelopesByThreadRootId(this.data.accountId, this.data.threadRootId) + : [this.data] + for (const envelope of threadEnvelopes) { + const promises = [] + const tags = this.mainStore.getEnvelopeExposedTags(envelope.databaseId) + for (const tag of tags) { + const call = this.mainStore.removeEnvelopeTag({ envelope, imapLabel: tag.imapLabel }) + promises.push(call) + } + await Promise.all(promises) + } + }, + unselect() { if (this.selected) { this.$emit('update:selected', false) diff --git a/src/components/TagModal.vue b/src/components/TagModal.vue index 7d163375a0..80dbeb393a 100644 --- a/src/components/TagModal.vue +++ b/src/components/TagModal.vue @@ -66,7 +66,6 @@ import TagItem from './TagItem.vue' import logger from '../logger.js' import useMainStore from '../store/mainStore.js' import { validateTag } from '../util/tag.js' -import { hiddenTags } from './tags.js' function randomColor() { let randomHexColor = ((1 << 24) * Math.random() | 0).toString(16) @@ -114,7 +113,7 @@ export default { computed: { ...mapStores(useMainStore), tags() { - return this.mainStore.getTags.filter((tag) => tag.imapLabel !== '$label1' && !(tag.displayName.toLowerCase() in hiddenTags)).sort((a, b) => { + return this.mainStore.getExposedTags().sort((a, b) => { if (a.isDefaultTag && !b.isDefaultTag) { return -1 } diff --git a/src/components/ThreadEnvelope.vue b/src/components/ThreadEnvelope.vue index 7484e623aa..37d1ca9219 100644 --- a/src/components/ThreadEnvelope.vue +++ b/src/components/ThreadEnvelope.vue @@ -435,7 +435,6 @@ import useOutboxStore from '../store/outboxStore.js' import { mailboxHasRights } from '../util/acl.js' import { translateTagDisplayName } from '../util/tag.js' import { Text, toPlain } from '../util/text.js' -import { hiddenTags } from './tags.js' // Ternary loading state const Loading = Object.seal({ @@ -625,7 +624,7 @@ export default { }, tags() { - return this.mainStore.getEnvelopeTags(this.envelope.databaseId).filter((tag) => tag.imapLabel !== '$label1' && !(tag.displayName.toLowerCase() in hiddenTags)) + return this.mainStore.getEnvelopeExposedTags(this.envelope.databaseId) }, hasChangedSubject() { diff --git a/src/components/quickActions/Action.vue b/src/components/quickActions/Action.vue index 3b510ebef1..287a5929e7 100644 --- a/src/components/quickActions/Action.vue +++ b/src/components/quickActions/Action.vue @@ -36,7 +36,6 @@ import CloseIcon from 'vue-material-design-icons/Close.vue' import DragIcon from 'vue-material-design-icons/Drag.vue' import Icon from './Icon.vue' import useMainStore from '../../store/mainStore.js' -import { hiddenTags } from '../tags.js' export default { name: 'Action', @@ -85,7 +84,7 @@ export default { options() { if (this.action.name === 'applyTag') { - return this.mainStore.getTags.filter((tag) => tag.imapLabel !== '$label1' && !(tag.displayName.toLowerCase() in hiddenTags)).map((tag) => ({ + return this.mainStore.getExposedTags().map((tag) => ({ value: tag.displayName, id: tag.id, })) @@ -109,6 +108,8 @@ export default { return this.t('mail', 'Mark as spam') case 'applyTag': return this.t('mail', 'Tag') + case 'removeTags': + return this.t('mail', 'Remove tags') case 'moveThread': return this.t('mail', 'Move thread') case 'deleteThread': diff --git a/src/components/quickActions/Icon.vue b/src/components/quickActions/Icon.vue index ca6bbbceb5..37f18ca981 100644 --- a/src/components/quickActions/Icon.vue +++ b/src/components/quickActions/Icon.vue @@ -14,6 +14,7 @@ import EmailUnread from 'vue-material-design-icons/EmailOutline.vue' import ImportantIcon from 'vue-material-design-icons/LabelVariant.vue' import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue' import IconFavorite from 'vue-material-design-icons/Star.vue' +import TagOffIcon from 'vue-material-design-icons/TagOffOutline.vue' import TagIcon from 'vue-material-design-icons/TagOutline.vue' import IconDelete from 'vue-material-design-icons/TrashCanOutline.vue' export default { @@ -32,6 +33,8 @@ export default { return AlertOctagonIcon case 'applyTag': return TagIcon + case 'removeTags': + return TagOffIcon case 'markAsImportant': return ImportantIcon case 'markAsFavorite': diff --git a/src/components/quickActions/Settings.vue b/src/components/quickActions/Settings.vue index 29a6283365..37a55d6bb5 100644 --- a/src/components/quickActions/Settings.vue +++ b/src/components/quickActions/Settings.vue @@ -64,6 +64,12 @@ {{ t('mail', 'Tag') }} + + + {{ t('mail', 'Remove tags') }} +