From 0547f92543eeecf8552ace982567708937bba3c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Sun, 18 Nov 2018 10:24:17 +0100 Subject: [PATCH 1/4] Do not break rfc cardinality Fix #728 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- .../ContactDetails/ContactDetailsAddNewProp.vue | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/ContactDetails/ContactDetailsAddNewProp.vue b/src/components/ContactDetails/ContactDetailsAddNewProp.vue index 448b52466..5ec61970e 100644 --- a/src/components/ContactDetails/ContactDetailsAddNewProp.vue +++ b/src/components/ContactDetails/ContactDetailsAddNewProp.vue @@ -56,13 +56,18 @@ export default { }, computed: { + usedProperties() { + return this.contact.jCal[1].map(prop => prop[0]) + }, availableProperties() { - return Object.keys(rfcProps.properties).map(key => { - return { - id: key, - name: rfcProps.properties[key].readableName - } - }).sort((a, b) => a.name.localeCompare(b.name)) + return Object.keys(rfcProps.properties) + .filter(prop => prop.multiple || this.usedProperties.indexOf(prop) === -1) + .map(key => { + return { + id: key, + name: rfcProps.properties[key].readableName + } + }).sort((a, b) => a.name.localeCompare(b.name)) } }, From caefe3ac654348185e72306da965b393cc5bcf59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Sun, 18 Nov 2018 17:49:27 +0100 Subject: [PATCH 2/4] Always show groups Fix #727 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 | 34 +++++++++++++++++++ .../ContactDetails/ContactDetailsProperty.vue | 6 ---- src/components/Properties/PropertyGroups.vue | 16 +++++---- src/models/contact.js | 1 + src/models/rfcProps.js | 3 -- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue index 7ed646394..b08b1abe4 100644 --- a/src/components/ContactDetails.vue +++ b/src/components/ContactDetails.vue @@ -109,6 +109,11 @@ + + + @@ -283,6 +288,35 @@ export default { } }, + groupsModel() { + return { + readableName: t('contacts', 'Groups') + // icon: 'icon-address-book' + } + }, + + /** + * Usable groups object linked to the local contact + * + * @param {string[]} data An array of groups + * @returns {Array} + */ + groups: { + get: function() { + return this.contact.groups + }, + set: function(data) { + console.log(data); + let property = this.contact.vCard.getFirstProperty('categories') + if (!property) { + // Ical.js store comma separated by an Array of array of string + property = this.contact.vCard.addPropertyWithValue('categories', [data]) + } + property.setValues(data) + this.updateContact() + } + }, + /** * Store getters filtered and mapped to usable object * diff --git a/src/components/ContactDetails/ContactDetailsProperty.vue b/src/components/ContactDetails/ContactDetailsProperty.vue index 4191ef16e..f5de4fd54 100644 --- a/src/components/ContactDetails/ContactDetailsProperty.vue +++ b/src/components/ContactDetails/ContactDetailsProperty.vue @@ -38,7 +38,6 @@ import Contact from 'Models/contact' import PropertyText from 'Components/Properties/PropertyText' import PropertyMultipleText from 'Components/Properties/PropertyMultipleText' import PropertyDateTime from 'Components/Properties/PropertyDateTime' -import propertyGroups from 'Components/Properties/PropertyGroups' import PropertySelect from 'Components/Properties/PropertySelect' export default { @@ -68,11 +67,6 @@ export default { computed: { // dynamically load component based on property type componentInstance() { - // groups - if (this.propName === 'categories') { - return propertyGroups - } - // dynamic matching if (this.property.isMultiValue && this.propType === 'text') { return PropertyMultipleText diff --git a/src/components/Properties/PropertyGroups.vue b/src/components/Properties/PropertyGroups.vue index 15677320c..70407a009 100644 --- a/src/components/Properties/PropertyGroups.vue +++ b/src/components/Properties/PropertyGroups.vue @@ -28,10 +28,11 @@ + :multiple="true" :taggable="true" :close-on-select="false" + :readonly="isReadOnly" :tag-width="60" + tag-placeholder="create" class="property__value" + @input="updateValue" @tag="validateGroup" @select="addContactToGroup" + @remove="removeContactToGroup"> +{{ localValue.length - 3 }} @@ -98,7 +99,7 @@ export default { /** * Debounce and send update event to parent */ - updateValue: debounce(function(e) { + updateValue: debounce(function() { // https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier this.$emit('update:value', this.localValue) }, 500), @@ -108,11 +109,12 @@ export default { * * @param {String} groupName the group name */ - addContactToGroup(groupName) { - this.$store.dispatch('addContactToGroup', { + async addContactToGroup(groupName) { + await this.$store.dispatch('addContactToGroup', { contact: this.contact, groupName }) + this.updateValue() }, /** diff --git a/src/models/contact.js b/src/models/contact.js index c2a5bfcc0..3f985b902 100644 --- a/src/models/contact.js +++ b/src/models/contact.js @@ -166,6 +166,7 @@ export default class Contact { let groupsProp = this.vCard.getFirstProperty('categories') if (groupsProp) { return groupsProp.getValues() + .filter(group => group !== '') } return [] } diff --git a/src/models/rfcProps.js b/src/models/rfcProps.js index 3f7efe534..3a21b2501 100644 --- a/src/models/rfcProps.js +++ b/src/models/rfcProps.js @@ -88,9 +88,6 @@ const properties = { { id: 'OTHER', name: t('contacts', 'Other') } ] }, - categories: { - readableName: t('contacts', 'Groups') - }, bday: { readableName: t('contacts', 'Birthday'), icon: 'icon-calendar-dark', From bfa38cba598fb7c3391334b6cd7fa9a7095dfc43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Mon, 19 Nov 2018 18:30:53 +0100 Subject: [PATCH 3/4] Improve traduction 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue index b08b1abe4..5ba9138c6 100644 --- a/src/components/ContactDetails.vue +++ b/src/components/ContactDetails.vue @@ -186,7 +186,7 @@ export default { if (!this.contact.dav) { return { icon: 'icon-error-white header-icon--pulse', - msg: t('contacts', 'This contact is not yet synced. Edit it to trigger a change.') + msg: t('contacts', 'This contact is not yet synced. Edit it to save it to the server.') } } else if (this.isReadOnly) { return { From 85c3b1a00ff3261333ad68e816d9535eb10f6c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Mon, 19 Nov 2018 18:51:38 +0100 Subject: [PATCH 4/4] JSDoc cleanup and better code comments 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 | 19 +++++++----- .../ContactDetailsAddNewProp.vue | 20 +++++++++++++ .../ContactDetails/ContactDetailsAvatar.vue | 13 ++++++++ .../ContactDetails/ContactDetailsProperty.vue | 6 ++-- .../ContactsList/ContactsListItem.vue | 4 +-- src/components/Properties/PropertyGroups.vue | 10 ++++--- .../Settings/SettingsAddressbookShare.vue | 4 +-- src/models/contact.js | 2 +- src/store/addressbooks.js | 30 +++++++++++-------- src/store/contacts.js | 2 +- src/store/groups.js | 6 ++-- src/store/importState.js | 12 ++++---- 12 files changed, 86 insertions(+), 42 deletions(-) diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue index 5ba9138c6..c559bd9a6 100644 --- a/src/components/ContactDetails.vue +++ b/src/components/ContactDetails.vue @@ -109,10 +109,9 @@ - + + :is-read-only="isReadOnly" class="property--groups property--last" /> @@ -180,7 +179,7 @@ export default { /** * Warning messages * - * @returns {Object|Boolean} + * @returns {Object|boolean} */ warning() { if (!this.contact.dav) { @@ -200,7 +199,7 @@ export default { /** * Conflict message * - * @returns {String|Boolean} + * @returns {string|boolean} */ conflict() { if (this.contact.conflict) { @@ -288,10 +287,15 @@ export default { } }, + /** + * Fake model to use the propertyGroups component + * + * @returns {Object} + */ groupsModel() { return { - readableName: t('contacts', 'Groups') - // icon: 'icon-address-book' + readableName: t('contacts', 'Groups'), + icon: 'icon-contacts' } }, @@ -306,7 +310,6 @@ export default { return this.contact.groups }, set: function(data) { - console.log(data); let property = this.contact.vCard.getFirstProperty('categories') if (!property) { // Ical.js store comma separated by an Array of array of string diff --git a/src/components/ContactDetails/ContactDetailsAddNewProp.vue b/src/components/ContactDetails/ContactDetailsAddNewProp.vue index 5ec61970e..74baa7a03 100644 --- a/src/components/ContactDetails/ContactDetailsAddNewProp.vue +++ b/src/components/ContactDetails/ContactDetailsAddNewProp.vue @@ -56,12 +56,26 @@ export default { }, computed: { + /** + * List of properties that the contact already have + * + * @returns {string[]} + */ usedProperties() { return this.contact.jCal[1].map(prop => prop[0]) }, + + /** + * List of every properties you are allowed to add + * on this contact + * + * @returns {Object[]} + */ availableProperties() { return Object.keys(rfcProps.properties) + // only allow to add multiple properties OR props that are not yet in the contact .filter(prop => prop.multiple || this.usedProperties.indexOf(prop) === -1) + // usable array of objects .map(key => { return { id: key, @@ -72,6 +86,12 @@ export default { }, methods: { + /** + * Add a new prop to the contact + * + * @param {Object} data destructuring object + * @param {string} data.id the id of the property. e.g fn + */ addProp({ id }) { let defaultData = rfcProps.properties[id].defaultValue let property = this.contact.vCard.addPropertyWithValue(id, defaultData ? defaultData.value : '') diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue index c142c82f6..c98264b6c 100644 --- a/src/components/ContactDetails/ContactDetailsAvatar.vue +++ b/src/components/ContactDetails/ContactDetailsAvatar.vue @@ -57,6 +57,11 @@ export default { } }, methods: { + /** + * Handler to store a new photo on the current contact + * + * @param {Object} event the event object containing the image + */ processFile(event) { if (event.target.files) { let file = event.target.files[0] @@ -73,10 +78,18 @@ export default { reader.readAsDataURL(file) } }, + + /** + * Toggle the full image preview + */ toggleSize() { // maximise or minimise avatar photo this.maximizeAvatar = !this.maximizeAvatar }, + + /** + * Remove the contact's picture + */ removePhoto() { this.contact.vCard.removeProperty('photo') this.maximizeAvatar = !this.maximizeAvatar diff --git a/src/components/ContactDetails/ContactDetailsProperty.vue b/src/components/ContactDetails/ContactDetailsProperty.vue index f5de4fd54..129a4dd27 100644 --- a/src/components/ContactDetails/ContactDetailsProperty.vue +++ b/src/components/ContactDetails/ContactDetailsProperty.vue @@ -113,7 +113,7 @@ export default { /** * Return the type of the prop e.g. FN * - * @returns {String} + * @returns {string} */ propName() { return this.property.name @@ -122,7 +122,7 @@ export default { * Return the type or property * * @see src/models/rfcProps - * @returns {String} + * @returns {string} */ propType() { // if we have a force type set, use it! @@ -147,7 +147,7 @@ export default { * but make sure to include the selected one * in the final list * - * @returns {Array} + * @returns {Object[]} */ sortedModelOptions() { if (this.propModel.options) { diff --git a/src/components/ContactsList/ContactsListItem.vue b/src/components/ContactsList/ContactsListItem.vue index 8bda16a74..4453390b8 100644 --- a/src/components/ContactsList/ContactsListItem.vue +++ b/src/components/ContactsList/ContactsListItem.vue @@ -58,7 +58,7 @@ export default { /** * Is this matching the current search ? * - * @returns {Boolean} + * @returns {boolean} */ matchSearch() { if (this.searchQuery !== '') { @@ -69,7 +69,7 @@ export default { /** * avatar color based on server toRgb method and the displayName - * @returns {String} the color in css format + * @returns {string} the color in css format */ colorAvatar() { try { diff --git a/src/components/Properties/PropertyGroups.vue b/src/components/Properties/PropertyGroups.vue index 70407a009..0ee7f23c9 100644 --- a/src/components/Properties/PropertyGroups.vue +++ b/src/components/Properties/PropertyGroups.vue @@ -23,6 +23,8 @@