diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-02-04 16:25:58 +0100 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-03-13 12:13:54 +0100 |
commit | 02de7d76e48ff602a6c97159984cb9f27b5784e2 (patch) | |
tree | d22110d792bb3ba19bf69af0ebcb53646113e362 /src | |
parent | 1d41416dec7277d5525386a2e6182262fda7cd91 (diff) |
Add ability to clone contact
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/Actions/ActionCopyNtoFN.vue | 2 | ||||
-rw-r--r-- | src/components/Actions/ActionToggleYear.vue | 2 | ||||
-rw-r--r-- | src/components/ContactDetails.vue | 132 | ||||
-rw-r--r-- | src/components/ContactDetails/ContactDetailsAddNewProp.vue | 4 | ||||
-rw-r--r-- | src/components/ContactDetails/ContactDetailsAvatar.vue | 8 | ||||
-rw-r--r-- | src/components/Properties/PropertyActions.vue | 6 | ||||
-rw-r--r-- | src/components/Properties/PropertyDateTime.vue | 6 | ||||
-rw-r--r-- | src/components/Properties/PropertyGroups.vue | 9 | ||||
-rw-r--r-- | src/components/Properties/PropertyMultipleText.vue | 4 | ||||
-rw-r--r-- | src/components/Properties/PropertySelect.vue | 4 | ||||
-rw-r--r-- | src/components/Properties/PropertyText.vue | 4 | ||||
-rw-r--r-- | src/components/Settings/SettingsAddressbook.vue | 13 | ||||
-rw-r--r-- | src/components/Settings/SettingsAddressbookShare.vue | 6 | ||||
-rw-r--r-- | src/components/Settings/SettingsImportContacts.vue | 11 | ||||
-rw-r--r-- | src/components/Settings/SettingsSortContacts.vue | 7 | ||||
-rw-r--r-- | src/main.js | 10 | ||||
-rw-r--r-- | src/store/addressbooks.js | 31 | ||||
-rw-r--r-- | src/views/Contacts.vue | 20 |
18 files changed, 230 insertions, 49 deletions
diff --git a/src/components/Actions/ActionCopyNtoFN.vue b/src/components/Actions/ActionCopyNtoFN.vue index c73ff9b4..4f4335b8 100644 --- a/src/components/Actions/ActionCopyNtoFN.vue +++ b/src/components/Actions/ActionCopyNtoFN.vue @@ -26,7 +26,7 @@ </ActionButton> </template> <script> -import { ActionButton } from '@nextcloud/vue' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' import ActionsMixin from '../../mixins/ActionsMixin' export default { diff --git a/src/components/Actions/ActionToggleYear.vue b/src/components/Actions/ActionToggleYear.vue index d76533a9..9da87b0c 100644 --- a/src/components/Actions/ActionToggleYear.vue +++ b/src/components/Actions/ActionToggleYear.vue @@ -28,7 +28,7 @@ </ActionCheckbox> </template> <script> -import { ActionCheckbox } from '@nextcloud/vue' +import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox' import ActionsMixin from '../../mixins/ActionsMixin' export default { diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue index e1a433c2..62321e60 100644 --- a/src/components/ContactDetails.vue +++ b/src/components/ContactDetails.vue @@ -94,7 +94,7 @@ :class="{'icon-loading-small': loadingUpdate, [`${warning.icon}`]: warning}" class="header-icon" - href="#" /> + @click="onWarningClick" /> <!-- conflict message --> <div v-if="conflict" @@ -117,12 +117,23 @@ @click="updateContact" /> <!-- menu actions --> - <Actions class="header-menu" menu-align="right"> + <Actions ref="actions" + class="header-menu" + menu-align="right" + :open.sync="openedMenu"> <ActionLink :href="contact.url" :download="`${contact.displayName}.vcf`" icon="icon-download"> {{ t('contacts', 'Download') }} </ActionLink> + <!-- user can clone if there is at least one option available --> + <ActionButton v-if="isReadOnly && addressbooksOptions.length > 0" + ref="cloneAction" + :close-after-click="true" + icon="icon-clone" + @click="cloneContact"> + {{ t('contacts', 'Clone contact') }} + </ActionButton> <ActionButton icon="icon-qrcode" @click="showQRcode"> {{ t('contacts', 'Generate QR Code') }} </ActionButton> @@ -135,9 +146,34 @@ <!-- qrcode --> <Modal v-if="qrcode" id="qrcode-modal" + :clear-view-delay="-1" :title="contact.displayName" @close="closeQrModal"> - <img :src="`data:image/svg+xml;base64,${qrcode}`" class="qrcode" width="400"> + <img :src="`data:image/svg+xml;base64,${qrcode}`" + :alt="t('contacts', 'Contact vcard as qrcode')" + class="qrcode" + width="400"> + </Modal> + + <!-- pick addressbook when cloning contact --> + <Modal v-if="showPickAddressbookModal" + id="pick-addressbook-modal" + :clear-view-delay="-1" + :title="t('contacts', 'Pick an address book')" + @close="closePickAddressbookModal"> + <Multiselect ref="pickAddressbook" + v-model="pickedAddressbook" + :allow-empty="false" + :options="addressbooksOptions" + :placeholder="t('contacts', 'Select addressbook')" + track-by="id" + label="name" /> + <button @click="closePickAddressbookModal"> + {{ t('contacts', 'Cancel') }} + </button> + <button class="primary" @click="cloneContact"> + {{ t('contacts', 'Clone contact') }} + </button> </Modal> </header> @@ -193,7 +229,12 @@ import debounce from 'debounce' import PQueue from 'p-queue' import qr from 'qr-image' import { stringify } from 'ical.js' -import { ActionLink, ActionButton } from '@nextcloud/vue' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import ActionLink from '@nextcloud/vue/dist/Components/ActionLink' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' +import Modal from '@nextcloud/vue/dist/Components/Modal' +import { showError } from '@nextcloud/dialogs' import rfcProps from '../models/rfcProps' import validate from '../services/validate' @@ -211,6 +252,7 @@ export default { name: 'ContactDetails', components: { + Actions, ActionButton, ActionLink, AddNewProp, @@ -219,6 +261,8 @@ export default { PropertyGroups, PropertyRev, PropertySelect, + Modal, + Multiselect, }, props: { @@ -247,6 +291,8 @@ export default { loadingUpdate: false, openedMenu: false, qrcode: '', + showPickAddressbookModal: false, + pickedAddressbook: null, } }, @@ -377,6 +423,7 @@ export default { /** * Store getters filtered and mapped to usable object + * This is the list of addressbooks that are available to write * * @returns {Array} */ @@ -504,11 +551,11 @@ export default { await this.updateLocalContact(contact) } catch (error) { if (error.name === 'ParserError') { - OC.Notification.showTemporary(t('contacts', 'Syntax error. Cannot open the contact.')) + showError(t('contacts', 'Syntax error. Cannot open the contact.')) } else if (error.status === 404) { - OC.Notification.showTemporary(t('contacts', `The contact doesn't exists anymore on the server.`)) + showError(t('contacts', `The contact doesn't exists anymore on the server.`)) } else { - OC.Notification.showTemporary(t('contacts', `Unable to retrieve the contact from the server, please check your network connection.`)) + showError(t('contacts', `Unable to retrieve the contact from the server, please check your network connection.`)) } console.error(error) // trigger a local deletion from the store only @@ -556,6 +603,41 @@ export default { }) } catch (error) { console.error(error) + showError(t('contacts', 'An error occured while trying to move the contact')) + } finally { + this.loadingUpdate = false + } + } + }, + + /** + * Copy contact to the specified addressbook + * + * @param {string} addressbookId the desired addressbook ID + */ + async copyContactToAddressbook(addressbookId) { + const addressbook = this.addressbooks.find(search => search.id === addressbookId) + this.loadingUpdate = true + if (addressbook) { + try { + const contact = await this.$store.dispatch('copyContactToAddressbook', { + // we need to use the store contact, not the local contact + // using this.contact and not this.localContact + contact: this.contact, + addressbook, + }) + // select the contact again + this.$router.push({ + name: 'contact', + params: { + selectedGroup: this.$route.params.selectedGroup, + selectedContact: contact.key, + }, + }) + } catch (error) { + console.error(error) + window.temp1 = error + showError(t('contacts', 'An error occured while trying to copy the contact')) } finally { this.loadingUpdate = false } @@ -600,6 +682,42 @@ export default { this.debounceUpdateContact() } }, + + /** + * Clone the current contact to another addressbook + */ + async cloneContact() { + // only one addressbook, let's clone it there + if (this.pickedAddressbook && this.addressbooks.find(addressbook => addressbook.id === this.pickedAddressbook.id)) { + console.debug('Cloning contact to', this.pickedAddressbook.name) + await this.copyContactToAddressbook(this.pickedAddressbook.id) + this.closePickAddressbookModal() + } else if (this.addressbooksOptions.length === 1) { + console.debug('Cloning contact to', this.addressbooksOptions[0].name) + await this.copyContactToAddressbook(this.addressbooksOptions[0].id) + } else { + this.showPickAddressbookModal = true + } + }, + + closePickAddressbookModal() { + this.showPickAddressbookModal = false + this.pickedAddressbook = null + }, + + /** + * The user clicked the warning icon + */ + onWarningClick() { + // if the user clicked the readonly icon, let's focus the clone button + if (this.isReadOnly && this.addressbooksOptions.length > 0) { + this.openedMenu = true + this.$nextTick(() => { + // focus the clone button + this.$refs.actions.onMouseFocusAction({ target: this.$refs.cloneAction.$el }) + }) + } + }, }, } </script> diff --git a/src/components/ContactDetails/ContactDetailsAddNewProp.vue b/src/components/ContactDetails/ContactDetailsAddNewProp.vue index c6b3ac64..7dc86f00 100644 --- a/src/components/ContactDetails/ContactDetailsAddNewProp.vue +++ b/src/components/ContactDetails/ContactDetailsAddNewProp.vue @@ -29,7 +29,7 @@ <div class="property__label" /> <!-- type selector --> - <multiselect :options="availableProperties" + <Multiselect :options="availableProperties" :placeholder="t('contacts', 'Choose property type')" class="property__value" track-by="id" @@ -40,6 +40,7 @@ </template> <script> +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import rfcProps from '../../models/rfcProps' import Contact from '../../models/contact' import PropertyTitle from '../Properties/PropertyTitle' @@ -49,6 +50,7 @@ export default { name: 'ContactDetailsAddNewProp', components: { + Multiselect, PropertyTitle, }, diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue index e2a03b9b..7b211c8c 100644 --- a/src/components/ContactDetails/ContactDetailsAvatar.vue +++ b/src/components/ContactDetails/ContactDetailsAvatar.vue @@ -46,6 +46,7 @@ <Modal v-if="maximizeAvatar" ref="modal" + :clear-view-delay="-1" class="contact-header-modal" size="large" :title="contact.displayName" @@ -86,7 +87,10 @@ <script> import debounce from 'debounce' -import { ActionLink, ActionButton } from '@nextcloud/vue' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import ActionLink from '@nextcloud/vue/dist/Components/ActionLink' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import Modal from '@nextcloud/vue/dist/Components/Modal' import { getFilePickerBuilder } from '@nextcloud/dialogs' import { generateRemoteUrl } from '@nextcloud/router' @@ -99,8 +103,10 @@ export default { name: 'ContactDetailsAvatar', components: { + Actions, ActionLink, ActionButton, + Modal, }, props: { diff --git a/src/components/Properties/PropertyActions.vue b/src/components/Properties/PropertyActions.vue index 8de4723a..f015d65c 100644 --- a/src/components/Properties/PropertyActions.vue +++ b/src/components/Properties/PropertyActions.vue @@ -25,7 +25,7 @@ <ActionButton icon="icon-delete" @click="deleteProperty"> {{ t('contacts', 'Delete') }} </ActionButton> - <actions :is="action" + <Actions :is="action" v-for="(action, index) in actions" :key="index" :component="propertyComponent" /> @@ -33,12 +33,14 @@ </template> <script> -import { ActionButton } from '@nextcloud/vue' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' export default { name: 'PropertyActions', components: { + Actions, ActionButton, }, diff --git a/src/components/Properties/PropertyDateTime.vue b/src/components/Properties/PropertyDateTime.vue index cd0af107..4402e748 100644 --- a/src/components/Properties/PropertyDateTime.vue +++ b/src/components/Properties/PropertyDateTime.vue @@ -30,7 +30,7 @@ <div class="property__row"> <!-- type selector --> - <multiselect v-if="propModel.options" + <Multiselect v-if="propModel.options" v-model="localType" :options="options" :searchable="false" @@ -84,7 +84,8 @@ <script> import debounce from 'debounce' import moment from 'moment' -import { DatetimePicker } from '@nextcloud/vue' +import DatetimePicker from '@nextcloud/vue/dist/Components/DatetimePicker' +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import { getLocale } from '@nextcloud/l10n' import { VCardTime } from 'ical.js' @@ -96,6 +97,7 @@ export default { name: 'PropertyDateTime', components: { + Multiselect, DatetimePicker, PropertyTitle, PropertyActions, diff --git a/src/components/Properties/PropertyGroups.vue b/src/components/Properties/PropertyGroups.vue index b2ce1547..d5f9e7ff 100644 --- a/src/components/Properties/PropertyGroups.vue +++ b/src/components/Properties/PropertyGroups.vue @@ -30,7 +30,7 @@ </div> <!-- multiselect taggable groups with a limit to 3 groups shown --> - <multiselect v-model="localValue" + <Multiselect v-model="localValue" :options="groups" :placeholder="t('contacts', 'Add contact in group')" :multiple="true" @@ -51,18 +51,23 @@ <span slot="noResult"> {{ t('settings', 'No results') }} </span> - </multiselect> + </Multiselect> </div> </div> </template> <script> import debounce from 'debounce' +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import Contact from '../../models/contact' export default { name: 'PropertyGroups', + components: { + Multiselect, + }, + props: { propModel: { type: Object, diff --git a/src/components/Properties/PropertyMultipleText.vue b/src/components/Properties/PropertyMultipleText.vue index 896ff661..af7bb430 100644 --- a/src/components/Properties/PropertyMultipleText.vue +++ b/src/components/Properties/PropertyMultipleText.vue @@ -30,7 +30,7 @@ <div class="property__row"> <!-- type selector --> - <multiselect v-if="propModel.options" + <Multiselect v-if="propModel.options" v-model="localType" :options="options" :placeholder="t('contacts', 'Select type')" @@ -101,6 +101,7 @@ </template> <script> +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import PropertyMixin from '../../mixins/PropertyMixin' import PropertyTitle from './PropertyTitle' import PropertyActions from './PropertyActions' @@ -109,6 +110,7 @@ export default { name: 'PropertyMultipleText', components: { + Multiselect, PropertyTitle, PropertyActions, }, diff --git a/src/components/Properties/PropertySelect.vue b/src/components/Properties/PropertySelect.vue index 1ddd6893..321ec771 100644 --- a/src/components/Properties/PropertySelect.vue +++ b/src/components/Properties/PropertySelect.vue @@ -39,7 +39,7 @@ {{ propModel.readableName }} </div> - <multiselect v-model="matchedOptions" + <Multiselect v-model="matchedOptions" :options="propModel.options" :placeholder="t('contacts', 'Select option')" :disabled="isSingleOption || isReadOnly" @@ -59,6 +59,7 @@ </template> <script> +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import PropertyMixin from '../../mixins/PropertyMixin' import PropertyTitle from './PropertyTitle' import PropertyActions from './PropertyActions' @@ -67,6 +68,7 @@ export default { name: 'PropertySelect', components: { + Multiselect, PropertyTitle, PropertyActions, }, diff --git a/src/components/Properties/PropertyText.vue b/src/components/Properties/PropertyText.vue index ffc7148c..69a7b3c8 100644 --- a/src/components/Properties/PropertyText.vue +++ b/src/components/Properties/PropertyText.vue @@ -30,7 +30,7 @@ <div class="property__row"> <!-- type selector --> - <multiselect v-if="propModel.options" + <Multiselect v-if="propModel.options" v-model="localType" :options="options" :placeholder="t('contacts', 'Select type')" @@ -92,6 +92,7 @@ </template> <script> +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import debounce from 'debounce' import PropertyMixin from '../../mixins/PropertyMixin' import PropertyTitle from './PropertyTitle' @@ -101,6 +102,7 @@ export default { name: 'PropertyText', components: { + Multiselect, PropertyTitle, PropertyActions, }, diff --git a/src/components/Settings/SettingsAddressbook.vue b/src/components/Settings/SettingsAddressbook.vue index 3d9b2271..0b86134c 100644 --- a/src/components/Settings/SettingsAddressbook.vue +++ b/src/components/Settings/SettingsAddressbook.vue @@ -93,18 +93,23 @@ </template> <script> -import { ActionLink, ActionButton, ActionInput, ActionCheckbox } from '@nextcloud/vue' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import ActionLink from '@nextcloud/vue/dist/Components/ActionLink' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import ActionInput from '@nextcloud/vue/dist/Components/ActionInput' +import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox' import ShareAddressBook from './SettingsAddressbookShare' export default { name: 'SettingsAddressbook', components: { - ShareAddressBook, - ActionLink, ActionButton, - ActionInput, ActionCheckbox, + ActionInput, + ActionLink, + Actions, + ShareAddressBook, }, props: { diff --git a/src/components/Settings/SettingsAddressbookShare.vue b/src/components/Settings/SettingsAddressbookShare.vue index ce43c655..728a4cfd 100644 --- a/src/components/Settings/SettingsAddressbookShare.vue +++ b/src/components/Settings/SettingsAddressbookShare.vue @@ -22,7 +22,7 @@ <template> <div class="addressbook-shares"> - <multiselect + <Multiselect id="users-groups-search" :options="usersOrGroups" :searchable="true" @@ -48,6 +48,7 @@ </template> <script> +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import client from '../../services/cdav' import addressBookSharee from './SettingsAddressbookSharee' @@ -55,9 +56,12 @@ import debounce from 'debounce' export default { name: 'SettingsAddressbookShare', + components: { + Multiselect, addressBookSharee, }, + props: { addressbook: { type: Object, diff --git a/src/components/Settings/SettingsImportContacts.vue b/src/components/Settings/SettingsImportContacts.vue index 809c2185..db53be5f 100644 --- a/src/components/Settings/SettingsImportContacts.vue +++ b/src/components/Settings/SettingsImportContacts.vue @@ -34,7 +34,7 @@ @close="toggleModal"> <section class="import-contact__modal-addressbook"> <h3>{{ t('contacts', 'Import contacts') }}</h3> - <multiselect + <Multiselect v-if="!isSingleAddressbook" id="select-addressbook" v-model="selectedAddressbook" @@ -47,7 +47,7 @@ <template slot="singleLabel" slot-scope="{ option }"> {{ t('contacts', `Import into the '{addressbookName}' addressbook`, { addressbookName: option.displayName }) }} </template> - </multiselect> + </Multiselect> </section> <section class="import-contact__modal-pick"> <input id="contact-import" @@ -84,6 +84,8 @@ </template> <script> +import Modal from '@nextcloud/vue/dist/Components/Modal' +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' import { encodePath } from '@nextcloud/paths' import { getCurrentUser } from '@nextcloud/auth' import { generateRemoteUrl } from '@nextcloud/router' @@ -103,6 +105,11 @@ const picker = getFilePickerBuilder(t('contacts', 'Choose a vCard file to import export default { name: 'SettingsImportContacts', + components: { + Modal, + Multiselect, + }, + data() { return { cancelRequest: () => {}, diff --git a/src/components/Settings/SettingsSortContacts.vue b/src/components/Settings/SettingsSortContacts.vue index 7bf0644f..11bf0da3 100644 --- a/src/components/Settings/SettingsSortContacts.vue +++ b/src/components/Settings/SettingsSortContacts.vue @@ -22,7 +22,7 @@ <template> <div class="sort-contacts"> - <multiselect + <Multiselect id="sort-by" :value="orderKeyOption" :searchable="false" @@ -36,10 +36,15 @@ </template> <script> +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' export default { name: 'SettingsSortContacts', + components: { + Multiselect, + }, + computed: { /* Order Keys */ options() { diff --git a/src/main.js b/src/main.js index 47b4353e..58cc554a 100644 --- a/src/main.js +++ b/src/main.js @@ -32,7 +32,6 @@ import { generateFilePath } from '@nextcloud/router' import { getRequestToken } from '@nextcloud/auth' /** GLOBAL COMPONENTS AND DIRECTIVE */ -import { Actions, DatetimePicker, Multiselect, PopoverMenu, Modal } from '@nextcloud/vue' import ClickOutside from 'vue-click-outside' import VTooltip from '@nextcloud/vue/dist/Directives/Tooltip' import VueClipboard from 'vue-clipboard2' @@ -48,13 +47,7 @@ __webpack_nonce__ = btoa(getRequestToken()) // eslint-disable-next-line __webpack_public_path__ = generateFilePath('contacts', '', 'js/') -// Register global components -Vue.component('Actions', Actions) -Vue.component('DatetimePicker', DatetimePicker) -Vue.component('Modal', Modal) -Vue.component('Multiselect', Multiselect) -Vue.component('PopoverMenu', PopoverMenu) - +// Register global directives Vue.directive('ClickOutside', ClickOutside) Vue.directive('Tooltip', VTooltip) @@ -71,6 +64,7 @@ Vue.prototype.oc_config = oc_config Vue.prototype.OC = OC Vue.prototype.OCA = OCA +// Force redirect if rewrite enabled but accessed through index.php if (window.location.pathname.split('/')[1] === 'index.php' && oc_config.modRewriteWorking) { router.push({ |