Skip to content

Commit

Permalink
FEATURE: Implements multiple envelopes selection
Browse files Browse the repository at this point in the history
#2273

Signed-off-by: Cyrille Bollu <cyrpub@bollu.be>
  • Loading branch information
StCyr committed Apr 1, 2020
1 parent 82b71cf commit 19047f8
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 1 deletion.
41 changes: 40 additions & 1 deletion src/components/Envelope.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<template>
<router-link class="app-content-list-item" :class="{unseen: data.flags.unseen, draft}" :to="link">
<router-link
class="app-content-list-item"
:class="{unseen: data.flags.unseen, draft, selected: selected}"
:to="link"
>
<div
v-if="folder.isUnified"
class="mail-message-account-color"
Expand All @@ -13,6 +17,10 @@
></div>
<div class="app-content-list-item-icon">
<Avatar :display-name="addresses" :email="avatarEmail" />
<div v-show="selectMode" class="app-content-list-item-select-checkbox">
<input ref="selectCheckbox" class="checkbox" type="checkbox" @click.stop="$emit('select', data)" />
<label />
</div>
</div>
<div class="app-content-list-item-line-one" :title="addresses">
{{ addresses }}
Expand All @@ -35,6 +43,9 @@
<ActionButton icon="icon-mail" @click.prevent="onToggleSeen">{{
data.flags.unseen ? t('mail', 'Mark read') : t('mail', 'Mark unread')
}}</ActionButton>
<ActionButton icon="icon-checkmark" :close-after-click="true" @click.prevent="onToggleSelect">{{
selected ? t('mail', 'Unselect') : t('mail', 'Select')
}}</ActionButton>
<ActionButton icon="icon-delete" @click.prevent="onDelete">{{ t('mail', 'Delete') }}</ActionButton>
</Actions>
</router-link>
Expand Down Expand Up @@ -65,6 +76,14 @@ export default {
type: Object,
required: true,
},
selectMode: {
type: Boolean,
default: false,
},
selected: {
type: Boolean,
default: false,
},
},
computed: {
accountColor() {
Expand Down Expand Up @@ -130,6 +149,9 @@ export default {
}
},
},
updated() {
this.$refs.selectCheckbox.checked = this.selected
},
methods: {
onToggleFlagged() {
this.$store.dispatch('toggleEnvelopeFlagged', this.data)
Expand All @@ -145,6 +167,10 @@ export default {
id: this.data.id,
})
},
onToggleSelect() {
this.$refs.selectCheckbox.checked = !this.$refs.selectCheckbox.cheked
this.$emit('select', this.data)
},
},
}
</script>
Expand All @@ -158,12 +184,25 @@ export default {
z-index: 1;
}
.app-content-list-item-select-checkbox {
position: absolute;
left: 22px;
top: 20px;
z-index: 50; // same as icon-starred
}
.app-content-list-item.selected {
background-color: var(--color-background-dark);
}
.app-content-list-item.unseen {
font-weight: bold;
}
.app-content-list-item.draft .app-content-list-item-line-two {
font-style: italic;
}
.app-content-list-item.active {
background-color: var(--color-primary-light);
}
.icon-reply,
.icon-attachment {
Expand Down
151 changes: 151 additions & 0 deletions src/components/EnvelopeList.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,57 @@
<template>
<div>
<transition name="multiselect-header">
<div v-if="selectMode" key="multiselect-header" class="multiselect-header">
<div class="button primary" @click.prevent="markSelectedSeenOrUnseen">
<span id="action-label">{{
areAllSelectedRead
? t('mail', 'Mark ' + selection.length + ' unread')
: t('mail', 'Mark ' + selection.length + ' read')
}}</span>
</div>
<Actions class="app-content-list-item-menu" menu-align="right">
<ActionButton icon="icon-starred" @click.prevent="favoriteOrUnfavoriteAll">{{
areAllSelectedFavorite
? t('mail', 'Unfavorite ' + selection.length)
: t('mail', 'Favorite ' + selection.length)
}}</ActionButton>
<ActionButton icon="icon-close" @click.prevent="unselectAll">
{{ t('mail', 'Unselect ' + selection.length) }}
</ActionButton>
<ActionButton icon="icon-delete" @click.prevent="deleteAllSelected">
{{ t('mail', 'Delete ' + selection.length) }}
</ActionButton>
</Actions>
</div>
</transition>
<transition-group name="list">
<div id="list-refreshing" key="loading" class="icon-loading-small" :class="{refreshing: refreshing}" />
<Envelope
v-for="env in envelopes"
:key="env.uid"
:data="env"
:folder="folder"
:selected="isEnvelopeSelected(envelopes.indexOf(env))"
:select-mode="selectMode"
@delete="$emit('delete', env.uid)"
@select="onEnvelopeSelect"
/>
<div id="load-more-mail-messages" key="loadingMore" :class="{'icon-loading-small': loadingMore}" />
</transition-group>
</div>
</template>

<script>
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import Envelope from './Envelope'
export default {
name: 'EnvelopeList',
components: {
Actions,
ActionButton,
Envelope,
},
props: {
Expand All @@ -38,16 +70,122 @@ export default {
refreshing: {
type: Boolean,
required: true,
default: true,
},
loadingMore: {
type: Boolean,
required: true,
},
},
data() {
return {
selection: [],
}
},
computed: {
selectMode() {
// returns true when in selection mode (where the user selects several emails at once)
return this.selection.length > 0
},
areAllSelectedRead() {
// returns false if at least one selected message has not been read yet
return this.selection.every((idx) => this.envelopes[idx].flags.unseen === false)
},
areAllSelectedFavorite() {
// returns false if at least one selected message has not been favorited yet
return this.selection.every((idx) => this.envelopes[idx].flags.flagged === true)
},
},
methods: {
isEnvelopeSelected(idx) {
if (this.selection.length == 0) {
return false
}
return this.selection.includes(idx)
},
markSelectedSeenOrUnseen() {
let seenFlag = this.areAllSelectedRead
this.selection.forEach((envelopeId) => {
this.$store.dispatch('markEnvelopeSeenOrUnseen', {
envelope: this.envelopes[envelopeId],
seenFlag: seenFlag,
})
})
this.unselectAll()
},
favoriteOrUnfavoriteAll() {
let favFlag = !this.areAllSelectedFavorite
this.selection.forEach((envelopeId) => {
this.$store.dispatch('markEnvelopeFavoriteOrUnfavorite', {
envelope: this.envelopes[envelopeId],
favFlag: favFlag,
})
})
this.unselectAll()
},
deleteAllSelected() {
this.selection.forEach((envelopeId) => {
// Navigate if the message beeing deleted is the one currently viewed
if (this.envelopes[envelopeId].uid == this.$route.params.messageUid) {
let next
if (envelopeId === 0) {
next = this.envelopes[envelopeId + 1]
} else {
next = this.envelopes[envelopeId - 1]
}
if (next) {
this.$router.push({
name: 'message',
params: {
accountId: this.$route.params.accountId,
folderId: this.$route.params.folderId,
messageUid: next.uid,
},
})
}
}
this.$store.dispatch('deleteMessage', this.envelopes[envelopeId])
})
this.unselectAll()
},
onEnvelopeSelect(envelope) {
const idx = this.envelopes.indexOf(envelope)
if (this.selection.indexOf(idx) == -1) {
envelope.flags.selected = true
this.selection.push(idx)
} else {
envelope.flags.selected = false
this.selection.splice(this.selection.indexOf(idx), 1)
}
return
},
unselectAll() {
this.envelopes.forEach((env) => {
env.flags.selected = false
})
this.selection = []
},
},
}
</script>

<style scoped>
.multiselect-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: var(--color-main-background-translucent);
position: sticky;
top: 0px;
height: 48px;
z-index: 100;
}
#load-more-mail-messages {
margin: 10px auto;
padding: 10px;
Expand All @@ -73,15 +211,28 @@ export default {
min-height: 32px;
}
.multiselect-header-enter-active,
.multiselect-header-leave-active,
.list-enter-active,
.list-leave-active {
transition: all var(--animation-quick);
}
.multiselect-header-enter,
.multiselect-header-leave-to,
.list-enter,
.list-leave-to {
opacity: 0;
height: 0px;
transform: scaleY(0);
}
#action-label {
vertical-align: middle;
}
@media only screen and (min-width: 600px) {
#action-label {
display: block;
}
}
</style>
40 changes: 40 additions & 0 deletions src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,46 @@ export default {
})
})
},
markEnvelopeFavoriteOrUnfavorite({commit, getters}, {envelope, favFlag}) {
// Change immediately and switch back on error
const oldState = envelope.flags.flagged
commit('flagEnvelope', {
envelope,
flag: 'flagged',
value: favFlag,
})

setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.id, 'flagged', favFlag).catch((e) => {
console.error('could not favorite/unfavorite message ' + envelope.uid, e)

// Revert change
commit('flagEnvelope', {
envelope,
flag: 'flagged',
value: oldState,
})
})
},
markEnvelopeSeenOrUnseen({commit, getters}, {envelope, seenFlag}) {
// Change immediately and switch back on error
const oldState = envelope.flags.unseen
commit('flagEnvelope', {
envelope,
flag: 'unseen',
value: seenFlag,
})

setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.id, 'unseen', seenFlag).catch((e) => {
console.error('could not mark message ' + envelope.uid + ' seen/unseen', e)

// Revert change
commit('flagEnvelope', {
envelope,
flag: 'unseen',
value: oldState,
})
})
},
fetchMessage({commit}, uid) {
const {accountId, folderId, id} = parseUid(uid)
return fetchMessage(accountId, folderId, id).then((message) => {
Expand Down

0 comments on commit 19047f8

Please sign in to comment.