Allow to select contact groups
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
cdce2f6fd8
commit
1ef769ea3b
|
@ -122,10 +122,12 @@ class ContactController extends Controller {
|
|||
return new JSONResponse();
|
||||
}
|
||||
|
||||
$result = $this->contactsManager->search($search, ['FN', 'EMAIL']);
|
||||
$contactsResult = $this->contactsManager->search($search, ['FN', 'EMAIL']);
|
||||
|
||||
$groupsContactsResult = $this->contactsManager->search($search, ['CATEGORIES']);
|
||||
|
||||
$contacts = [];
|
||||
foreach ($result as $r) {
|
||||
foreach ($contactsResult as $r) {
|
||||
// Information about system users is fetched via DAV nowadays
|
||||
if (isset($r['isLocalSystemBook']) && $r['isLocalSystemBook']) {
|
||||
continue;
|
||||
|
@ -135,43 +137,30 @@ class ContactController extends Controller {
|
|||
continue;
|
||||
}
|
||||
|
||||
$name = $this->getNameFromContact($r);
|
||||
if (\is_string($r['EMAIL'])) {
|
||||
$r['EMAIL'] = [$r['EMAIL']];
|
||||
}
|
||||
|
||||
$photo = isset($r['PHOTO'])
|
||||
? $this->getPhotoUri($r['PHOTO'])
|
||||
: null;
|
||||
|
||||
$lang = null;
|
||||
if (isset($r['LANG'])) {
|
||||
if (\is_array($r['LANG'])) {
|
||||
$lang = $r['LANG'][0];
|
||||
} else {
|
||||
$lang = $r['LANG'];
|
||||
}
|
||||
}
|
||||
|
||||
$timezoneId = null;
|
||||
if (isset($r['TZ'])) {
|
||||
if (\is_array($r['TZ'])) {
|
||||
$timezoneId = $r['TZ'][0];
|
||||
} else {
|
||||
$timezoneId = $r['TZ'];
|
||||
}
|
||||
}
|
||||
|
||||
$contacts[] = [
|
||||
'name' => $name,
|
||||
'emails' => $r['EMAIL'],
|
||||
'lang' => $lang,
|
||||
'tzid' => $timezoneId,
|
||||
'photo' => $photo,
|
||||
];
|
||||
$contacts[] = $this->processContact($r);
|
||||
}
|
||||
|
||||
return new JSONResponse($contacts);
|
||||
$groupsContacts = array_reduce($groupsContactsResult, function (array $acc, array $groupContact) use ($search) {
|
||||
|
||||
// Information about system users is fetched via DAV nowadays
|
||||
if (isset($groupContact['isLocalSystemBook']) && $groupContact['isLocalSystemBook']) {
|
||||
return $acc;
|
||||
}
|
||||
|
||||
if (!isset($groupContact['EMAIL'])) {
|
||||
return $acc;
|
||||
}
|
||||
|
||||
$categories = array_filter(explode(',', $groupContact['CATEGORIES']), function (string $category) use ($search) {
|
||||
return str_contains(mb_strtolower($category), mb_strtolower($search));
|
||||
});
|
||||
foreach ($categories as $category) {
|
||||
$acc[$category][] = $this->processContact($groupContact);
|
||||
}
|
||||
return $acc;
|
||||
}, []);
|
||||
|
||||
return new JSONResponse(['contacts' => $contacts, 'groups' => $groupsContacts]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -252,4 +241,41 @@ class ContactController extends Controller {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function processContact(array $contactData): array {
|
||||
$name = $this->getNameFromContact($contactData);
|
||||
if (\is_string($contactData['EMAIL'])) {
|
||||
$contactData['EMAIL'] = [$contactData['EMAIL']];
|
||||
}
|
||||
|
||||
$photo = isset($contactData['PHOTO'])
|
||||
? $this->getPhotoUri($contactData['PHOTO'])
|
||||
: null;
|
||||
|
||||
$lang = null;
|
||||
if (isset($contactData['LANG'])) {
|
||||
if (\is_array($contactData['LANG'])) {
|
||||
$lang = $contactData['LANG'][0];
|
||||
} else {
|
||||
$lang = $contactData['LANG'];
|
||||
}
|
||||
}
|
||||
|
||||
$timezoneId = null;
|
||||
if (isset($contactData['TZ'])) {
|
||||
if (\is_array($contactData['TZ'])) {
|
||||
$timezoneId = $contactData['TZ'][0];
|
||||
} else {
|
||||
$timezoneId = $contactData['TZ'];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $name,
|
||||
'emails' => $contactData['EMAIL'],
|
||||
'lang' => $lang,
|
||||
'tzid' => $timezoneId,
|
||||
'photo' => $photo,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,8 +38,12 @@
|
|||
@select="addAttendee">
|
||||
<template #option="{ option }">
|
||||
<div class="invitees-search-list-item">
|
||||
<!-- We need to specify a unique key here for the avatar to be reactive. -->
|
||||
<Avatar v-if="option.isUser"
|
||||
<Avatar v-if="option.type === 'group'">
|
||||
<template #icon>
|
||||
<AccountMultiple :size="20" />
|
||||
</template>
|
||||
</Avatar><!-- We need to specify a unique key here for the avatar to be reactive. -->
|
||||
<Avatar v-else-if="option.isUser"
|
||||
:key="option.uid"
|
||||
:user="option.avatar"
|
||||
:display-name="option.dropdownName" />
|
||||
|
@ -52,9 +56,12 @@
|
|||
<div>
|
||||
{{ option.dropdownName }}
|
||||
</div>
|
||||
<div v-if="option.email !== option.dropdownName">
|
||||
<div v-if="option.email !== option.dropdownName && option.type !== 'group'">
|
||||
{{ option.email }}
|
||||
</div>
|
||||
<div v-if="option.type === 'group'">
|
||||
{{ option.subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -69,12 +76,14 @@ import HttpClient from '@nextcloud/axios'
|
|||
import debounce from 'debounce'
|
||||
import { linkTo } from '@nextcloud/router'
|
||||
import { randomId } from '../../../utils/randomId.js'
|
||||
import AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue'
|
||||
|
||||
export default {
|
||||
name: 'InviteesListSearch',
|
||||
components: {
|
||||
Avatar,
|
||||
Multiselect,
|
||||
AccountMultiple,
|
||||
},
|
||||
props: {
|
||||
alreadyInvitedEmails: {
|
||||
|
@ -147,7 +156,13 @@ export default {
|
|||
this.matches = matches
|
||||
}, 500),
|
||||
addAttendee(selectedValue) {
|
||||
this.$emit('add-attendee', selectedValue)
|
||||
if (selectedValue.type === 'group') {
|
||||
selectedValue.contacts.forEach((contact) => {
|
||||
this.$emit('add-attendee', contact)
|
||||
})
|
||||
} else {
|
||||
this.$emit('add-attendee', selectedValue)
|
||||
}
|
||||
},
|
||||
async findAttendeesFromContactsAPI(query) {
|
||||
let response
|
||||
|
@ -161,8 +176,24 @@ export default {
|
|||
return []
|
||||
}
|
||||
|
||||
const data = response.data
|
||||
return data.reduce((arr, result) => {
|
||||
const contacts = []
|
||||
/** Groups are shown before contacts */
|
||||
for (const [groupName, groupContacts] of Object.entries(response.data.groups)) {
|
||||
const processedGroupContacts = this.buildEmailsFromContactData(groupContacts)
|
||||
if (processedGroupContacts.length > 0) {
|
||||
contacts.push({
|
||||
type: 'group',
|
||||
dropdownName: groupName,
|
||||
subtitle: this.$n('calendar', 'Contains %n contact', 'Contains %n contacts', processedGroupContacts.length),
|
||||
contacts: processedGroupContacts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return [...contacts, ...this.buildEmailsFromContactData(response.data.contacts)]
|
||||
},
|
||||
buildEmailsFromContactData(contactsData) {
|
||||
return contactsData.reduce((arr, result) => {
|
||||
const hasMultipleEMails = result.emails.length > 1
|
||||
|
||||
result.emails.forEach((email) => {
|
||||
|
@ -174,12 +205,11 @@ export default {
|
|||
} else {
|
||||
name = email
|
||||
}
|
||||
|
||||
if (this.alreadyInvitedEmails.includes(email)) {
|
||||
return
|
||||
}
|
||||
|
||||
arr.push({
|
||||
type: 'contact',
|
||||
calendarUserType: 'INDIVIDUAL',
|
||||
commonName: result.name,
|
||||
email,
|
||||
|
@ -191,7 +221,6 @@ export default {
|
|||
dropdownName: name,
|
||||
})
|
||||
})
|
||||
|
||||
return arr
|
||||
}, [])
|
||||
},
|
||||
|
|
|
@ -167,10 +167,10 @@ class ContactControllerTest extends TestCase {
|
|||
->with()
|
||||
->willReturn(true);
|
||||
|
||||
$this->manager->expects(self::once())
|
||||
$this->manager->expects(self::exactly(2))
|
||||
->method('search')
|
||||
->with('search 123', ['FN', 'EMAIL'])
|
||||
->willReturn([
|
||||
->withConsecutive(['search 123', ['FN', 'EMAIL']], ['search 123', ['CATEGORIES']])
|
||||
->willReturnOnConsecutiveCalls([
|
||||
[
|
||||
'FN' => 'Person 1',
|
||||
'ADR' => [
|
||||
|
@ -213,29 +213,68 @@ class ContactControllerTest extends TestCase {
|
|||
'TZ' => 'Australia/Adelaide',
|
||||
'PHOTO' => 'VALUE:BINARY:4242424242'
|
||||
],
|
||||
], [
|
||||
[
|
||||
'FN' => 'Person 4',
|
||||
'EMAIL' => 'foo4@example.com',
|
||||
'CATEGORIES' => 'search 123,other'
|
||||
],
|
||||
[
|
||||
'FN' => 'Person 5',
|
||||
'CATEGORIES' => 'search 123'
|
||||
],
|
||||
[
|
||||
'FN' => 'Person 6',
|
||||
'EMAIL' => 'foo6@example.com',
|
||||
'CATEGORIES' => 'search 123'
|
||||
],
|
||||
]);
|
||||
|
||||
$response = $this->controller->searchAttendee('search 123');
|
||||
|
||||
$this->assertInstanceOf(JSONResponse::class, $response);
|
||||
$this->assertEquals([
|
||||
[
|
||||
'name' => 'Person 1',
|
||||
'emails' => [
|
||||
'foo1@example.com',
|
||||
'foo2@example.com',
|
||||
],
|
||||
'lang' => 'de',
|
||||
'tzid' => 'Europe/Berlin',
|
||||
'photo' => 'http://foo.bar',
|
||||
], [
|
||||
'name' => 'Person 2',
|
||||
'emails' => [
|
||||
'foo3@example.com'
|
||||
],
|
||||
'lang' => null,
|
||||
'tzid' => null,
|
||||
'photo' => null,
|
||||
'contacts' => [
|
||||
[
|
||||
'name' => 'Person 1',
|
||||
'emails' => [
|
||||
'foo1@example.com',
|
||||
'foo2@example.com',
|
||||
],
|
||||
'lang' => 'de',
|
||||
'tzid' => 'Europe/Berlin',
|
||||
'photo' => 'http://foo.bar',
|
||||
], [
|
||||
'name' => 'Person 2',
|
||||
'emails' => [
|
||||
'foo3@example.com'
|
||||
],
|
||||
'lang' => null,
|
||||
'tzid' => null,
|
||||
'photo' => null,
|
||||
]
|
||||
],
|
||||
'groups' => [
|
||||
'search 123' => [
|
||||
[
|
||||
'name' => 'Person 4',
|
||||
'emails' => [
|
||||
'foo4@example.com'
|
||||
],
|
||||
'lang' => null,
|
||||
'tzid' => null,
|
||||
'photo' => null,
|
||||
],
|
||||
[
|
||||
'name' => 'Person 6',
|
||||
'emails' => [
|
||||
'foo6@example.com'
|
||||
],
|
||||
'lang' => null,
|
||||
'tzid' => null,
|
||||
'photo' => null,
|
||||
]
|
||||
]
|
||||
]
|
||||
], $response->getData());
|
||||
$this->assertEquals(200, $response->getStatus());
|
||||
|
|
Loading…
Reference in New Issue