nextcloud/core/src/views/ContactsMenu.vue
julia.kirschenheuter ab845405df Replace input form with NcTextField
Signed-off-by: julia.kirschenheuter <julia.kirschenheuter@nextcloud.com>
2023-11-22 14:14:56 +01:00

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>