Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add property-defined actions and populate FN if unchanged or empty #898

Merged
merged 2 commits into from Jan 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 21 additions & 20 deletions css/Properties/Properties.scss
Expand Up @@ -26,8 +26,8 @@ $property-value-max-width: 250px;
.property {
@include generate-grid-span(1);
position: relative;
padding-right: 44px; // delete button
// we need this to keep the alignment of the ext and delete button
padding-right: 44px; // actions menu / button
// we need this to keep the alignment of the ext and delete/action button
// The flex grow will never go over those values. Therefore we can set
// the max width and keep the right alignment
max-width: $property-label-max-width + $property-value-max-width + 44px;
Expand All @@ -37,8 +37,8 @@ $property-value-max-width: 250px;
&--last {
margin-bottom: $grid-height-unit;
}
// no delete icon on addressbook selector
&--addressbooks &__delete {
// no delete/action icon on addressbook selector
&--addressbooks &__actions {
display: none !important;
}

Expand Down Expand Up @@ -132,17 +132,15 @@ $property-value-max-width: 250px;
&:hover,
&:focus,
&:active {
~ .property__ext,
~ .property__delete {
~ .property__ext {
opacity: .5;
}
}
}
}

// show ext & delete button on full row hover
&:hover &__ext,
&:hover &__delete {
// show ext button on full row hover
&:hover &__ext{
opacity: .5;
}

Expand All @@ -156,28 +154,31 @@ $property-value-max-width: 250px;
&:focus,
&:active {
opacity: .7;
// still show the delete button for keyboard accessibility
~ .property__delete {
opacity: .5;
}
}
}

// Delete property button
&__delete {
position: absolute;
// Delete property button + actions
&__actions {
position: absolute !important;
top: 0;
left: 100%;
width: $grid-height-unit;
height: $grid-height-unit;
margin: 0;
margin-top: -3px; // align with line because of the 44x44px size
border: 0;
background-color: transparent;
opacity: 0;
z-index: 10;
// opacity applies on the single action OR
&:not(.action-item--multiple),
&.action-item--multiple .icon-more {
opacity: 0.5;
}
&:hover,
&:active,
&:focus {
opacity: .7;
&:not(.action-item--multiple),
&.action-item--multiple .icon-more {
opacity: 0.7;
}
}
}
}
4 changes: 4 additions & 0 deletions css/icons.scss
Expand Up @@ -35,3 +35,7 @@
.icon-eye-white {
@include icon-color('eye', 'contacts', $color-white, 1);
}

.icon-up {
@include icon-color('up', 'contacts', $color-black, 1);
}
3 changes: 3 additions & 0 deletions img/up.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/components/ContactDetails.vue
Expand Up @@ -95,9 +95,11 @@
<section v-else class="contact-details">
<!-- properties iteration -->
<!-- using contact.key in the key and index as key to avoid conflicts between similar data and exact key -->
<!-- passing the debounceUpdateContact so that the contact-property component contains the function
and allow us to use it on the rfcProps since the scope is forwarded to the actions -->
<contact-property v-for="(property, index) in sortedProperties" :key="`${index}-${contact.key}-${property.name}`" :index="index"
:sorted-properties="sortedProperties" :property="property" :contact="contact"
@updatedcontact="debounceUpdateContact" />
:update-contact="debounceUpdateContact" @updatedcontact="debounceUpdateContact" />

<!-- addressbook change select - no last property because class is not applied here,
empty property because this is a required prop on regular property-select. But since
Expand Down
17 changes: 13 additions & 4 deletions src/components/ContactDetails/ContactDetailsAddNewProp.vue
Expand Up @@ -55,6 +55,15 @@ export default {
},

computed: {

/**
* Rfc props scoped
* @returns {Object}
*/
properties() {
return rfcProps.properties(this)
},

/**
* List of properties that the contact already have
*
Expand All @@ -71,14 +80,14 @@ export default {
* @returns {Object[]}
*/
availableProperties() {
return Object.keys(rfcProps.properties)
return Object.keys(this.properties)
// only allow to add multiple properties OR props that are not yet in the contact
.filter(key => rfcProps.properties[key].multiple || this.usedProperties.indexOf(key) === -1)
.filter(key => this.properties[key].multiple || this.usedProperties.indexOf(key) === -1)
// usable array of objects
.map(key => {
return {
id: key,
name: rfcProps.properties[key].readableName
name: this.properties[key].readableName
}
}).sort((a, b) => a.name.localeCompare(b.name))
}
Expand All @@ -92,7 +101,7 @@ export default {
* @param {string} data.id the id of the property. e.g fn
*/
addProp({ id }) {
let defaultData = rfcProps.properties[id].defaultValue
let defaultData = this.properties[id].defaultValue
let property = this.contact.vCard.addPropertyWithValue(id, defaultData ? defaultData.value : '')
if (defaultData && defaultData.type) {
property.setParameter('type', defaultData.type)
Expand Down
12 changes: 6 additions & 6 deletions src/components/ContactDetails/ContactDetailsAvatar.vue
@@ -1,10 +1,10 @@
<!--
import rfcProps from '../../models/rfcProps';
* @copyright Copyright (c) 2018 Team Popcorn <teampopcornberlin@gmail.com>
*
* @author Team Popcorn <teampopcornberlin@gmail.com>
*
* @license GNU AGPL version 3 or any later version
- @copyright Copyright (c) 2018 Team Popcorn <teampopcornberlin@gmail.com>
-
- @author Team Popcorn <teampopcornberlin@gmail.com>
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @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
Expand Down
12 changes: 11 additions & 1 deletion src/components/ContactDetails/ContactDetailsProperty.vue
Expand Up @@ -61,6 +61,14 @@ export default {
contact: {
type: Contact,
default: null
},
/**
* This is needed so that we can update
* the contact within the rfcProps actions
*/
updateContact: {
type: Function,
default: () => {}
}
},

Expand All @@ -81,8 +89,10 @@ export default {
},

// rfc properties list
// passing this to properties to allow us to scope the properties object
// this make possible defining actions there
properties() {
return rfcProps.properties
return rfcProps.properties(this)
},
fieldOrder() {
return rfcProps.fieldOrder
Expand Down
2 changes: 1 addition & 1 deletion src/components/ContactsList/ContactsListItem.vue
Expand Up @@ -65,7 +65,7 @@ export default {
* @returns {boolean}
*/
matchSearch() {
if (this.searchQuery !== '') {
if (this.searchQuery.trim() !== '') {
return this.contact.searchData.toString().toLowerCase().search(this.searchQuery.toLowerCase()) !== -1
}
return true
Expand Down
5 changes: 2 additions & 3 deletions src/components/Properties/PropertyDateTime.vue
Expand Up @@ -43,9 +43,8 @@
{{ propModel.readableName }}
</div>

<!-- delete the prop -->
<button v-if="!isReadOnly" :title="t('contacts', 'Delete')" class="property__delete icon-delete"
@click="deleteProperty" />
<!-- props actions -->
<action :actions="actions" class="property__actions" />

<!-- Real input where the picker shows -->
<datetime-picker :value="localValue.toJSDate()" :minute-step="10" :lang="lang"
Expand Down
5 changes: 2 additions & 3 deletions src/components/Properties/PropertyMultipleText.vue
Expand Up @@ -47,9 +47,8 @@
<input v-if="!property.isStructuredValue" v-model.trim="localValue[0]" :readonly="isReadOnly"
class="property__value" type="text" @input="updateValue">

<!-- delete the prop -->
<button v-if="!isReadOnly" :title="t('contacts', 'Delete')" class="property__delete icon-delete"
@click="deleteProperty" />
<!-- props actions -->
<action :actions="actions" class="property__actions" />
</div>

<!-- force order based on model -->
Expand Down
5 changes: 2 additions & 3 deletions src/components/Properties/PropertySelect.vue
Expand Up @@ -37,9 +37,8 @@
{{ propModel.readableName }}
</div>

<!-- delete the prop -->
<button v-if="!isReadOnly" :title="t('contacts', 'Delete')" class="property__delete icon-delete"
@click="deleteProperty" />
<!-- props actions -->
<action :actions="actions" class="property__actions" />

<multiselect v-model="matchedOptions" :options="propModel.options" :placeholder="t('contacts', 'Select option')"
:disabled="isSingleOption || isReadOnly" class="property__value" track-by="id"
Expand Down
13 changes: 3 additions & 10 deletions src/components/Properties/PropertyText.vue
Expand Up @@ -58,9 +58,8 @@
<a v-if="haveExtHandler" :href="externalHandler" class="property__ext icon-external"
target="_blank" />

<!-- delete the prop -->
<button v-if="!isReadOnly" :title="t('contacts', 'Delete')" class="property__delete icon-delete"
@click="deleteProperty" />
<!-- props actions -->
<action :actions="actions" class="property__actions" />
</div>
</div>
</template>
Expand Down Expand Up @@ -148,7 +147,7 @@ export default {
},

haveExtHandler() {
return this.externalHandler !== '' && this.value && this.value.length > 0
return this.externalHandler.trim() !== '' && this.value && this.value.length > 0
}
},

Expand All @@ -157,12 +156,6 @@ export default {
},

methods: {
/**
* Delete the property
*/
deleteProperty() {
this.$emit('delete')
},

/**
* Watch textarea resize and update the gridSize accordingly
Expand Down
3 changes: 2 additions & 1 deletion src/main.js
Expand Up @@ -27,7 +27,7 @@ import { sync } from 'vuex-router-sync'
import { generateFilePath } from 'nextcloud-server/dist/router'

/** GLOBAL COMPONENTS AND DIRECTIVE */
import { AppNavigation, DatetimePicker, Multiselect, PopoverMenu } from 'nextcloud-vue'
import { Action, AppNavigation, DatetimePicker, Multiselect, PopoverMenu } from 'nextcloud-vue'
import ClickOutside from 'vue-click-outside'
import { VTooltip } from 'v-tooltip'
import VueClipboard from 'vue-clipboard2'
Expand All @@ -43,6 +43,7 @@ __webpack_nonce__ = btoa(OC.requestToken)
// eslint-disable-next-line
__webpack_public_path__ = generateFilePath('contacts', '', 'js/')

Vue.component('Action', Action)
Vue.component('AppNavigation', AppNavigation)
Vue.component('DatetimePicker', DatetimePicker)
Vue.component('Multiselect', Multiselect)
Expand Down
13 changes: 12 additions & 1 deletion src/mixins/PropertyMixin.js
Expand Up @@ -28,7 +28,7 @@ export default {
type: [Object],
default: () => {}
},
// Coming fro the rfcProps Model
// Coming from the rfcProps Model
propModel: {
type: Object,
default: () => {},
Expand Down Expand Up @@ -76,6 +76,17 @@ export default {
}
},

computed: {
actions() {
const del = {
text: t('contacts', 'Delete'),
icon: 'icon-delete',
action: this.deleteProperty
}
return [...this.propModel.actions ? this.propModel.actions : [], del]
}
},

watch: {
/**
* Since we're updating a local data based on the value prop,
Expand Down
2 changes: 1 addition & 1 deletion src/models/contact.js
Expand Up @@ -178,7 +178,7 @@ export default class Contact {
let groupsProp = this.vCard.getFirstProperty('categories')
if (groupsProp) {
return groupsProp.getValues()
.filter(group => group !== '')
.filter(group => group.trim() !== '')
}
return []
}
Expand Down
23 changes: 20 additions & 3 deletions src/models/rfcProps.js
Expand Up @@ -21,7 +21,17 @@
*/
import { VCardTime } from 'ical.js'

const properties = {
const copyNtoFN = ({ contact, updateContact }) => () => {
if (contact.vCard.hasProperty('n')) {
// Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P.
// -> John Stevenson
const n = contact.vCard.getFirstPropertyValue('n')
contact.fullName = n.slice(0, 2).reverse().join(' ')
updateContact()
}
}

const properties = component => ({
nickname: {
readableName: t('contacts', 'Nickname'),
icon: 'icon-user'
Expand All @@ -39,7 +49,14 @@ const properties = {
defaultValue: {
value: ['', '', '', '', '']
},
icon: 'icon-user'
icon: 'icon-user',
actions: [
{
text: t('contacts', 'Copy to full name'),
icon: 'icon-up',
action: copyNtoFN(component)
}
]
},
note: {
readableName: t('contacts', 'Notes'),
Expand Down Expand Up @@ -266,7 +283,7 @@ const properties = {
{ id: 'O', name: t('contacts', 'Other') }
]
}
}
})

const fieldOrder = [
'org',
Expand Down