mirror of
https://github.com/nextcloud/server.git
synced 2026-03-12 05:33:11 -04:00
235 lines
6 KiB
Vue
235 lines
6 KiB
Vue
<!--
|
|
- @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
|
-
|
|
- @author Christopher Ng <chrng8@gmail.com>
|
|
-
|
|
- @license AGPL-3.0-or-later
|
|
-
|
|
- 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>
|
|
<NcHeaderMenu id="contactsmenu"
|
|
class="contactsmenu"
|
|
:aria-label="t('core', 'Search contacts')"
|
|
@open="handleOpen">
|
|
<template #trigger>
|
|
<Contacts :size="20" />
|
|
</template>
|
|
<div class="contactsmenu__menu">
|
|
<div class="contactsmenu__menu__input-wrapper">
|
|
<NcTextField :value.sync="searchTerm"
|
|
trailing-button-icon="close"
|
|
ref="contactsMenuInput"
|
|
:label="t('core', 'Search contacts')"
|
|
:trailing-button-label="t('core','Reset search')"
|
|
:show-trailing-button="searchTerm !== ''"
|
|
:placeholder="t('core', 'Search contacts …')"
|
|
id="contactsmenu__menu__search"
|
|
class="contactsmenu__menu__search"
|
|
@input="onInputDebounced"
|
|
@trailing-button-click="onReset" />
|
|
</div>
|
|
<NcEmptyContent v-if="error" :name="t('core', 'Could not load your contacts')">
|
|
<template #icon>
|
|
<Magnify />
|
|
</template>
|
|
</NcEmptyContent>
|
|
<NcEmptyContent v-else-if="loadingText" :name="loadingText">
|
|
<template #icon>
|
|
<NcLoadingIcon />
|
|
</template>
|
|
</NcEmptyContent>
|
|
<NcEmptyContent v-else-if="contacts.length === 0" :name="t('core', 'No contacts found')">
|
|
<template #icon>
|
|
<Magnify />
|
|
</template>
|
|
</NcEmptyContent>
|
|
<div v-else class="contactsmenu__menu__content">
|
|
<div id="contactsmenu-contacts">
|
|
<ul>
|
|
<Contact v-for="contact in contacts" :key="contact.id" :contact="contact" />
|
|
</ul>
|
|
</div>
|
|
<div v-if="contactsAppEnabled" class="contactsmenu__menu__content__footer">
|
|
<NcButton type="tertiary" :href="contactsAppURL">
|
|
{{ t('core', 'Show all contacts') }}
|
|
</NcButton>
|
|
</div>
|
|
<div v-else-if="canInstallApp" class="contactsmenu__menu__content__footer">
|
|
<NcButton type="tertiary" :href="contactsAppMgmtURL">
|
|
{{ t('core', 'Install the Contacts app') }}
|
|
</NcButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</NcHeaderMenu>
|
|
</template>
|
|
|
|
<script>
|
|
import axios from '@nextcloud/axios'
|
|
import Contacts from 'vue-material-design-icons/Contacts.vue'
|
|
import debounce from 'debounce'
|
|
import { getCurrentUser } from '@nextcloud/auth'
|
|
import { generateUrl } from '@nextcloud/router'
|
|
import Magnify from 'vue-material-design-icons/Magnify.vue'
|
|
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
|
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
|
|
import NcHeaderMenu from '@nextcloud/vue/dist/Components/NcHeaderMenu.js'
|
|
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
|
import { translate as t } from '@nextcloud/l10n'
|
|
|
|
import Contact from '../components/ContactsMenu/Contact.vue'
|
|
import logger from '../logger.js'
|
|
import Nextcloud from '../mixins/Nextcloud.js'
|
|
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
|
|
|
export default {
|
|
name: 'ContactsMenu',
|
|
|
|
components: {
|
|
Contact,
|
|
Contacts,
|
|
Magnify,
|
|
NcButton,
|
|
NcEmptyContent,
|
|
NcHeaderMenu,
|
|
NcLoadingIcon,
|
|
NcTextField,
|
|
},
|
|
|
|
mixins: [Nextcloud],
|
|
|
|
data() {
|
|
const user = getCurrentUser()
|
|
return {
|
|
contactsAppEnabled: false,
|
|
contactsAppURL: generateUrl('/apps/contacts'),
|
|
contactsAppMgmtURL: generateUrl('/settings/apps/social/contacts'),
|
|
canInstallApp: user.isAdmin,
|
|
contacts: [],
|
|
loadingText: undefined,
|
|
error: false,
|
|
searchTerm: '',
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
async handleOpen() {
|
|
await this.getContacts('')
|
|
},
|
|
async getContacts(searchTerm) {
|
|
if (searchTerm === '') {
|
|
this.loadingText = t('core', 'Loading your contacts …')
|
|
} else {
|
|
this.loadingText = t('core', 'Looking for {term} …', {
|
|
term: searchTerm,
|
|
})
|
|
}
|
|
|
|
// Let the user try a different query if the previous one failed
|
|
this.error = false
|
|
|
|
try {
|
|
const { data: { contacts, contactsAppEnabled } } = await axios.post(generateUrl('/contactsmenu/contacts'), {
|
|
filter: searchTerm,
|
|
})
|
|
this.contacts = contacts
|
|
this.contactsAppEnabled = contactsAppEnabled
|
|
this.loadingText = undefined
|
|
} catch (error) {
|
|
logger.error('could not load contacts', {
|
|
error,
|
|
searchTerm,
|
|
})
|
|
this.error = true
|
|
}
|
|
},
|
|
onInputDebounced: debounce(function() {
|
|
this.getContacts(this.searchTerm)
|
|
}, 500),
|
|
|
|
/**
|
|
* Reset the search state
|
|
*/
|
|
onReset() {
|
|
this.searchTerm = ''
|
|
this.contacts = []
|
|
this.focusInput()
|
|
},
|
|
|
|
/**
|
|
* Focus the search input on next tick
|
|
*/
|
|
focusInput() {
|
|
this.$nextTick(() => {
|
|
this.$refs.contactsMenuInput.focus()
|
|
this.$refs.contactsMenuInput.select()
|
|
})
|
|
},
|
|
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.contactsmenu {
|
|
overflow-y: hidden;
|
|
|
|
&__menu {
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
height: calc(50px * 6 + 2px + 26px);
|
|
max-height: inherit;
|
|
|
|
label[for="contactsmenu__menu__search"] {
|
|
font-weight: bold;
|
|
font-size: 19px;
|
|
margin-left: 13px;
|
|
}
|
|
|
|
&__input-wrapper {
|
|
padding: 10px;
|
|
z-index: 2;
|
|
top: 0;
|
|
}
|
|
|
|
&__search {
|
|
width: 100%;
|
|
height: 34px;
|
|
margin-top: 0!important;
|
|
}
|
|
|
|
&__content {
|
|
overflow-y: auto;
|
|
margin-top: 10px;
|
|
flex: 1 1 auto;
|
|
|
|
&__footer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
a {
|
|
&:focus-visible {
|
|
box-shadow: inset 0 0 0 2px var(--color-main-text) !important; // override rule in core/css/headers.scss #header a:focus-visible
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|