From 670493bd8a029d8e3b6ab75778cb2fd0cb16378a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 28 Aug 2018 14:53:19 +0200 Subject: Dav lib 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- src/components/ContactDetails.vue | 123 ++++++++++++---- .../ContactDetails/ContactDetailsProperty.vue | 16 ++- src/components/Properties/PropertyDateTime.vue | 142 +++++++++++++++++++ src/components/Properties/PropertyGroups.vue | 157 +++++++++++++++++++++ src/components/Properties/PropertyMultipleText.vue | 145 +++++++++++++++++++ src/components/Properties/PropertySelect.vue | 147 +++++++++++++++++++ src/components/Properties/PropertyText.vue | 139 ++++++++++++++++++ src/components/Properties/PropertyTitle.vue | 48 +++++++ src/components/properties/PropertyDateTime.vue | 127 ----------------- src/components/properties/PropertyGroups.vue | 141 ------------------ src/components/properties/PropertyMultipleText.vue | 130 ----------------- src/components/properties/PropertyText.vue | 125 ---------------- src/components/properties/PropertyTitle.vue | 46 ------ src/models/contact.js | 11 ++ src/models/rfcProps.js | 17 ++- src/services/cdav.js | 47 ++++++ src/store/addressbooks.js | 70 ++++----- src/store/contacts.js | 20 ++- src/views/Contacts.vue | 67 ++++++--- 19 files changed, 1039 insertions(+), 679 deletions(-) create mode 100644 src/components/Properties/PropertyDateTime.vue create mode 100644 src/components/Properties/PropertyGroups.vue create mode 100644 src/components/Properties/PropertyMultipleText.vue create mode 100644 src/components/Properties/PropertySelect.vue create mode 100644 src/components/Properties/PropertyText.vue create mode 100644 src/components/Properties/PropertyTitle.vue delete mode 100644 src/components/properties/PropertyDateTime.vue delete mode 100644 src/components/properties/PropertyGroups.vue delete mode 100644 src/components/properties/PropertyMultipleText.vue delete mode 100644 src/components/properties/PropertyText.vue delete mode 100644 src/components/properties/PropertyTitle.vue create mode 100644 src/services/cdav.js (limited to 'src') diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue index 95de110d..cae7cd9c 100644 --- a/src/components/ContactDetails.vue +++ b/src/components/ContactDetails.vue @@ -80,26 +80,33 @@
+ + + + +
diff --git a/src/components/ContactDetails/ContactDetailsProperty.vue b/src/components/ContactDetails/ContactDetailsProperty.vue index 7aa6b0c9..4260f447 100644 --- a/src/components/ContactDetails/ContactDetailsProperty.vue +++ b/src/components/ContactDetails/ContactDetailsProperty.vue @@ -24,7 +24,8 @@ + :is-last-property="isLastProperty" :class="{'property--last': isLastProperty}" :contact="contact" + @delete="deleteProp" /> diff --git a/src/components/Properties/PropertyGroups.vue b/src/components/Properties/PropertyGroups.vue new file mode 100644 index 00000000..56a6ce8e --- /dev/null +++ b/src/components/Properties/PropertyGroups.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/src/components/Properties/PropertyMultipleText.vue b/src/components/Properties/PropertyMultipleText.vue new file mode 100644 index 00000000..45fc70ef --- /dev/null +++ b/src/components/Properties/PropertyMultipleText.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/src/components/Properties/PropertySelect.vue b/src/components/Properties/PropertySelect.vue new file mode 100644 index 00000000..4981eb47 --- /dev/null +++ b/src/components/Properties/PropertySelect.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/components/Properties/PropertyText.vue b/src/components/Properties/PropertyText.vue new file mode 100644 index 00000000..32937db0 --- /dev/null +++ b/src/components/Properties/PropertyText.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/src/components/Properties/PropertyTitle.vue b/src/components/Properties/PropertyTitle.vue new file mode 100644 index 00000000..cde06dd3 --- /dev/null +++ b/src/components/Properties/PropertyTitle.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/components/properties/PropertyDateTime.vue b/src/components/properties/PropertyDateTime.vue deleted file mode 100644 index 2d6205b4..00000000 --- a/src/components/properties/PropertyDateTime.vue +++ /dev/null @@ -1,127 +0,0 @@ - - - - - diff --git a/src/components/properties/PropertyGroups.vue b/src/components/properties/PropertyGroups.vue deleted file mode 100644 index 01976b4a..00000000 --- a/src/components/properties/PropertyGroups.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - - - diff --git a/src/components/properties/PropertyMultipleText.vue b/src/components/properties/PropertyMultipleText.vue deleted file mode 100644 index 1ac0e8ad..00000000 --- a/src/components/properties/PropertyMultipleText.vue +++ /dev/null @@ -1,130 +0,0 @@ - - - - - diff --git a/src/components/properties/PropertyText.vue b/src/components/properties/PropertyText.vue deleted file mode 100644 index a15caa4d..00000000 --- a/src/components/properties/PropertyText.vue +++ /dev/null @@ -1,125 +0,0 @@ - - - - - diff --git a/src/components/properties/PropertyTitle.vue b/src/components/properties/PropertyTitle.vue deleted file mode 100644 index d857a647..00000000 --- a/src/components/properties/PropertyTitle.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - - diff --git a/src/models/contact.js b/src/models/contact.js index b332e89d..e5e9e83e 100644 --- a/src/models/contact.js +++ b/src/models/contact.js @@ -48,6 +48,7 @@ export default class Contact { this.vCard.addPropertyWithValue('uid', uuid()) } } + /** * Update internal data of this contact * @@ -59,6 +60,16 @@ export default class Contact { this.vCard = new ICAL.Component(this.jCal) } + /** + * Update linked addressbook of this contact + * + * @param {Object} addressbook + * @memberof Contact + */ + updateAddressbook(addressbook) { + this.addressbook = addressbook + } + /** * Ensure we're normalizing the possible arrays * into a string by taking the first element diff --git a/src/models/rfcProps.js b/src/models/rfcProps.js index 55df4349..cae39970 100644 --- a/src/models/rfcProps.js +++ b/src/models/rfcProps.js @@ -54,7 +54,7 @@ const properties = { readableName: t('contacts', 'Federated Cloud ID'), defaultValue: { value: [''], - meta: { type: ['HOME'] } + type: ['HOME'] }, options: [ { id: 'HOME', name: t('contacts', 'Home') }, @@ -76,9 +76,10 @@ const properties = { ], displayOrder: [0, 2, 1, 5, 3, 4, 6], icon: 'icon-address', + default: true, defaultValue: { value: ['', '', '', '', '', '', ''], - meta: { type: ['HOME'] } + type: ['HOME'] }, options: [ { id: 'HOME', name: t('contacts', 'Home') }, @@ -105,9 +106,10 @@ const properties = { multiple: true, readableName: t('contacts', 'Email'), icon: 'icon-mail', + default: true, defaultValue: { value: '', - meta: { type: ['HOME'] } + type: ['HOME'] }, options: [ { id: 'HOME', name: t('contacts', 'Home') }, @@ -121,7 +123,7 @@ const properties = { icon: 'icon-comment', defaultValue: { value: [''], - meta: { type: ['SKYPE'] } + type: ['SKYPE'] }, options: [ { id: 'IRC', name: 'IRC' }, @@ -135,9 +137,10 @@ const properties = { multiple: true, readableName: t('contacts', 'Phone'), icon: 'icon-comment', + default: true, defaultValue: { value: '', - meta: { type: ['HOME,VOICE'] } + type: ['HOME,VOICE'] }, options: [ { id: 'HOME,VOICE', name: t('contacts', 'Home') }, @@ -161,7 +164,7 @@ const properties = { readableName: t('contacts', 'Social network'), defaultValue: { value: [''], - meta: { type: ['facebook'] } + type: ['facebook'] }, options: [ { id: 'FACEBOOK', name: 'Facebook' }, @@ -208,7 +211,7 @@ const properties = { ), defaultValue: { value: [''], - meta: { type: ['CONTACT'] } + type: ['CONTACT'] }, options: [ { id: 'CONTACT', name: t('contacts', 'Contact') }, diff --git a/src/services/cdav.js b/src/services/cdav.js new file mode 100644 index 00000000..baecc096 --- /dev/null +++ b/src/services/cdav.js @@ -0,0 +1,47 @@ +/** + * @copyright Copyright (c) 2018 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + * + */ + +import cdav from 'cdav-library' + +function xhrProvider() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'requesttoken': OC.requestToken + } + var xhr = new XMLHttpRequest() + var oldOpen = xhr.open + + // override open() method to add headers + xhr.open = function() { + var result = oldOpen.apply(this, arguments) + for (let name in headers) { + xhr.setRequestHeader(name, headers[name]) + } + return result + } + OC.registerXHRForErrorProcessing(xhr) + return xhr +} + +export default new cdav.DavClient({ + rootUrl: OC.linkToRemote('dav') +}, xhrProvider) diff --git a/src/store/addressbooks.js b/src/store/addressbooks.js index 2dfe1aa8..775c4bf9 100644 --- a/src/store/addressbooks.js +++ b/src/store/addressbooks.js @@ -25,13 +25,16 @@ import vcfFile from '!raw-loader!./FakeName.vcf' import parseVcf from '../services/parseVcf' import Vue from 'vue' +import client from '../services/cdav' + const addressbookModel = { id: '', displayName: '', enabled: true, owner: '', shares: [], - contacts: {} + contacts: {}, + url: '' } const state = { @@ -80,8 +83,8 @@ const mutations = { * @param {Contact} contact */ addContactToAddressbook(state, contact) { - let addressbook = state.addressbooks.find(search => search === contact.addressbook) - Vue.set(addressbook.contacts, contact.key, contact) + let addressbook = state.addressbooks.find(search => search.id === contact.addressbook.id) + Vue.set(addressbook.contacts, contact.uid, contact) }, /** @@ -91,7 +94,7 @@ const mutations = { * @param {Contact} contact the contact to delete */ deleteContactFromAddressbook(state, contact) { - let addressbook = state.addressbooks.find(addressbook => addressbook === contact.addressbook) + let addressbook = state.addressbooks.find(search => search.id === contact.addressbook.id) Vue.delete(addressbook, contact.uid) }, @@ -163,50 +166,19 @@ const actions = { * @returns {Promise} fetch and commit */ async getAddressbooks(context) { - // Fake data before using real dav requests - let addressbooks = [ - { + let addressbooks = client.addressbookHomes.map(addressbook => { + return { id: 'ab1', displayName: 'Addressbook 1', enabled: true, owner: 'admin', - shares: [ - { displayname: 'Bob', writeable: true }, - { displayname: 'Rita', writeable: true }, - { displayname: 'Sue', writeable: false } - ], - contacts: {} - }, - { - id: 'ab2', - displayName: 'Addressbook 2', - enabled: false, - owner: 'admin', - shares: [ - { displayname: 'Aimee', writeable: false }, - { displayname: 'Jaguar', writeable: true } - ], - contacts: {} - }, - { - id: 'ab3', - displayName: 'Addressbook 3', - enabled: true, - owner: 'User1', - shares: [], - contacts: {} + dav: addressbook } - ] - // fake request - return new Promise((resolve, reject) => { - return setTimeout(() => { - addressbooks.forEach(addressbook => { - context.commit('addAddressbooks', addressbook) - }) - resolve() - return addressbooks - }, 1000) }) + addressbooks.forEach(addressbook => { + context.commit('addAddressbooks', addressbook) + }) + return addressbooks }, /** @@ -252,6 +224,20 @@ const actions = { */ shareAddressbook(contect, addressbook, sharee) { // Share addressbook with entered group or user + }, + + /** + * Move a contact to the provided addressbook + * + * @param {Object} context + * @param {Object} data + * @param {Contact} data.contact + * @param {Object} data.addressbook + */ + moveContactToAddressbook(context, { contact, addressbook }) { + context.commit('deleteContactFromAddressbook', contact) + context.commit('updateContactAddressbook', { contact, addressbook }) + context.commit('addContactToAddressbook', contact) } } diff --git a/src/store/contacts.js b/src/store/contacts.js index 1fcb427b..9554766a 100644 --- a/src/store/contacts.js +++ b/src/store/contacts.js @@ -95,7 +95,7 @@ const mutations = { Vue.set(state.contacts, contact.key, contact) } else { - console.error('Tried to replace a contact with a wrong object', contact) + console.error('Error while adding the following contact', contact) } }, @@ -115,7 +115,6 @@ const mutations = { // has the sort key changed for this contact ? let hasChanged = sortedContact.value !== contact[state.orderKey] if (hasChanged) { - console.debug('Sort triggered') // then we sort again state.sortedContacts .sort((a, b) => { @@ -130,6 +129,23 @@ const mutations = { } }, + /** + * Update a contact + * + * @param {Object} state + * @param {Contact} contact + */ + updateContactAddressbook(state, { contact, addressbook }) { + if (state.contacts[contact.key] && contact instanceof Contact) { + + // replace contact object data + state.contacts[contact.key].updateAddressbook(addressbook) + + } else { + console.error('Error while replacing the addressbook of following contact', contact) + } + }, + /** * Order the contacts list. Filters have terrible performances. * We do not want to run the sorting function every time. diff --git a/src/views/Contacts.vue b/src/views/Contacts.vue index 903ee506..5eaef100 100644 --- a/src/views/Contacts.vue +++ b/src/views/Contacts.vue @@ -49,6 +49,9 @@ import contentList from '../components/ContentList' import contactDetails from '../components/ContactDetails' import Contact from '../models/contact' +import rfcProps from '../models/rfcProps.js' + +import client from '../services/cdav.js' export default { components: { @@ -180,25 +183,44 @@ export default { beforeMount() { // get addressbooks then get contacts - this.$store.dispatch('getAddressbooks') - .then(() => { - Promise.all(this.addressbooks.map(async addressbook => { - await this.$store.dispatch('getContactsFromAddressBook', addressbook) - })).then(() => { - this.loading = false - this.selectFirstContactIfNone() + client.connect({ enableCardDAV: true }).then(() => { + this.$store.dispatch('getAddressbooks') + .then(() => { + Promise.all(this.addressbooks.map(async addressbook => { + await this.$store.dispatch('getContactsFromAddressBook', addressbook) + })).then(() => { + this.loading = false + this.selectFirstContactIfNone() + }) }) - }) - // check local storage for orderKey - if (localStorage.getItem('orderKey')) { - // run setOrder mutation with local storage key - this.$store.commit('setOrder', localStorage.getItem('orderKey')) - } + // check local storage for orderKey + if (localStorage.getItem('orderKey')) { + // run setOrder mutation with local storage key + this.$store.commit('setOrder', localStorage.getItem('orderKey')) + } + }) }, methods: { newContact() { - let contact = new Contact('BEGIN:VCARD\nVERSION:4.0\nFN:' + t('contacts', 'New Contact') + '\nCATEGORIES:' + this.selectedGroup + '\nEND:VCARD', this.defaultAddressbook) + let contact = new Contact('BEGIN:VCARD\nVERSION:4.0\nEND:VCARD', this.defaultAddressbook) + contact.fullName = 'New contact' + // itterate over all properties (filter is not usable on objects and we need the key of the property) + for (let name in rfcProps.properties) { + if (rfcProps.properties[name].default) { + let defaultData = rfcProps.properties[name].defaultValue + // add default field + let property = contact.vCard.addPropertyWithValue(name, defaultData.value) + // add default type + if (defaultData.type) { + property.setParameter('type', defaultData.type) + + } + } + } + if (this.selectedGroup !== t('contacts', 'All contacts')) { + contact.vCard.addPropertyWithValue('categories', this.selectedGroup) + } this.$store.dispatch('addContact', contact) this.$router.push({ name: 'contact', @@ -230,13 +252,16 @@ export default { if (this.selectedContact && !inList) { OC.Notification.showTemporary(t('contacts', 'Contact not found')) } - this.$router.push({ - name: 'contact', - params: { - selectedGroup: this.selectedGroup, - selectedContact: Object.values(this.contactsList)[0].key - } - }) + if (Object.keys(this.contactsList).length) { + this.$router.push({ + name: 'contact', + params: { + selectedGroup: this.selectedGroup, + selectedContact: Object.values(this.contactsList)[0].key + } + }) + document.querySelector('.app-content-list-item.active').scrollIntoView() + } } } } -- cgit v1.2.3 From e3c2f46c9bc3f8019042b14c8564b34fee6a1ce5 Mon Sep 17 00:00:00 2001 From: Jessica Date: Thu, 9 Aug 2018 18:17:25 +0200 Subject: added api.js with axios and worked on getting the matching users and groups based on input --- src/services/api.js | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/services/api.js (limited to 'src') diff --git a/src/services/api.js b/src/services/api.js new file mode 100644 index 00000000..fb77db13 --- /dev/null +++ b/src/services/api.js @@ -0,0 +1,121 @@ +/* + * @copyright Copyright (c) 2018 John Molakvoæ + * + * @author John Molakvoæ + * + * @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 . + * + */ + +import axios from 'axios' + +const requestToken = document.getElementsByTagName('head')[0].getAttribute('data-requesttoken') +const tokenHeaders = { headers: { requesttoken: requestToken } } + +const sanitize = function(url) { + return url.replace(/\/$/, '') // Remove last url slash +} + +export default { + + /** + * This Promise is used to chain a request that require an admin password confirmation + * Since chaining Promise have a very precise behavior concerning catch and then, + * you'll need to be careful when using it. + * e.g + * // store + * action(context) { + * return api.requireAdmin().then((response) => { + * return api.get('url') + * .then((response) => {API success}) + * .catch((error) => {API failure}); + * }).catch((error) => {requireAdmin failure}); + * } + * // vue + * this.$store.dispatch('action').then(() => {always executed}) + * + * Since Promise.then().catch().then() will always execute the last then + * this.$store.dispatch('action').then will always be executed + * + * If you want requireAdmin failure to also catch the API request failure + * you will need to throw a new error in the api.get.catch() + * + * e.g + * api.requireAdmin().then((response) => { + * api.get('url') + * .then((response) => {API success}) + * .catch((error) => {throw error;}); + * }).catch((error) => {requireAdmin OR API failure}); + * + * @returns {Promise} + */ + requireAdmin() { + return new Promise(function(resolve, reject) { + // TODO: migrate the OC.dialog to Vue and avoid this mess + // wait for password confirmation + let passwordTimeout + let waitForpassword = function() { + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + passwordTimeout = setTimeout(waitForpassword, 500) + return + } + clearTimeout(passwordTimeout) + clearTimeout(promiseTimeout) + resolve() + } + + // automatically reject after 5s if not resolved + let promiseTimeout = setTimeout(() => { + clearTimeout(passwordTimeout) + // close dial