Skip to content

Commit e4a501d

Browse files
authored
Merge pull request #4521 from nextcloud/feat/merging-duplicate-contacts
Add merging duplicate contacts
2 parents 4ac918d + 025472c commit e4a501d

2 files changed

Lines changed: 720 additions & 1 deletion

File tree

src/components/ContactsList.vue

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
{ number: readOnlyMultiSelectedCount })" />
2626
</NcDialog>
2727

28+
<NcModal v-if="isMerging"
29+
:name="t('contacts', 'Merge contacts')"
30+
size="large"
31+
@close="isMerging = false">
32+
<Merging :contacts="multiSelectedContacts" @finished="finishContactMerging" />
33+
</NcModal>
34+
2835
<div class="contacts-list__header">
2936
<div class="search-contacts-field">
3037
<input v-model="query" type="text" :placeholder="t('contacts', 'Search contacts …')">
@@ -45,6 +52,15 @@
4552
@click.prevent="attemptDeleteAllMultiSelected">
4653
<IconDelete :size="16" />
4754
</NcButton>
55+
<NcButton v-if="!isMergingLoading"
56+
type="tertiary"
57+
:disabled="!areTwoEditable"
58+
:title="mergeActionTitle"
59+
:close-after-click="true"
60+
@click.prevent="initiateContactMerging">
61+
<IconSetMerge :size="20" />
62+
</NcButton>
63+
<NcLoadingIcon v-else :size="20" />
4864
</div>
4965
</transition>
5066

@@ -59,11 +75,14 @@
5975
</template>
6076

6177
<script>
62-
import { NcAppContentList as AppContentList, NcButton, NcDialog, NcNoteCard } from '@nextcloud/vue'
78+
import { NcAppContentList as AppContentList, NcButton, NcDialog, NcNoteCard, NcModal, NcLoadingIcon } from '@nextcloud/vue'
6379
import ContactsListItem from './ContactsList/ContactsListItem.vue'
6480
import VirtualList from 'vue-virtual-scroll-list'
6581
import IconSelect from 'vue-material-design-icons/CloseThick.vue'
6682
import IconDelete from 'vue-material-design-icons/DeleteOutline.vue'
83+
import IconSetMerge from 'vue-material-design-icons/SetMerge.vue'
84+
import Merging from './ContactsList/Merging.vue'
85+
6786
// eslint-disable-next-line import/no-unresolved
6887
import IconCancelRaw from '@mdi/svg/svg/cancel.svg?raw'
6988
// eslint-disable-next-line import/no-unresolved
@@ -79,7 +98,11 @@ export default {
7998
NcButton,
8099
IconSelect,
81100
IconDelete,
101+
IconSetMerge,
82102
NcDialog,
103+
NcModal,
104+
Merging,
105+
NcLoadingIcon,
83106
},
84107
85108
props: {
@@ -106,6 +129,7 @@ export default {
106129
ContactsListItem,
107130
query: '',
108131
multiSelectedContacts: new Map(),
132+
refreshKey: 0, // used to force re-render of the list when search query changes, can be removed in vue3
109133
showDeleteConfirmationDialog: false,
110134
buttons: [
111135
{
@@ -121,6 +145,8 @@ export default {
121145
},
122146
],
123147
lastToggledIndex: undefined,
148+
isMerging: false,
149+
isMergingLoading: false,
124150
}
125151
},
126152
@@ -159,11 +185,19 @@ export default {
159185
isAtLeastOneEditable() {
160186
return this.readOnlyMultiSelectedCount !== this.multiSelectedContacts.size
161187
},
188+
areTwoEditable() {
189+
return this.multiSelectedContacts.size - this.readOnlyMultiSelectedCount === 2
190+
},
162191
deleteActionTitle() {
163192
return this.isAtLeastOneEditable
164193
? n('contacts', 'Delete {number} contact', 'Delete {number} contacts', this.multiSelectedContacts.size, { number: this.multiSelectedContacts.size })
165194
: t('contacts', 'Please select at least one editable contact to delete')
166195
},
196+
mergeActionTitle() {
197+
return this.areTwoEditable
198+
? t('contacts', 'Merge contacts')
199+
: t('contacts', 'Please select two editable contacts to merge')
200+
},
167201
},
168202
169203
watch: {
@@ -302,6 +336,30 @@ export default {
302336
this.unselectAllMultiSelected()
303337
this.showDeleteConfirmationDialog = false
304338
},
339+
340+
async initiateContactMerging() {
341+
// For every contact in the multiSelectedContacts, we need to dispatch the load contact action
342+
this.isMergingLoading = true
343+
const contacts = Array.from(this.multiSelectedContacts.values())
344+
for (const contact of contacts) {
345+
await this.$store.dispatch('fetchFullContact', { contact })
346+
}
347+
348+
this.isMergingLoading = false
349+
this.isMerging = true
350+
},
351+
352+
async finishContactMerging(mergedContact) {
353+
// After merging, we need to update the contact in the store
354+
await this.$store.dispatch('fetchFullContact', { contact: mergedContact, forceReFetch: true })
355+
356+
this.unselectAllMultiSelected()
357+
this.isMerging = false
358+
359+
await this.$router.push({
360+
name: 'root',
361+
})
362+
},
305363
},
306364
}
307365
</script>

0 commit comments

Comments
 (0)