Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the selected contacts to a group #2763

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 156 additions & 1 deletion src/components/ContactsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,65 @@
<div class="search-contacts-field">
<input v-model="query" type="text" :placeholder="t('contacts', 'Search contacts …')">
</div>
<Actions
class="merge-button"
menu-align="right">
<template v-if="selected.length >= 1">
<ActionButton v-if="showAddToGroup"
icon="icon-clone"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

icon classes have been deprecated. please use a material icon.

@click="OpenMultiselect">
{{ t('contacts', 'Add to group') }}
</ActionButton>
<Multiselect v-if="showSelectGroup"
:options="groups"
:taggable="true"
@input="addSelectedContactsToGroup"
@tag="addSelectedContactsToGroup"
@close="closeMultiselect" />
</template>
<ActionButton v-if="selected.length >= 2"
:close-after-click="true"
icon="icon-clone"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

material icon required

@click="mergeContact">
{{ t('contacts', 'Merge') }}
</ActionButton>
<ActionButton v-if="selected.length >= 1"
:close-after-click="true"
icon="icon-delete"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

material icon required

@click="deleteMultipleContact">
{{ t('contacts', 'Delete') }}
</ActionButton>
</Actions>
</div>
<VirtualList ref="scroller"
class="contacts-list"
data-key="key"
:data-sources="filteredList"
:data-component="ContactsListItem"
:estimate-size="68" />
:estimate-size="68"
:extra-props={selected}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing quotes

@update-check-selected="selectionChanged" />
</AppContentList>
</template>

<script>
import AppContentList from '@nextcloud/vue/dist/Components/NcAppContentList'
import ContactsListItem from './ContactsList/ContactsListItem'
import VirtualList from 'vue-virtual-scroll-list'
import Actions from '@nextcloud/vue/dist/Components/NcActions'
import ActionButton from '@nextcloud/vue/dist/Components/NcActionButton'
import naturalCompare from 'string-natural-compare'
import Multiselect from '@nextcloud/vue/dist/Components/NcMultiselect'

export default {
name: 'ContactsList',

components: {
AppContentList,
VirtualList,
Actions,
ActionButton,
Multiselect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing trailing comma

},

props: {
Expand All @@ -68,10 +106,18 @@ export default {
return {
ContactsListItem,
query: '',
selected: [],
showGroups: false,
showAddToGroup: true,
showSelectGroup: false,
}
},

computed: {
groups() {
return this.$store.getters.getGroups.slice(0).map(group => group.name)
.sort((a, b) => naturalCompare(a, b, { caseInsensitive: true }))
},
selectedContact() {
return this.$route.params.selectedContact
},
Expand Down Expand Up @@ -157,6 +203,112 @@ export default {
}
return true
},
selectionChanged(newValue) {
if (this.selected.includes(newValue)) {
this.selected.splice(this.selected.indexOf(newValue), 1)
} else {
this.selected.push(newValue)
}
},
deleteMultipleContact() {
const temp = []
this.selected.forEach(element => {
if (this.contacts[element]) {
// delete contact
this.$store.dispatch('deleteContact', { contact: this.contacts[element] })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispatch is async. you have to await the result and handle any possible error.

temp.push(this.selected.indexOf(element), 1)
}
})
// delete the uid in selected of the contact deleted
temp.forEach(el => {
this.selected.splice(temp, 1)
})
},
cleanContactValue(contact, value) {
contact.jCal[1].forEach(element => {
if (element[0] === value[0] && element[3] === '' && !Array.isArray(element[3])) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are those magic numbers from? what is the 0th element, 0th value and 3rd element?

contact.jCal[1].splice(contact.jCal[1].indexOf(element), 1)
} else if (element[0] === value[0] && Array.isArray(element[3])) {
let isempty = true
value[3].forEach(arr => {
if (arr !== '') {
isempty = false
}
})
Comment on lines +232 to +237
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (isempty === false) {
contact.jCal[1].splice(contact.jCal[1].indexOf(element), 1)
}
}
})
},
addValue(contact, jcalvalue) {
jcalvalue.filter(element => {
// exclude the unique field we don't want to add
return !['uid', 'version', 'fn', 'prodid', 'gender', 'rev'].includes(element[0])
}).forEach(value => {
if (Array.isArray(value[3])) {
let isempty = true
value[3].forEach(arr => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic number?

if (arr !== '') {
isempty = false
}
})
Comment on lines +250 to +255
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Array.some, again

if (isempty === false) {
// delete blank field of the same type and push the new field
this.cleanContactValue(contact, value)
contact.jCal[1].push(value)
}
} else if (value[3] !== '') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic numer?

let include = false
contact.jCal[1].forEach(element => {
if (element[0] === value[0] && element[3] === value[3]) {
include = true
}
})
if (!include) {
// delete blank field of the same type and push the new field
this.cleanContactValue(contact, value)
contact.jCal[1].push(value)
}
}
})
return contact
},
mergeContact() {
const firstContact = this.contacts[this.selected[0]]
this.selected.slice(1).forEach((element) => {
if (this.contacts[element]) {
const contactjcal = this.contacts[element].jCal[1]
this.addValue(firstContact, contactjcal)
// delete the contact merged and the uid in the selected
this.$store.dispatch('deleteContact', { contact: this.contacts[element] }) && this.selected.splice(this.selected.indexOf(element), 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await the action

}
})
this.$store.dispatch('updateContact', firstContact)
},
OpenMultiselect() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
OpenMultiselect() {
openMultiselect() {

camel case for methods

this.showAddToGroup = false
this.showSelectGroup = true
},
closeMultiselect() {
this.showAddToGroup = true
this.showSelectGroup = false
},
addSelectedContactsToGroup(value) {
this.selected.forEach(element => {
const selectedContact = this.contacts[element]
const data = selectedContact.groups
if (!data.includes(value)) {
this.$store.dispatch('addContactToGroup', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await the action

contact: selectedContact,
groupName: value,
})
data.push(value)
selectedContact.groups = data
this.$store.dispatch('updateContact', selectedContact)
}
})
},
},
}
</script>
Expand Down Expand Up @@ -187,4 +339,7 @@ export default {
padding: 0 4px;
}

.merge-button {
float: right;
}
</style>
87 changes: 67 additions & 20 deletions src/components/ContactsList/ContactsListItem.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
<template>
<ListItem
:id="id"
:key="source.key"
class="list-item-style envelope"
:title="source.displayName"
:to="{ name: 'contact', params: { selectedGroup: selectedGroup, selectedContact: source.key } }">
<!-- @slot Icon slot -->
<div class="contacts-list__item"
:class="{'contacts-list__item--active': selectedContact === source.key}"
@mouseover="hover = true"
@mouseleave="hover = false">
<ActionCheckbox v-if="hover || ischecked"
:checked="ischecked"
@check="isSelected"
@uncheck="isUnselected" />
<BaseAvatar v-else-if="!hover && !ischecked"
:display-name="source.displayName"
:url="avatarUrl"
:size="40" />
<ListItem
:id="id"
:key="source.key"
class="list-item-style envelope"
:title="source.displayName"
:to="{ name: 'contact', params: { selectedGroup: selectedGroup, selectedContact: source.key } }">
<!-- @slot Icon slot -->

<template #icon>
<div class="app-content-list-item-icon">
<BaseAvatar :display-name="source.displayName" :url="avatarUrl" :size="40" />
</div>
</template>
<template #subtitle>
<div class="envelope__subtitle">
<span class="envelope__subtitle__subject">
{{ source.email }}
</span>
</div>
</template>
</ListItem>
<template #icon>
<div class="app-content-list-item-icon">
<BaseAvatar :display-name="source.displayName" :url="avatarUrl" :size="40" />
</div>
</template>
<template #subtitle>
<div class="envelope__subtitle">
<span class="envelope__subtitle__subject">
{{ source.email }}
</span>
</div>
</template>
</ListItem>
</div>
</template>

<script>
import ActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox'
import { NcListItem as ListItem } from '@nextcloud/vue'
import BaseAvatar from '@nextcloud/vue/dist/Components/NcAvatar'

Expand All @@ -32,6 +46,7 @@ export default {
components: {
ListItem,
BaseAvatar,
ActionCheckbox,
},

props: {
Expand All @@ -43,10 +58,22 @@ export default {
type: Object,
required: true,
},
selected: {
type: Array,
required: false
}
},
data() {
return {
avatarUrl: undefined,
hover: false,
ischecked: false,
}
},
beforeMount() {
// check the checkbox who is already selected
if (this.selected.includes(this.source.key)) {
this.ischecked = true
}
},
computed: {
Expand Down Expand Up @@ -85,6 +112,26 @@ export default {
this.avatarUrl = `${this.source.url}?photo`
}
},

isSelected() {
this.ischecked = true
// update the selected
this.$parent.$parent.$emit('update-check-selected', this.source.key)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't access the grandparent component this way

},

isUnselected() {
this.ischecked = false
// update the selected
this.$parent.$parent.$emit('update-check-selected', this.source.key)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessing the grandparent is a no go. this makes the component depend on the exact structure and is prone to break in unnoticed ways. Please find a better way to pass around the data. Could you use Vuex?

},

/**
* Select this contact within the list
*/
selectContact() {
// change url with router
this.$router.push({ name: 'contact', params: { selectedGroup: this.selectedGroup, selectedContact: this.source.key } })
},
},
}
</script>
Expand Down