summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2019-01-30 11:16:29 +0100
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2019-01-30 12:17:23 +0100
commita1561dfe3a728f3b636852df05d36c1947883dd7 (patch)
tree91e90dae3ec08878ae81a2f68a90fba4050ebfc2
parent7af5d37ac8428784843fd3cca531fb30ecf71fe3 (diff)
Add property-defined actions
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
-rw-r--r--css/Properties/Properties.scss41
-rw-r--r--css/icons.scss4
-rw-r--r--img/up.svg3
-rw-r--r--package-lock.json8
-rw-r--r--src/components/ContactDetails.vue4
-rw-r--r--src/components/ContactDetails/ContactDetailsProperty.vue12
-rw-r--r--src/components/Properties/PropertyDateTime.vue5
-rw-r--r--src/components/Properties/PropertyMultipleText.vue5
-rw-r--r--src/components/Properties/PropertySelect.vue5
-rw-r--r--src/components/Properties/PropertyText.vue11
-rw-r--r--src/main.js3
-rw-r--r--src/mixins/PropertyMixin.js11
-rw-r--r--src/models/rfcProps.js23
-rw-r--r--src/services/checks/missingFN.js4
-rw-r--r--src/store/contacts.js4
15 files changed, 95 insertions, 48 deletions
diff --git a/css/Properties/Properties.scss b/css/Properties/Properties.scss
index 55ad6c99..a777054f 100644
--- a/css/Properties/Properties.scss
+++ b/css/Properties/Properties.scss
@@ -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;
@@ -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;
}
@@ -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;
}
@@ -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;
+ }
}
}
}
diff --git a/css/icons.scss b/css/icons.scss
index 5549a162..41cc93b3 100644
--- a/css/icons.scss
+++ b/css/icons.scss
@@ -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);
+}
diff --git a/img/up.svg b/img/up.svg
new file mode 100644
index 00000000..fa3ecbb1
--- /dev/null
+++ b/img/up.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+ <path d="M10 15H6V8H1l7-7 7 7h-5z"/>
+</svg>
diff --git a/package-lock.json b/package-lock.json
index 597691c0..b77bc877 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2042,7 +2042,7 @@
},
"cdav-library": {
"version": "github:nextcloud/cdav-library#37cfc379b050deff8cb15407d5652098c350bff0",
- "from": "github:nextcloud/cdav-library#37cfc379b050deff8cb15407d5652098c350bff0",
+ "from": "github:nextcloud/cdav-library",
"requires": {
"@babel/polyfill": "^7.2.5"
}
@@ -10242,9 +10242,9 @@
"dev": true
},
"vue2-datepicker": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/vue2-datepicker/-/vue2-datepicker-2.8.0.tgz",
- "integrity": "sha512-IQw/ai04WYGZC4P7toLhryBHhrqc1hCJ3ivgEayXfeDD1EjJnIhUMb4nqo2JvRG72nPqc9HJvHM/66K+AZnjUA==",
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/vue2-datepicker/-/vue2-datepicker-2.9.0.tgz",
+ "integrity": "sha512-THS5uCcWAPSU8OAqeie+Jie+7aw23WVbptfR5x1A8YFn4IizgWvttXoLpgwKmwwuJ59O36eoY0aeP/ugsK1RCQ==",
"requires": {
"fecha": "^2.3.3"
}
diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue
index ab55c221..1bc9b3cf 100644
--- a/src/components/ContactDetails.vue
+++ b/src/components/ContactDetails.vue
@@ -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
diff --git a/src/components/ContactDetails/ContactDetailsProperty.vue b/src/components/ContactDetails/ContactDetailsProperty.vue
index f6bab167..35db5032 100644
--- a/src/components/ContactDetails/ContactDetailsProperty.vue
+++ b/src/components/ContactDetails/ContactDetailsProperty.vue
@@ -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: () => {}
}
},
@@ -139,7 +147,9 @@ export default {
* @returns {Object}
*/
propModel() {
- return this.properties[this.propName]
+ // passing this to properties to allow us to scope the properties object
+ // this make possible defining actions there
+ return this.properties(this)[this.propName]
},
/**
diff --git a/src/components/Properties/PropertyDateTime.vue b/src/components/Properties/PropertyDateTime.vue
index 96c72f58..ff2df7e8 100644
--- a/src/components/Properties/PropertyDateTime.vue
+++ b/src/components/Properties/PropertyDateTime.vue
@@ -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"
diff --git a/src/components/Properties/PropertyMultipleText.vue b/src/components/Properties/PropertyMultipleText.vue
index b2220e03..1408e3ff 100644
--- a/src/components/Properties/PropertyMultipleText.vue
+++ b/src/components/Properties/PropertyMultipleText.vue
@@ -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 -->
diff --git a/src/components/Properties/PropertySelect.vue b/src/components/Properties/PropertySelect.vue
index 51ccd032..69593f8c 100644
--- a/src/components/Properties/PropertySelect.vue
+++ b/src/components/Properties/PropertySelect.vue
@@ -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"
diff --git a/src/components/Properties/PropertyText.vue b/src/components/Properties/PropertyText.vue
index b61df21d..447775ab 100644
--- a/src/components/Properties/PropertyText.vue
+++ b/src/components/Properties/PropertyText.vue
@@ -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>
@@ -157,12 +156,6 @@ export default {
},
methods: {
- /**
- * Delete the property
- */
- deleteProperty() {
- this.$emit('delete')
- },
/**
* Watch textarea resize and update the gridSize accordingly
diff --git a/src/main.js b/src/main.js
index 1cbb209c..e0264e5f 100644
--- a/src/main.js
+++ b/src/main.js
@@ -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'
@@ -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)
diff --git a/src/mixins/PropertyMixin.js b/src/mixins/PropertyMixin.js
index 73b3f21c..cdb166f2 100644
--- a/src/mixins/PropertyMixin.js
+++ b/src/mixins/PropertyMixin.js
@@ -76,6 +76,17 @@ export default {
}
},
+ computed: {
+ actions() {
+ const del = {
+ text: t('contacts', 'Delete'),
+ icon: 'icon-delete',
+ action: this.deleteProperty
+ }
+ return [del, ...this.propModel.actions ? this.propModel.actions : []]
+ }
+ },
+
watch: {
/**
* Since we're updating a local data based on the value prop,
diff --git a/src/models/rfcProps.js b/src/models/rfcProps.js
index 122736f2..79c891e6 100644
--- a/src/models/rfcProps.js
+++ b/src/models/rfcProps.js
@@ -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'
@@ -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'),
@@ -266,7 +283,7 @@ const properties = {
{ id: 'O', name: t('contacts', 'Other') }
]
}
-}
+})
const fieldOrder = [
'org',
diff --git a/src/services/checks/missingFN.js b/src/services/checks/missingFN.js
index 1f17afc8..449f4a43 100644
--- a/src/services/checks/missingFN.js
+++ b/src/services/checks/missingFN.js
@@ -29,6 +29,10 @@ export default {
run: contact => {
return !contact.vCard.hasProperty('fn') // No FN
|| contact.vCard.getFirstPropertyValue('fn') === '' // Empty FN
+ || ( // we don't want to fix newly created contacts
+ contact.dav // Existing contact
+ && contact.vCard.getFirstPropertyValue('fn') === t('contacts', 'New contact') // AND Unchanged FN
+ )
},
fix: contact => {
if (contact.vCard.hasProperty('n')) {
diff --git a/src/store/contacts.js b/src/store/contacts.js
index 5258d861..d5538b6e 100644
--- a/src/store/contacts.js
+++ b/src/store/contacts.js
@@ -285,6 +285,10 @@ const actions = {
* @returns {Promise}
*/
async updateContact(context, contact) {
+
+ // Checking contact validity 🙈
+ validate(contact)
+
let vData = ICAL.stringify(contact.vCard.jCal)
// if no dav key, contact does not exists on server