summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2018-08-20 17:40:17 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2018-08-21 18:17:45 +0200
commit0b5f975b5288e1a9548260f9ac8f47453982f9a7 (patch)
tree41c70a22dc26fe6bf42eb4eb5bee22e94d645e87 /src
parent48d0f179817b784eb5133b857ad9981d72dd0ac1 (diff)
Type update, management and auto selection fix
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/ContactDetails.vue13
-rw-r--r--src/components/ContactDetails/ContactDetailsProperty.vue45
-rw-r--r--src/components/properties/PropertyDateTime.vue17
-rw-r--r--src/components/properties/PropertyGroups.vue45
-rw-r--r--src/components/properties/PropertyMultipleText.vue17
-rw-r--r--src/components/properties/PropertyText.vue19
-rw-r--r--src/store/addressbooks.js2
-rw-r--r--src/store/groups.js68
8 files changed, 168 insertions, 58 deletions
diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue
index f732befd..95de110d 100644
--- a/src/components/ContactDetails.vue
+++ b/src/components/ContactDetails.vue
@@ -81,24 +81,25 @@
<!-- contact details -->
<section class="contact-details">
<contact-details-property v-for="(property, index) in sortedProperties" :key="index" :index="index"
- :sorted-properties="sortedProperties" :property="property" @updatedcontact="updateContact" />
+ :sorted-properties="sortedProperties" :property="property" :contact="contact"
+ @updatedcontact="updateContact" />
</section>
</template>
</div>
</template>
<script>
-import popoverMenu from './core/popoverMenu'
-import contactDetailsProperty from './ContactDetails/ContactDetailsProperty'
-
-import Contact from '../models/contact'
-import rfcProps from '../models/rfcProps.js'
import ICAL from 'ical.js'
import ClickOutside from 'vue-click-outside'
import Vue from 'vue'
import VTooltip from 'v-tooltip'
import debounce from 'debounce'
+import Contact from '../models/contact'
+import rfcProps from '../models/rfcProps.js'
+
+import popoverMenu from './core/popoverMenu'
+import contactDetailsProperty from './ContactDetails/ContactDetailsProperty'
Vue.use(VTooltip)
diff --git a/src/components/ContactDetails/ContactDetailsProperty.vue b/src/components/ContactDetails/ContactDetailsProperty.vue
index 5b7b2e27..7aa6b0c9 100644
--- a/src/components/ContactDetails/ContactDetailsProperty.vue
+++ b/src/components/ContactDetails/ContactDetailsProperty.vue
@@ -22,14 +22,16 @@
<template>
<!-- If not in the rfcProps then we don't want to display it -->
- <component v-if="propModel && propType !== 'unknown'" :is="componentInstance" :select-type="selectType"
+ <component v-if="propModel && propType !== 'unknown'" :is="componentInstance" :select-type.sync="selectType"
:prop-model="propModel" :value.sync="value" :is-first-property="isFirstProperty"
- :class="{'property--last': isLastProperty}" @delete="deleteProp" />
+ :class="{'property--last': isLastProperty}" :contact="contact" @delete="deleteProp" />
</template>
<script>
import { Property } from 'ical.js'
import rfcProps from '../../models/rfcProps.js'
+import Contact from '../../models/contact'
+
import PropertyText from '../properties/PropertyText'
import PropertyMultipleText from '../properties/PropertyMultipleText'
import PropertyDateTime from '../properties/PropertyDateTime'
@@ -53,6 +55,10 @@ export default {
index: {
type: Number,
default: 0
+ },
+ contact: {
+ type: Contact,
+ default: null
}
},
@@ -122,16 +128,32 @@ export default {
// we only use uppercase strings
.map(str => str.toUpperCase())
- // Compare array and check if the number of exact matches
- // equals the array length to find the exact property
- return this.propModel.options.find(option => selectedType.length === option.id.split(',').reduce((matches, type) => {
- matches += selectedType.indexOf(type) > -1 ? 1 : 0
- return matches
- }, 0))
- } else if (this.type) {
+ // Compare array and score them by how many matches they have to the selected type
+ // sorting directly is cleaner but slower
+ // https://jsperf.com/array-map-and-intersection-perf
+ let matchingTypes = this.propModel.options.map(type => {
+ return {
+ type,
+ // "WORK,HOME" => ['WORK', 'HOME']
+ score: type.id.split(',').filter(value => selectedType.indexOf(value) !== -1).length
+ }
+ })
+
+ // Sort by score, filtering out the null score and selecting the first match
+ let matchingType = matchingTypes
+ .sort((a, b) => b.score - a.score)
+ .filter(type => type.score > 0)[0]
+
+ if (matchingType) {
+ return matchingType.type
+ }
+ }
+ if (this.type) {
+ // vcard 3.0 save pref alongside TYPE
+ let selectedType = this.type.filter(type => type !== 'pref').join(',')
return {
- id: this.type.join(','),
- name: this.type.join(',')
+ id: selectedType,
+ name: selectedType
}
}
return false
@@ -139,6 +161,7 @@ export default {
set(data) {
// ical.js take types as arrays
this.type = data.id.split(',')
+ this.$emit('updatedcontact')
}
},
diff --git a/src/components/properties/PropertyDateTime.vue b/src/components/properties/PropertyDateTime.vue
index b8a1c940..2d6205b4 100644
--- a/src/components/properties/PropertyDateTime.vue
+++ b/src/components/properties/PropertyDateTime.vue
@@ -27,9 +27,10 @@
<div class="property__row">
<!-- type selector -->
- <multiselect v-if="propModel.options" v-model="selectType"
+ <multiselect v-if="propModel.options" v-model="localType"
:options="propModel.options" :searchable="false" :placeholder="t('contacts', 'Select type')"
- class="multiselect-vue property__label" track-by="id" label="name" />
+ class="multiselect-vue property__label" track-by="id" label="name"
+ @input="updateType" />
<!-- if we do not support any type on our model but one is set anyway -->
<div v-else-if="selectType" class="property__label">{{ selectType.name }}</div>
@@ -41,7 +42,7 @@
<button :title="t('contacts', 'Delete')" class="property__delete icon-delete" @click="deleteProperty" />
<input v-model.trim="localValue" class="property__value" type="text"
- @input="updateProp">
+ @input="updateValue">
</div>
</div>
</template>
@@ -85,7 +86,8 @@ export default {
data() {
return {
- localValue: this.value
+ localValue: this.value,
+ localType: this.selectType
}
},
@@ -110,9 +112,14 @@ export default {
/**
* Debounce and send update event to parent
*/
- updateProp: debounce(function(e) {
+ updateValue: debounce(function(e) {
// https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
this.$emit('update:value', this.localValue)
+ }, 500),
+
+ updateType: debounce(function(e) {
+ // https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
+ this.$emit('update:selectType', this.localType)
}, 500)
}
}
diff --git a/src/components/properties/PropertyGroups.vue b/src/components/properties/PropertyGroups.vue
index 0fb0cd2b..01976b4a 100644
--- a/src/components/properties/PropertyGroups.vue
+++ b/src/components/properties/PropertyGroups.vue
@@ -25,11 +25,13 @@
<div class="property__row">
<div class="property__label">{{ propModel.readableName }}</div>
+ <!-- multiselect taggable groups with a limit to 3 groups shown -->
<multiselect v-model="localValue" :options="groups" :placeholder="t('contacts', 'Add contact in group')"
:limit="3" :multiple="true" :taggable="true"
:close-on-select="false" tag-placeholder="create" class="multiselect-vue property__value"
- @tag="createGroup" @select="addContactToGroup" @remove="removeContactToGroup">
- <span v-tooltip.auto="formatGroupsTitle" slot="limit" class="multiselect__limit">+{{ localValue.length - 2 }}</span>
+ @tag="validateGroup" @select="addContactToGroup" @remove="removeContactToGroup">
+ <!-- show how many groups are hidden and add tooltip -->
+ <span v-tooltip.auto="formatGroupsTitle" slot="limit" class="multiselect__limit">+{{ localValue.length - 3 }}</span>
<span slot="noResult">{{ t('settings', 'No results') }}</span>
</multiselect>
</div>
@@ -88,29 +90,50 @@ export default {
/**
* Debounce and send update event to parent
*/
- updateProp: debounce(function(e) {
+ updateValue: debounce(function(e) {
// https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
this.$emit('update:value', this.localValue)
}, 500),
- addContactToGroup(group) {
- console.log(group)
+ /**
+ * Dispatch contact addition to group
+ *
+ * @param {String} groupName the group name
+ */
+ addContactToGroup(groupName) {
this.$store.dispatch('addContactToGroup', {
contact: this.contact,
- group
+ groupName
})
},
- removeContactToGroup(group) {
- console.log(group)
+ /**
+ * Dispatch contact removal from group
+ *
+ * @param {String} groupName the group name
+ */
+ removeContactToGroup(groupName) {
this.$store.dispatch('removeContactToGroup', {
contact: this.contact,
- group
+ groupName
})
},
- createGroup(group) {
- console.log(group)
+ /**
+ * Validate groupname and dispatch creation
+ *
+ * @param {String} groupName the group name
+ * @returns {Boolean}
+ */
+ validateGroup(groupName) {
+ // Only allow characters without vcard special chars
+ let groupRegex = /^[^;,:]+$/gmi
+ if (groupName.match(groupRegex)) {
+ this.addContactToGroup(groupName)
+ this.localValue.push(groupName)
+ return true
+ }
+ return false
}
}
}
diff --git a/src/components/properties/PropertyMultipleText.vue b/src/components/properties/PropertyMultipleText.vue
index 3252d3da..1ac0e8ad 100644
--- a/src/components/properties/PropertyMultipleText.vue
+++ b/src/components/properties/PropertyMultipleText.vue
@@ -27,9 +27,10 @@
<div class="property__row">
<!-- type selector -->
- <multiselect v-if="propModel.options" v-model="selectType"
+ <multiselect v-if="propModel.options" v-model="localType"
:options="propModel.options" :searchable="false" :placeholder="t('contacts', 'Select type')"
- class="multiselect-vue property__label" track-by="id" label="name" />
+ class="multiselect-vue property__label" track-by="id" label="name"
+ @input="updateType" />
<!-- if we do not support any type on our model but one is set anyway -->
<div v-else-if="selectType" class="property__label">{{ selectType.name }}</div>
@@ -44,7 +45,7 @@
<div v-for="index in propModel.displayOrder" :key="index" class="property__row">
<div class="property__label">{{ propModel.readableValues[index] }}</div>
<input v-model.trim="localValue[index]" class="property__value" type="text"
- @input="updateProp">
+ @input="updateValue">
</div>
</div>
</template>
@@ -87,7 +88,8 @@ export default {
data() {
return {
- localValue: this.value
+ localValue: this.value,
+ localType: this.selectType
}
},
@@ -113,9 +115,14 @@ export default {
/**
* Debounce and send update event to parent
*/
- updateProp: debounce(function(e) {
+ updateValue: debounce(function(e) {
// https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
this.$emit('update:value', this.localValue)
+ }, 500),
+
+ updateType: debounce(function(e) {
+ // https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
+ this.$emit('update:selectType', this.localType)
}, 500)
}
}
diff --git a/src/components/properties/PropertyText.vue b/src/components/properties/PropertyText.vue
index cdd613a9..a15caa4d 100644
--- a/src/components/properties/PropertyText.vue
+++ b/src/components/properties/PropertyText.vue
@@ -27,9 +27,10 @@
<div class="property__row">
<!-- type selector -->
- <multiselect v-if="propModel.options" v-model="selectType"
+ <multiselect v-if="propModel.options" v-model="localType"
:options="propModel.options" :searchable="false" :placeholder="t('contacts', 'Select type')"
- class="multiselect-vue property__label" track-by="id" label="name" />
+ class="multiselect-vue property__label" track-by="id" label="name"
+ @input="updateType" />
<!-- if we do not support any type on our model but one is set anyway -->
<div v-else-if="selectType" class="property__label">{{ selectType.name }}</div>
@@ -40,8 +41,8 @@
<!-- delete the prop -->
<button :title="t('contacts', 'Delete')" class="property__delete icon-delete" @click="deleteProperty" />
- <input v-model.trim="localValue"class="property__value" type="text"
- @input="updateProp">
+ <input v-model.trim="localValue" class="property__value" type="text"
+ @input="updateValue">
</div>
</div>
</template>
@@ -84,7 +85,8 @@ export default {
data() {
return {
- localValue: this.value
+ localValue: this.value,
+ localType: this.selectType
}
},
@@ -108,9 +110,14 @@ export default {
/**
* Debounce and send update event to parent
*/
- updateProp: debounce(function(e) {
+ updateValue: debounce(function(e) {
// https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
this.$emit('update:value', this.localValue)
+ }, 500),
+
+ updateType: debounce(function(e) {
+ // https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
+ this.$emit('update:selectType', this.localType)
}, 500)
}
}
diff --git a/src/store/addressbooks.js b/src/store/addressbooks.js
index 301a3446..2dfe1aa8 100644
--- a/src/store/addressbooks.js
+++ b/src/store/addressbooks.js
@@ -221,7 +221,7 @@ const actions = {
context.commit('appendContactsToAddressbook', { addressbook, contacts })
context.commit('appendContacts', contacts)
context.commit('sortContacts')
- context.commit('appendGroups', contacts)
+ context.commit('appendGroupsFromContacts', contacts)
},
/**
diff --git a/src/store/groups.js b/src/store/groups.js
index a557ba76..ca261603 100644
--- a/src/store/groups.js
+++ b/src/store/groups.js
@@ -31,15 +31,17 @@ const mutations = {
* @param {Object} state
* @param {Contact[]} contacts
*/
- appendGroups(state, contacts) {
+ appendGroupsFromContacts(state, contacts) {
// init groups list
let groups = Object.values(contacts)
.map(contact => contact.groups.map(group => {
+ // extend group to model
return {
name: group,
contacts: []
}
})[0])
+ // ensure we only have one group of each
state.groups = state.groups.concat(groups)
.filter(function(group, index, self) {
return group && self.findIndex(search => search && search.name === group.name) === index
@@ -48,21 +50,49 @@ const mutations = {
Object.values(contacts)
.forEach(contact => {
if (contact.groups) {
- contact.groups.forEach(group => {
- state.groups.find(search => search.name === group).contacts.push(contact.key)
+ contact.groups.forEach(groupName => {
+ let group = state.groups.find(search => search.name === groupName)
+ group.contacts.push(contact.key)
})
}
})
},
/**
- * Add group if no exist and append contact to it
+ * Add contact to group and create groupif not existing
*
* @param {Object} state
- * @param {Contact} contact
+ * @param {Object} data
+ * @param {String} data.groupName the name of the group
+ * @param {Contact} data.contact the contact
*/
- addContactToGroup(state, { group, contact }) {
- state.groups.find(search => search.name === group).contacts.push(contact.key)
+ addContactToGroup(state, { groupName, contact }) {
+ let group = state.groups.find(search => search.name === groupName)
+ // nothing? create a new one
+ if (!group) {
+ state.groups.push({
+ name: groupName,
+ contacts: []
+ })
+ group = state.groups.find(search => search.name === groupName)
+ }
+ group.contacts.push(contact.key)
+ },
+
+ /**
+ * Remove contact from group
+ *
+ * @param {Object} state
+ * @param {Object} data
+ * @param {String} data.groupName the name of the group
+ * @param {Contact} data.contact the contact
+ */
+ removeContactToGroup(state, { groupName, contact }) {
+ let contacts = state.groups.find(search => search.name === groupName).contacts
+ let index = contacts.findIndex(search => search === contact.key)
+ if (index > -1) {
+ contacts.splice(index, 1)
+ }
}
}
@@ -73,15 +103,27 @@ const getters = {
const actions = {
/**
- * Add contact and update groups
+ * Add contact and to a group
+ *
+ * @param {Object} context
+ * @param {Object} data
+ * @param {String} data.groupName the name of the group
+ * @param {Contact} data.contact the contact
+ */
+ addContactToGroup(context, { groupName, contact }) {
+ context.commit('addContactToGroup', { groupName, contact })
+ },
+
+ /**
+ * Remove contact from group
*
* @param {Object} context
- * @param {Object} addressbook
+ * @param {Object} data
+ * @param {String} data.groupName the name of the group
+ * @param {Contact} data.contact the contact
*/
- addContact(context, contact) {
- contact.groups.forEach(group => {
- context.commit('addContactToGroup', { group, contact })
- })
+ removeContactToGroup(context, { groupName, contact }) {
+ context.commit('removeContactToGroup', { groupName, contact })
}
}