Allow to toggle year
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
88510f76d3
commit
5a1b11179d
|
@ -27,6 +27,7 @@ module.exports = {
|
||||||
'plugin:node/recommended',
|
'plugin:node/recommended',
|
||||||
'plugin:vue/essential',
|
'plugin:vue/essential',
|
||||||
'plugin:vue/recommended',
|
'plugin:vue/recommended',
|
||||||
|
'plugin:nextcloud/recommended',
|
||||||
'standard'
|
'standard'
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -73,8 +74,14 @@ module.exports = {
|
||||||
// es6 import/export and require
|
// es6 import/export and require
|
||||||
'node/no-unpublished-require': ['off'],
|
'node/no-unpublished-require': ['off'],
|
||||||
'node/no-unsupported-features/es-syntax': ['off'],
|
'node/no-unsupported-features/es-syntax': ['off'],
|
||||||
// kebab case components for vuejs
|
// PascalCase components names for vuejs
|
||||||
|
// https://vuejs.org/v2/style-guide/#Single-file-component-filename-casing-strongly-recommended
|
||||||
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
|
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
|
||||||
|
// force name
|
||||||
|
'vue/match-component-file-name': ['error', {
|
||||||
|
'extensions': ['jsx', 'vue', 'js'],
|
||||||
|
'shouldMatchCase': true
|
||||||
|
}],
|
||||||
// space before self-closing elements
|
// space before self-closing elements
|
||||||
'vue/html-closing-bracket-spacing': 'error',
|
'vue/html-closing-bracket-spacing': 'error',
|
||||||
// no ending html tag on a new line
|
// no ending html tag on a new line
|
||||||
|
|
|
@ -20,29 +20,14 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.icon-social {
|
|
||||||
@include icon-color('social', 'contacts', $color-black, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-qrcode {
|
@include icon-black-white('social', 'contacts', 1);
|
||||||
@include icon-color('qrcode', 'contacts', $color-black, 2);
|
@include icon-black-white('qrcode', 'contacts', 1);
|
||||||
}
|
@include icon-black-white('address-book', 'contacts', 1);
|
||||||
|
@include icon-black-white('phone', 'contacts', 1);
|
||||||
.icon-address-book {
|
@include icon-black-white('eye', 'contacts', 1);
|
||||||
@include icon-color('address-book', 'contacts', $color-black, 1);
|
@include icon-black-white('up', 'contacts', 1);
|
||||||
}
|
@include icon-black-white('no-calendar', 'contacts', 1);
|
||||||
|
|
||||||
.icon-phone {
|
|
||||||
@include icon-color('phone', 'contacts', $color-black, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-eye-white {
|
|
||||||
@include icon-color('eye', 'contacts', $color-white, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-up {
|
|
||||||
@include icon-color('up', 'contacts', $color-black, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-up-force-white {
|
.icon-up-force-white {
|
||||||
// using #fffffe to trick the accessibility dark theme icon invert
|
// using #fffffe to trick the accessibility dark theme icon invert
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M4 1c-.5 0-1 .5-1 1v2c0 .5.5 1 1 1s1-.5 1-1V2c0-.5-.5-1-1-1zm8 0c-.5 0-1 .5-1 1v2c0 .223.11.439.264.617L13 2.881V2c0-.5-.5-1-1-1zM5.5 3v1c0 .831-.5 1.5-1.5 1.5S2.5 5 2.5 4v-.938A1.998 1.998 0 0 0 1 5v8c0 .524.202.996.53 1.352L3 12.88V8h4.88l2.942-2.941C10.61 4.81 10.5 4.46 10.5 4V3h-5zM15 5.123L12.123 8H13v5H7.123l-2 2H13c1.108 0 2-.892 2-2V5.123z"/><path d="M13.773 2.756L1.903 14.627 3.274 16 15.146 4.129z" paint-order="fill markers stroke"/></svg>
|
After Width: | Height: | Size: 525 B |
|
@ -4053,6 +4053,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"eslint-plugin-nextcloud": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-nextcloud/-/eslint-plugin-nextcloud-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-LUD2qdirGL0BRt4uaMDGxen17mWVq9JwuGDt7P7Celz7bzdu0X48RrS8mhXn9e0w78+nYN5kPoULG2Bw04r4HA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"requireindex": "~1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eslint-plugin-node": {
|
"eslint-plugin-node": {
|
||||||
"version": "9.2.0",
|
"version": "9.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-9.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-9.2.0.tgz",
|
||||||
|
@ -5763,7 +5772,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz",
|
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz",
|
||||||
"integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==",
|
"integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -8810,7 +8819,7 @@
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -9127,6 +9136,12 @@
|
||||||
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
|
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"requireindex": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.8.1",
|
"version": "1.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
|
||||||
|
@ -9946,7 +9961,7 @@
|
||||||
},
|
},
|
||||||
"stream-browserify": {
|
"stream-browserify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||||
"integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
|
"integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -10012,7 +10027,7 @@
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -10752,7 +10767,7 @@
|
||||||
},
|
},
|
||||||
"tty-browserify": {
|
"tty-browserify": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||||
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
|
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
"nextcloud-dialogs": "0.0.3",
|
"nextcloud-dialogs": "0.0.3",
|
||||||
"nextcloud-l10n": "0.1.0",
|
"nextcloud-l10n": "0.1.0",
|
||||||
"nextcloud-router": "0.0.8",
|
"nextcloud-router": "0.0.8",
|
||||||
"nextcloud-vue": "^0.12.1",
|
"nextcloud-vue": "^0.12.2",
|
||||||
"p-limit": "^2.2.1",
|
"p-limit": "^2.2.1",
|
||||||
"p-queue": "^6.1.1",
|
"p-queue": "^6.1.1",
|
||||||
"qr-image": "^3.2.0",
|
"qr-image": "^3.2.0",
|
||||||
|
@ -81,6 +81,7 @@
|
||||||
"eslint-import-resolver-webpack": "^0.11.1",
|
"eslint-import-resolver-webpack": "^0.11.1",
|
||||||
"eslint-loader": "^3.0.0",
|
"eslint-loader": "^3.0.0",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.18.2",
|
||||||
|
"eslint-plugin-nextcloud": "^0.3.0",
|
||||||
"eslint-plugin-node": "^9.2.0",
|
"eslint-plugin-node": "^9.2.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.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
|
||||||
|
- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ActionButton icon="icon-up" @click="copyNtoFN">
|
||||||
|
{{ t('contacts', 'Copy to full name') }}
|
||||||
|
</ActionButton>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { ActionButton } from 'nextcloud-vue'
|
||||||
|
import ActionsMixin from 'Mixins/ActionsMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ActionCopyNtoFN',
|
||||||
|
components: {
|
||||||
|
ActionButton
|
||||||
|
},
|
||||||
|
mixins: [ActionsMixin],
|
||||||
|
methods: {
|
||||||
|
copyNToFN() {
|
||||||
|
console.info(this.component)
|
||||||
|
if (this.component.contact.vCard.hasProperty('n')) {
|
||||||
|
// Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P.
|
||||||
|
// -> John Stevenson
|
||||||
|
const n = this.component.contact.vCard.getFirstPropertyValue('n')
|
||||||
|
this.component.contact.fullName = n.slice(0, 2).reverse().join(' ')
|
||||||
|
this.component.updateContact()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,93 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.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
|
||||||
|
- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ActionButton :icon="icon" @click="toggleYear">
|
||||||
|
{{ omitYear ? t('contacts', 'Add year') : t('contacts', 'Omit year') }}
|
||||||
|
</ActionButton>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { ActionButton } from 'nextcloud-vue'
|
||||||
|
import ActionsMixin from 'Mixins/ActionsMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ActionToggleYear',
|
||||||
|
components: {
|
||||||
|
ActionButton
|
||||||
|
},
|
||||||
|
mixins: [ActionsMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
omitYear: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
icon() {
|
||||||
|
return this.omitYear
|
||||||
|
? 'icon-calendar-dark'
|
||||||
|
: 'icon-no-calendar'
|
||||||
|
},
|
||||||
|
text() {
|
||||||
|
return this.omitYear
|
||||||
|
? t('contacts', 'Add year')
|
||||||
|
: t('contacts', 'Omit year')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeMount() {
|
||||||
|
this.omitYear = !!this.component.property.getFirstParameter('x-apple-omit-year')
|
||||||
|
|| !this.component.value.year // if null
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleYear() {
|
||||||
|
const dateObject = this.component.localValue.toJSON()
|
||||||
|
|
||||||
|
// year was already ignored: adding it back
|
||||||
|
if (this.omitYear) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.component.updateValue(dateObject, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
} else if (this.component.localContact.version === '4.0') {
|
||||||
|
// year was already displayed: removing it
|
||||||
|
// and use --0124 format
|
||||||
|
dateObject.year = null
|
||||||
|
this.component.updateValue(dateObject)
|
||||||
|
} else {
|
||||||
|
// --0124 format is only for vcards 4.0
|
||||||
|
// using x-apple-omit-year custom parameter
|
||||||
|
const year = this.component.value.year
|
||||||
|
if (this.component.value.year) {
|
||||||
|
this.component.property.setParameter('x-apple-omit-year', parseInt(year).toString())
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.component.updateValue(dateObject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.omitYear = !this.omitYear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -128,7 +128,7 @@
|
||||||
:prop-model="addressbookModel" :value.sync="addressbook"
|
:prop-model="addressbookModel" :value.sync="addressbook"
|
||||||
:is-first-property="true" :is-last-property="true"
|
:is-first-property="true" :is-last-property="true"
|
||||||
:property="{}"
|
:property="{}"
|
||||||
class="property--addressbooks property--last" />
|
class="property--addressbooks property--last property--without-actions" />
|
||||||
|
|
||||||
<!-- Groups always visible -->
|
<!-- Groups always visible -->
|
||||||
<PropertyGroups :prop-model="groupsModel" :value.sync="groups" :contact="contact"
|
<PropertyGroups :prop-model="groupsModel" :value.sync="groups" :contact="contact"
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid-span-3 property property--last">
|
<div class="grid-span-3 property property--without-actions property--last">
|
||||||
<!-- title -->
|
<!-- title -->
|
||||||
<PropertyTitle :icon="'icon-add'" :readable-name="t('contacts', 'Add new property')" />
|
<PropertyTitle :icon="'icon-add'" :readable-name="t('contacts', 'Add new property')" />
|
||||||
|
|
||||||
|
@ -57,11 +57,11 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rfc props scoped
|
* Rfc props
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
properties() {
|
properties() {
|
||||||
return rfcProps.properties(this)
|
return rfcProps.properties
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -82,7 +82,7 @@ import { generateRemoteUrl } from 'nextcloud-router'
|
||||||
const axios = () => import('axios')
|
const axios = () => import('axios')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContactAvatar',
|
name: 'ContactDetailsAvatar',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ActionLink,
|
ActionLink,
|
||||||
|
|
|
@ -22,11 +22,11 @@
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- If not in the rfcProps then we don't want to display it -->
|
<!-- If not in the rfcProps then we don't want to display it -->
|
||||||
<component :is="componentInstance" v-if="propModel && propType !== 'unknown'" :select-type.sync="selectType"
|
<component :is="componentInstance" v-if="propModel && propType !== 'unknown'" ref="component"
|
||||||
:prop-model="propModel" :value.sync="value" :is-first-property="isFirstProperty"
|
:select-type.sync="selectType" :prop-model="propModel" :value.sync="value"
|
||||||
:property="property" :is-last-property="isLastProperty" :class="{'property--last': isLastProperty}"
|
:is-first-property="isFirstProperty" :property="property" :is-last-property="isLastProperty"
|
||||||
:local-contact="localContact" :prop-name="propName" :prop-type="propType"
|
:class="{'property--last': isLastProperty}" :local-contact="localContact" :prop-name="propName"
|
||||||
:options="sortedModelOptions" :is-read-only="isReadOnly"
|
:prop-type="propType" :options="sortedModelOptions" :is-read-only="isReadOnly"
|
||||||
@delete="deleteProp" @update="updateContact" />
|
@delete="deleteProp" @update="updateContact" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -93,10 +93,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
// rfc properties list
|
// rfc properties list
|
||||||
// passing this to properties to allow us to scope the properties object
|
|
||||||
// this make possible defining actions there
|
|
||||||
properties() {
|
properties() {
|
||||||
return rfcProps.properties(this)
|
return rfcProps.properties
|
||||||
},
|
},
|
||||||
fieldOrder() {
|
fieldOrder() {
|
||||||
return rfcProps.fieldOrder
|
return rfcProps.fieldOrder
|
||||||
|
|
|
@ -25,10 +25,8 @@
|
||||||
<ActionButton icon="icon-delete" @click="deleteProperty">
|
<ActionButton icon="icon-delete" @click="deleteProperty">
|
||||||
{{ t('contacts', 'Delete') }}
|
{{ t('contacts', 'Delete') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton v-for="(action, index) in actions" :key="index"
|
<actions :is="action" v-for="(action, index) in actions" :key="index"
|
||||||
:icon="action.icon" @click="action.action">
|
:component="propertyComponent" />
|
||||||
{{ action.text }}
|
|
||||||
</ActionButton>
|
|
||||||
</Actions>
|
</Actions>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -46,6 +44,10 @@ export default {
|
||||||
actions: {
|
actions: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
|
},
|
||||||
|
propertyComponent: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -43,14 +43,14 @@
|
||||||
{{ propModel.readableName }}
|
{{ propModel.readableName }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- props actions -->
|
|
||||||
<PropertyActions :actions="actions" @delete="deleteProperty" />
|
|
||||||
|
|
||||||
<!-- Real input where the picker shows -->
|
<!-- Real input where the picker shows -->
|
||||||
<DatetimePicker :value="vcardTimeLocalValue.toJSDate()" :minute-step="10" :lang="lang"
|
<DatetimePicker :value="vcardTimeLocalValue.toJSDate()" :minute-step="10" :lang="lang"
|
||||||
:clearable="false" :first-day-of-week="firstDay" :type="inputType"
|
:clearable="false" :first-day-of-week="firstDay" :type="inputType"
|
||||||
:readonly="isReadOnly" :format="dateFormat" class="property__value"
|
:readonly="isReadOnly" :format="dateFormat" class="property__value"
|
||||||
confirm @confirm="updateValue" />
|
confirm @confirm="debounceUpdateValue" />
|
||||||
|
|
||||||
|
<!-- props actions -->
|
||||||
|
<PropertyActions :actions="actions" :property-component="this" @delete="deleteProperty" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -159,42 +159,67 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Debounce and send update event to parent
|
* Debounce and send update event to parent
|
||||||
*/
|
*/
|
||||||
updateValue: debounce(function(e) {
|
debounceUpdateValue: debounce(function(date) {
|
||||||
const objMap = ['year', 'month', 'day', 'hour', 'minute', 'second']
|
const objMap = ['year', 'month', 'day', 'hour', 'minute', 'second']
|
||||||
let rawArray = moment(e).toArray()
|
const rawArray = moment(date).toArray()
|
||||||
|
|
||||||
const rawObject = rawArray.reduce((acc, cur, index) => {
|
let dateObject = rawArray.reduce((acc, cur, index) => {
|
||||||
acc[objMap[index]] = cur
|
acc[objMap[index]] = cur
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the current year to ensure we do not lose
|
|
||||||
* the year data on v4.0 since we currently have
|
|
||||||
* no options to remove the year selection.
|
|
||||||
* ! using this.value since this.localValue reflect the current change
|
|
||||||
* ! so we need to make sure we do not use the updated data
|
|
||||||
* TODO: add option to omit year and not use already existing data
|
|
||||||
*/
|
|
||||||
if (this.value.year === null) {
|
|
||||||
rawObject.year = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VCardTime starts months at 1
|
* VCardTime starts months at 1
|
||||||
* but moment and js starts at 0
|
* but moment and js starts at 0
|
||||||
* ! since we use moment to generate our time array
|
* ! since we use moment to generate our time array
|
||||||
* ! we need to make sure the conversion to VCardTime is done well
|
* ! we need to make sure the conversion to VCardTime is done well
|
||||||
*/
|
*/
|
||||||
rawObject.month++
|
dateObject.month++
|
||||||
|
|
||||||
|
this.updateValue(dateObject)
|
||||||
|
}, 500),
|
||||||
|
|
||||||
|
updateValue(dateObject, forceYear) {
|
||||||
|
const ignoreYear = this.property.getParameter('x-apple-omit-year')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If forceYear, we add back the year!
|
||||||
|
* taken from x-apple-omit-year parameter
|
||||||
|
* of from the current year if we don't have
|
||||||
|
* any other appropriate year data
|
||||||
|
*/
|
||||||
|
if (forceYear) {
|
||||||
|
this.property.removeParameter('x-apple-omit-year')
|
||||||
|
dateObject.year = parseInt(ignoreYear) ? ignoreYear : moment().year()
|
||||||
|
} else
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the current year to ensure we do not lose
|
||||||
|
* the year data on v4.0 since we currently have
|
||||||
|
* no options to remove the year selection.
|
||||||
|
* ! using this.value since this.localValue reflect the current change
|
||||||
|
* ! so we need to make sure we do not use the updated data
|
||||||
|
* If we force the removal of the year (vcard 4.0 only)
|
||||||
|
* year is still valid on the apple format x-apple-omit-year
|
||||||
|
*/
|
||||||
|
if (!this.value.year) {
|
||||||
|
dateObject.year = null
|
||||||
|
} else
|
||||||
|
|
||||||
|
// Apple style omit year parameter
|
||||||
|
// if year changed and we were already
|
||||||
|
// ignoring the year, we update the parameter
|
||||||
|
if (ignoreYear && dateObject.year) {
|
||||||
|
this.property.setParameter('x-apple-omit-year', parseInt(dateObject.year).toString())
|
||||||
|
}
|
||||||
|
|
||||||
// reset the VCardTime component to the selected date/time
|
// reset the VCardTime component to the selected date/time
|
||||||
this.localValue = new VCardTime(rawObject, null, this.propType)
|
this.localValue = new VCardTime(dateObject, null, this.propType)
|
||||||
|
|
||||||
// https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
|
// https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
|
||||||
// Use moment to convert the JsDate to Object
|
// Use moment to convert the JsDate to Object
|
||||||
this.$emit('update:value', this.localValue)
|
this.$emit('update:value', this.localValue)
|
||||||
}, 500),
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format time with locale to display only
|
* Format time with locale to display only
|
||||||
|
@ -211,6 +236,11 @@ export default {
|
||||||
let datetimeData = this.vcardTimeLocalValue.toJSON()
|
let datetimeData = this.vcardTimeLocalValue.toJSON()
|
||||||
let datetime = ''
|
let datetime = ''
|
||||||
|
|
||||||
|
const ignoreYear = this.property.getParameter('x-apple-omit-year')
|
||||||
|
if (ignoreYear) {
|
||||||
|
datetimeData.year = null
|
||||||
|
}
|
||||||
|
|
||||||
// FUN FACT: JS date starts month at zero!
|
// FUN FACT: JS date starts month at zero!
|
||||||
datetimeData.month--
|
datetimeData.month--
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="propModel" class="grid-span-2 property">
|
<div v-if="propModel" class="grid-span-2 property property--without-actions">
|
||||||
<!-- NO title if first element for groups -->
|
<!-- NO title if first element for groups -->
|
||||||
|
|
||||||
<div class="property__row">
|
<div class="property__row">
|
||||||
|
|
|
@ -45,12 +45,14 @@
|
||||||
{{ isFirstProperty ? '' : propModel.readableName }}
|
{{ isFirstProperty ? '' : propModel.readableName }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- show the first input if not -->
|
<!-- show the first input if not a structured value -->
|
||||||
<input v-if="!property.isStructuredValue" v-model.trim="localValue[0]" :readonly="isReadOnly"
|
<input v-if="!property.isStructuredValue" v-model.trim="localValue[0]" :readonly="isReadOnly"
|
||||||
class="property__value" type="text" @input="updateValue">
|
class="property__value" type="text" @input="updateValue">
|
||||||
|
|
||||||
<!-- props actions -->
|
<!-- props actions -->
|
||||||
<PropertyActions :actions="actions" @delete="deleteProperty" />
|
<PropertyActions class="property__actions--floating"
|
||||||
|
:actions="actions" :property-component="this"
|
||||||
|
@delete="deleteProperty" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- force order based on model -->
|
<!-- force order based on model -->
|
||||||
|
@ -82,7 +84,7 @@ import PropertyTitle from './PropertyTitle'
|
||||||
import PropertyActions from './PropertyActions'
|
import PropertyActions from './PropertyActions'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PropertyText',
|
name: 'PropertyMultipleText',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
PropertyTitle,
|
PropertyTitle,
|
||||||
|
|
|
@ -37,12 +37,12 @@
|
||||||
{{ propModel.readableName }}
|
{{ propModel.readableName }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- props actions -->
|
|
||||||
<PropertyActions :actions="actions" @delete="deleteProperty" />
|
|
||||||
|
|
||||||
<multiselect v-model="matchedOptions" :options="propModel.options" :placeholder="t('contacts', 'Select option')"
|
<multiselect v-model="matchedOptions" :options="propModel.options" :placeholder="t('contacts', 'Select option')"
|
||||||
:disabled="isSingleOption || isReadOnly" class="property__value" track-by="id"
|
:disabled="isSingleOption || isReadOnly" class="property__value" track-by="id"
|
||||||
label="name" @input="updateValue" />
|
label="name" @input="updateValue" />
|
||||||
|
|
||||||
|
<!-- props actions -->
|
||||||
|
<PropertyActions :actions="actions" :property-component="this" @delete="deleteProperty" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
target="_blank" />
|
target="_blank" />
|
||||||
|
|
||||||
<!-- props actions -->
|
<!-- props actions -->
|
||||||
<PropertyActions :actions="actions" @delete="deleteProperty" />
|
<PropertyActions :actions="actions" :property-component="this" @delete="deleteProperty" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -52,7 +52,7 @@ import addressBookSharee from './SettingsAddressbookSharee'
|
||||||
import debounce from 'debounce'
|
import debounce from 'debounce'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SettingsShareAddressbook',
|
name: 'SettingsAddressbookShare',
|
||||||
components: {
|
components: {
|
||||||
addressBookSharee
|
addressBookSharee
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SettingsShareSharee',
|
name: 'SettingsAddressbookSharee',
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
addressbook: {
|
addressbook: {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable vue/match-component-file-name */
|
||||||
/**
|
/**
|
||||||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
*
|
*
|
||||||
|
@ -24,7 +25,7 @@ import 'core-js/stable'
|
||||||
import 'regenerator-runtime/runtime'
|
import 'regenerator-runtime/runtime'
|
||||||
|
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App'
|
import App from './ContactsRoot'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import { sync } from 'vuex-router-sync'
|
import { sync } from 'vuex-router-sync'
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.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
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
// The current component root
|
||||||
|
component: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -89,6 +89,9 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
actions() {
|
actions() {
|
||||||
return this.propModel.actions ? this.propModel.actions : []
|
return this.propModel.actions ? this.propModel.actions : []
|
||||||
|
},
|
||||||
|
haveAction() {
|
||||||
|
return this.actions && this.actions.length > 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -20,18 +20,10 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import { VCardTime } from 'ical.js'
|
import { VCardTime } from 'ical.js'
|
||||||
|
import ActionCopyNtoFN from '../components/Actions/ActionCopyNtoFN'
|
||||||
|
import ActionToggleYear from '../components/Actions/ActionToggleYear'
|
||||||
|
|
||||||
const copyNtoFN = ({ contact, updateContact }) => () => {
|
const properties = {
|
||||||
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: {
|
nickname: {
|
||||||
readableName: t('contacts', 'Nickname'),
|
readableName: t('contacts', 'Nickname'),
|
||||||
icon: 'icon-user'
|
icon: 'icon-user'
|
||||||
|
@ -51,11 +43,7 @@ const properties = component => ({
|
||||||
},
|
},
|
||||||
icon: 'icon-user',
|
icon: 'icon-user',
|
||||||
actions: [
|
actions: [
|
||||||
{
|
ActionCopyNtoFN
|
||||||
text: t('contacts', 'Copy to full name'),
|
|
||||||
icon: 'icon-up',
|
|
||||||
action: copyNtoFN(component)
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
|
@ -113,7 +101,10 @@ const properties = component => ({
|
||||||
force: 'date', // most ppl prefer date for birthdays, time is usually irrelevant
|
force: 'date', // most ppl prefer date for birthdays, time is usually irrelevant
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
value: new VCardTime(null, null, 'date').fromJSDate(new Date())
|
value: new VCardTime(null, null, 'date').fromJSDate(new Date())
|
||||||
}
|
},
|
||||||
|
actions: [
|
||||||
|
ActionToggleYear
|
||||||
|
]
|
||||||
},
|
},
|
||||||
anniversary: {
|
anniversary: {
|
||||||
readableName: t('contacts', 'Anniversary'),
|
readableName: t('contacts', 'Anniversary'),
|
||||||
|
@ -289,7 +280,7 @@ const properties = component => ({
|
||||||
{ id: 'U', name: t('contacts', 'Unknown') }
|
{ id: 'U', name: t('contacts', 'Unknown') }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
const fieldOrder = [
|
const fieldOrder = [
|
||||||
'org',
|
'org',
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
||||||
fix: contact => {
|
fix: contact => {
|
||||||
const gender = contact.vCard.getFirstProperty('gender')
|
const gender = contact.vCard.getFirstProperty('gender')
|
||||||
const type = gender.getFirstParameter('type')
|
const type = gender.getFirstParameter('type')
|
||||||
const option = Object.values(rfcProps.properties({}).gender.options).find(opt => opt.id === type)
|
const option = Object.values(rfcProps.properties.gender.options).find(opt => opt.id === type)
|
||||||
if (option) {
|
if (option) {
|
||||||
gender.removeParameter('type')
|
gender.removeParameter('type')
|
||||||
gender.setValue(option.id)
|
gender.setValue(option.id)
|
||||||
|
|
|
@ -324,7 +324,7 @@ export default {
|
||||||
contact.rev = rev
|
contact.rev = rev
|
||||||
|
|
||||||
// itterate over all properties (filter is not usable on objects and we need the key of the property)
|
// itterate over all properties (filter is not usable on objects and we need the key of the property)
|
||||||
const properties = rfcProps.properties(this)
|
const properties = rfcProps.properties
|
||||||
for (let name in properties) {
|
for (let name in properties) {
|
||||||
if (properties[name].default) {
|
if (properties[name].default) {
|
||||||
let defaultData = properties[name].defaultValue
|
let defaultData = properties[name].defaultValue
|
||||||
|
|
Loading…
Reference in New Issue