nextcloud-calendar/src/components/Editor/Invitees/InviteesListSearch.vue

249 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
- @copyright Copyright (c) 2019 Georg Ehrke <oc.list@georgehrke.com>
-
- @author Georg Ehrke <oc.list@georgehrke.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<Multiselect
class="invitees-search"
:options="matches"
:searchable="true"
:internal-search="false"
:max-height="600"
:show-no-results="true"
:show-no-options="false"
:placeholder="placeholder"
:class="{ 'showContent': inputGiven, 'icon-loading': isLoading }"
open-direction="bottom"
track-by="email"
label="dropdownName"
@search-change="findAttendees"
@select="addAttendee">
<!--<template slot="singleLabel" slot-scope="props"><img class="option__image" :src="props.option.img" alt="No Mans Sky"><span class="option__desc"><span class="option__title">{{ props.option.title }}</span></span></template>-->
<template slot="singleLabel" slot-scope="props">
<div class="invitees-search-list-item">
<Avatar v-if="props.option.isUser" :user="props.option.avatar" :display-name="props.option.dropdownName" />
<Avatar v-if="!props.option.isUser" :url="props.option.avatar" :display-name="props.option.dropdownName" />
<div v-if="props.option.hasMultipleEMails" class="invitees-search-list-item__label invitees-search-list-item__label--with-displayname">
<div>
{{ props.option.commonName }}
</div>
<div>
{{ props.option.email }}
</div>
</div>
<div v-else class="invitees-search-list-item__label invitees-search-list-item__label--single-email">
<div>
{{ props.option.dropdownName }}
</div>
</div>
</div>
</template>
<template slot="option" slot-scope="props">
<div class="invitees-search-list-item">
<Avatar v-if="props.option.isUser" :user="props.option.avatar" :display-name="props.option.dropdownName" />
<Avatar v-if="!props.option.isUser" :url="props.option.avatar" :display-name="props.option.dropdownName" />
<div v-if="props.option.hasMultipleEMails" class="invitees-search-list-item__label invitees-search-list-item__label--with-multiple-email">
<div>
{{ props.option.commonName }}
</div>
<div>
{{ props.option.email }}
</div>
</div>
<div v-else class="invitees-search-list-item__label invitees-search-list-item__label--single-email">
<div>
{{ props.option.dropdownName }}
</div>
</div>
</div>
</template>
</Multiselect>
</template>
<script>
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import { findPrincipalsByDisplayName } from '../../../services/caldavService.js'
import HttpClient from '@nextcloud/axios'
import debounce from 'debounce'
import { linkTo } from '@nextcloud/router'
export default {
name: 'InviteesListSearch',
components: {
Avatar,
Multiselect,
},
props: {
alreadyInvitedEmails: {
type: Array,
required: true,
},
},
data() {
return {
isLoading: false,
inputGiven: false,
matches: [],
}
},
computed: {
placeholder() {
return this.$t('calendar', 'Search for e-mails, users, contacts, resources or rooms')
},
noResult() {
return this.$t('calendar', 'No match found')
},
},
methods: {
findAttendees: debounce(async function(query) {
this.isLoading = true
const matches = []
if (query.length > 0) {
const promises = [
this.findAttendeesFromContactsAPI(query),
this.findAttendeesFromDAV(query),
]
const [contactsResults, davResults] = await Promise.all(promises)
matches.push(...contactsResults)
matches.push(...davResults)
// Source of the Regex: https://stackoverflow.com/a/46181
// eslint-disable-next-line
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
if (emailRegex.test(query)) {
const alreadyInList = matches.find((attendee) => attendee.email === query)
if (!alreadyInList) {
matches.unshift({
calendarUserType: 'INDIVIDUAL',
commonName: query,
email: query,
isUser: false,
avatar: null,
language: null,
timezoneId: null,
hasMultipleEMails: false,
dropdownName: query,
})
}
}
this.isLoading = false
this.inputGiven = true
} else {
this.inputGiven = false
this.isLoading = false
}
this.matches = matches
}, 500),
addAttendee(selectedValue) {
this.$emit('addAttendee', selectedValue)
},
async findAttendeesFromContactsAPI(query) {
let response
try {
response = await HttpClient.post(linkTo('calendar', 'index.php') + '/v1/autocompletion/attendee', {
search: query,
})
} catch (error) {
console.debug(error)
return []
}
const data = response.data
return data.reduce((arr, result) => {
const hasMultipleEMails = result.emails.length > 1
result.emails.forEach((email) => {
let name
if (result.name && !hasMultipleEMails) {
name = result.name
} else if (result.name && hasMultipleEMails) {
name = `${result.name} (${email})`
} else {
name = email
}
if (this.alreadyInvitedEmails.includes(email)) {
return
}
arr.push({
calendarUserType: 'INDIVIDUAL',
commonName: result.name,
email: email,
isUser: false,
avatar: result.photo,
language: result.lang,
timezoneId: result.tzid,
hasMultipleEMails,
dropdownName: name,
})
})
return arr
}, [])
},
async findAttendeesFromDAV(query) {
let results
try {
results = await findPrincipalsByDisplayName(query)
} catch (error) {
console.debug(error)
return []
}
return results.filter((principal) => {
if (!principal.email) {
return false
}
if (this.alreadyInvitedEmails.includes(principal.email)) {
return
}
// We do not support GROUPS for now
if (principal.calendarUserType === 'GROUP') {
return false
}
return true
}).map((principal) => {
return {
commonName: principal.displayname,
calendarUserType: principal.calendarUserType,
email: principal.email,
lang: null,
isUser: principal.calendarUserType === 'INDIVIDUAL',
avatar: principal.userId,
hasMultipleEMails: false,
dropdownName: principal.displayname || principal.email,
}
})
},
},
}
</script>