From 5a1b11179d9f0b82d93ff2108751d203aa96556e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Fri, 30 Aug 2019 13:56:37 +0200 Subject: [PATCH] Allow to toggle year MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- .eslintrc.js | 9 +- css/icons.scss | 29 ++---- img/no-calendar.svg | 1 + package-lock.json | 25 ++++- package.json | 3 +- src/{App.vue => ContactsRoot.vue} | 0 src/components/Actions/ActionCopyNtoFN.vue | 51 ++++++++++ src/components/Actions/ActionToggleYear.vue | 93 +++++++++++++++++++ src/components/ContactDetails.vue | 2 +- .../ContactDetailsAddNewProp.vue | 6 +- .../ContactDetails/ContactDetailsAvatar.vue | 2 +- .../ContactDetails/ContactDetailsProperty.vue | 14 ++- src/components/Properties/PropertyActions.vue | 10 +- .../Properties/PropertyDateTime.vue | 74 ++++++++++----- src/components/Properties/PropertyGroups.vue | 2 +- .../Properties/PropertyMultipleText.vue | 8 +- src/components/Properties/PropertySelect.vue | 6 +- src/components/Properties/PropertyText.vue | 2 +- .../Settings/SettingsAddressbookShare.vue | 2 +- .../Settings/SettingsAddressbookSharee.vue | 2 +- src/main.js | 3 +- src/mixins/ActionsMixin.js | 32 +++++++ src/mixins/PropertyMixin.js | 3 + src/models/rfcProps.js | 27 ++---- src/services/checks/badGenderType.js | 2 +- src/views/Contacts.vue | 2 +- 26 files changed, 311 insertions(+), 99 deletions(-) create mode 100644 img/no-calendar.svg rename src/{App.vue => ContactsRoot.vue} (100%) create mode 100644 src/components/Actions/ActionCopyNtoFN.vue create mode 100644 src/components/Actions/ActionToggleYear.vue create mode 100644 src/mixins/ActionsMixin.js diff --git a/.eslintrc.js b/.eslintrc.js index 7c652642..8aabc2d3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,6 +27,7 @@ module.exports = { 'plugin:node/recommended', 'plugin:vue/essential', 'plugin:vue/recommended', + 'plugin:nextcloud/recommended', 'standard' ], settings: { @@ -73,8 +74,14 @@ module.exports = { // es6 import/export and require 'node/no-unpublished-require': ['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'], + // force name + 'vue/match-component-file-name': ['error', { + 'extensions': ['jsx', 'vue', 'js'], + 'shouldMatchCase': true + }], // space before self-closing elements 'vue/html-closing-bracket-spacing': 'error', // no ending html tag on a new line diff --git a/css/icons.scss b/css/icons.scss index a543346f..4f74e7d4 100644 --- a/css/icons.scss +++ b/css/icons.scss @@ -20,29 +20,14 @@ * */ -.icon-social { - @include icon-color('social', 'contacts', $color-black, 1); -} -.icon-qrcode { - @include icon-color('qrcode', 'contacts', $color-black, 2); -} - -.icon-address-book { - @include icon-color('address-book', 'contacts', $color-black, 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); -} +@include icon-black-white('social', 'contacts', 1); +@include icon-black-white('qrcode', 'contacts', 1); +@include icon-black-white('address-book', 'contacts', 1); +@include icon-black-white('phone', 'contacts', 1); +@include icon-black-white('eye', 'contacts', 1); +@include icon-black-white('up', 'contacts', 1); +@include icon-black-white('no-calendar', 'contacts', 1); .icon-up-force-white { // using #fffffe to trick the accessibility dark theme icon invert diff --git a/img/no-calendar.svg b/img/no-calendar.svg new file mode 100644 index 00000000..6a328ca1 --- /dev/null +++ b/img/no-calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6b1fa06d..fc4a8632 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-9.2.0.tgz", @@ -5763,7 +5772,7 @@ "dependencies": { "readable-stream": { "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==", "dev": true, "requires": { @@ -8810,7 +8819,7 @@ }, "readable-stream": { "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==", "dev": true, "requires": { @@ -9127,6 +9136,12 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "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": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", @@ -9946,7 +9961,7 @@ }, "stream-browserify": { "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=", "dev": true, "requires": { @@ -10012,7 +10027,7 @@ }, "string_decoder": { "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==", "dev": true, "requires": { @@ -10752,7 +10767,7 @@ }, "tty-browserify": { "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=", "dev": true }, diff --git a/package.json b/package.json index 8467372d..3e2358e2 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "nextcloud-dialogs": "0.0.3", "nextcloud-l10n": "0.1.0", "nextcloud-router": "0.0.8", - "nextcloud-vue": "^0.12.1", + "nextcloud-vue": "^0.12.2", "p-limit": "^2.2.1", "p-queue": "^6.1.1", "qr-image": "^3.2.0", @@ -81,6 +81,7 @@ "eslint-import-resolver-webpack": "^0.11.1", "eslint-loader": "^3.0.0", "eslint-plugin-import": "^2.18.2", + "eslint-plugin-nextcloud": "^0.3.0", "eslint-plugin-node": "^9.2.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", diff --git a/src/App.vue b/src/ContactsRoot.vue similarity index 100% rename from src/App.vue rename to src/ContactsRoot.vue diff --git a/src/components/Actions/ActionCopyNtoFN.vue b/src/components/Actions/ActionCopyNtoFN.vue new file mode 100644 index 00000000..ccce1fb8 --- /dev/null +++ b/src/components/Actions/ActionCopyNtoFN.vue @@ -0,0 +1,51 @@ + + + + diff --git a/src/components/Actions/ActionToggleYear.vue b/src/components/Actions/ActionToggleYear.vue new file mode 100644 index 00000000..2e43310d --- /dev/null +++ b/src/components/Actions/ActionToggleYear.vue @@ -0,0 +1,93 @@ + + + + diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue index 9c8b6317..86b0333d 100644 --- a/src/components/ContactDetails.vue +++ b/src/components/ContactDetails.vue @@ -128,7 +128,7 @@ :prop-model="addressbookModel" :value.sync="addressbook" :is-first-property="true" :is-last-property="true" :property="{}" - class="property--addressbooks property--last" /> + class="property--addressbooks property--last property--without-actions" /> @@ -46,6 +44,10 @@ export default { actions: { type: Array, default: () => [] + }, + propertyComponent: { + type: Object, + required: true } }, diff --git a/src/components/Properties/PropertyDateTime.vue b/src/components/Properties/PropertyDateTime.vue index 71561c42..83b1a314 100644 --- a/src/components/Properties/PropertyDateTime.vue +++ b/src/components/Properties/PropertyDateTime.vue @@ -43,14 +43,14 @@ {{ propModel.readableName }} - - - + confirm @confirm="debounceUpdateValue" /> + + + @@ -159,42 +159,67 @@ export default { /** * Debounce and send update event to parent */ - updateValue: debounce(function(e) { + debounceUpdateValue: debounce(function(date) { 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 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 * but moment and js starts at 0 * ! since we use moment to generate our time array * ! 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 - 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 // Use moment to convert the JsDate to Object this.$emit('update:value', this.localValue) - }, 500), + }, /** * Format time with locale to display only @@ -211,6 +236,11 @@ export default { let datetimeData = this.vcardTimeLocalValue.toJSON() let datetime = '' + const ignoreYear = this.property.getParameter('x-apple-omit-year') + if (ignoreYear) { + datetimeData.year = null + } + // FUN FACT: JS date starts month at zero! datetimeData.month-- diff --git a/src/components/Properties/PropertyGroups.vue b/src/components/Properties/PropertyGroups.vue index 4cdf296b..c6e6c419 100644 --- a/src/components/Properties/PropertyGroups.vue +++ b/src/components/Properties/PropertyGroups.vue @@ -21,7 +21,7 @@ --> diff --git a/src/components/Properties/PropertyText.vue b/src/components/Properties/PropertyText.vue index 8ccaf969..05ec9861 100644 --- a/src/components/Properties/PropertyText.vue +++ b/src/components/Properties/PropertyText.vue @@ -60,7 +60,7 @@ target="_blank" /> - + diff --git a/src/components/Settings/SettingsAddressbookShare.vue b/src/components/Settings/SettingsAddressbookShare.vue index a54e8d1f..eb3c29ca 100644 --- a/src/components/Settings/SettingsAddressbookShare.vue +++ b/src/components/Settings/SettingsAddressbookShare.vue @@ -52,7 +52,7 @@ import addressBookSharee from './SettingsAddressbookSharee' import debounce from 'debounce' export default { - name: 'SettingsShareAddressbook', + name: 'SettingsAddressbookShare', components: { addressBookSharee }, diff --git a/src/components/Settings/SettingsAddressbookSharee.vue b/src/components/Settings/SettingsAddressbookSharee.vue index 93a3ee71..d7bea024 100644 --- a/src/components/Settings/SettingsAddressbookSharee.vue +++ b/src/components/Settings/SettingsAddressbookSharee.vue @@ -53,7 +53,7 @@