summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-01-24 18:27:35 +0100
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-01-29 17:26:05 +0100
commit0ff05f50d458b5875707bea33ccf2b8976254edb (patch)
treed7e0c03b52eee2935508ceb848d7734d3e8b0f62
parent503be8085b4e02e22bd69dbea959bbea7534cc76 (diff)
Allow importing from files
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
-rw-r--r--package-lock.json100
-rw-r--r--package.json11
-rw-r--r--src/components/ContactDetails/ContactDetailsAvatar.vue7
-rw-r--r--src/components/Properties/PropertyDateTime.vue2
-rw-r--r--src/components/Properties/PropertyMultipleText.vue4
-rw-r--r--src/components/Settings/SettingsImportContacts.vue194
-rw-r--r--src/components/SettingsSection.vue2
-rw-r--r--src/main.js4
-rw-r--r--src/models/rfcProps.js2
-rw-r--r--src/router/index.js2
-rw-r--r--src/services/cdav.js4
11 files changed, 257 insertions, 75 deletions
diff --git a/package-lock.json b/package-lock.json
index b3cfe912..9cf2ee76 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2573,6 +2573,21 @@
"core-js": "^3.5.0"
}
},
+ "@nextcloud/dialogs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-1.0.0.tgz",
+ "integrity": "sha512-CV7sCJg37866j4ilNnf4PwvN2jNq6qZu+/cIdXLUZHUmK4HOfsSCqBmVqsf83Nq95VR6p/lCF1K1YbCvQG/P+g==",
+ "requires": {
+ "core-js": "3.5.0"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz",
+ "integrity": "sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw=="
+ }
+ }
+ },
"@nextcloud/event-bus": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.1.2.tgz",
@@ -2590,6 +2605,51 @@
}
}
},
+ "@nextcloud/initial-state": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-1.1.0.tgz",
+ "integrity": "sha512-c8VNSv7CbcPdaMNQO3ERJUMhsGyCvAgSBlvBHhugYHxGqlySjE+J+SqkpXmqB+eQ/DujDTahBX1IwoF3zjPtOw==",
+ "requires": {
+ "core-js": "3.6.1"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.1.tgz",
+ "integrity": "sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ=="
+ }
+ }
+ },
+ "@nextcloud/l10n": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.0.1.tgz",
+ "integrity": "sha512-42Uh7vFTwCUNziRfwunrYUzsKxmulp4DH+RamB4DsFrYPH4z2qyQ4a2UBZJsZAcT1720rDagKQaXgPB2+Q0/0w==",
+ "requires": {
+ "core-js": "3.6.1"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.1.tgz",
+ "integrity": "sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ=="
+ }
+ }
+ },
+ "@nextcloud/paths": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@nextcloud/paths/-/paths-1.1.0.tgz",
+ "integrity": "sha512-2fXdjY8cya5k38dIF7+BCYvJhprpWSsIGEiJDFKZQ4X02+jxP4IzI8qPG97iej7yhuQGN06xpmUv1OcT5NPftA==",
+ "requires": {
+ "core-js": "3.6.1"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.1.tgz",
+ "integrity": "sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ=="
+ }
+ }
+ },
"@nextcloud/router": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-1.0.0.tgz",
@@ -9166,46 +9226,6 @@
"integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
"dev": true
},
- "nextcloud-auth": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/nextcloud-auth/-/nextcloud-auth-0.0.3.tgz",
- "integrity": "sha512-qEAl55QJg2gZZIpfin9NzCPWm/Mfbo+HOdaXpsastPZw8oA7YLFFZon3x6SQ/p/LVIPQzRZmMpjd8R2FAAbjzg==",
- "requires": {
- "core-js": "^3.1.4"
- }
- },
- "nextcloud-dialogs": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/nextcloud-dialogs/-/nextcloud-dialogs-0.0.3.tgz",
- "integrity": "sha512-lR6KsGU8IRPIYijjES0AaSKbEYoYAOnZHHqvH0dbwg4T00/n6poIzXlt392tESXKkO9Rxl/zz+eCs5U0PTYkGA==",
- "requires": {
- "core-js": "^3.1.4"
- }
- },
- "nextcloud-initial-state": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/nextcloud-initial-state/-/nextcloud-initial-state-0.0.3.tgz",
- "integrity": "sha512-sL0dKbOb63QwvkAfQdDC5AldshVwaY8B8tKpAci7UMmJV3M1KLxTBzQoY+CVy03/uqTvvFt3Brz/Bd2UNp3zsQ==",
- "requires": {
- "core-js": "^3.1.4"
- }
- },
- "nextcloud-l10n": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/nextcloud-l10n/-/nextcloud-l10n-0.1.1.tgz",
- "integrity": "sha512-3ftj9xyJjuqygRrVkxvNbFbOQHoGotMm/AJIpDUDwT91j4ICbgl2lif8Xdoc6oKJ1CtKgZ86eNlpn/vsCKIjfg==",
- "requires": {
- "core-js": "^3.1.4"
- }
- },
- "nextcloud-router": {
- "version": "0.0.9",
- "resolved": "https://registry.npmjs.org/nextcloud-router/-/nextcloud-router-0.0.9.tgz",
- "integrity": "sha512-w0i4xqFwJJuXNWFf9AB9huCWW5XmwdJHSHa7oXlOLTAvP9WxwU3KCm/mcKy8Eb0cT0ElRPg72HLUxl7oyEWoBQ==",
- "requires": {
- "core-js": "^3.1.4"
- }
- },
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
diff --git a/package.json b/package.json
index bb484e0a..a9639138 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,12 @@
"test:watch": "mochapack -w --webpack-config webpack.test.js --require tests/setup.js tests/unit/specs/**/*.spec.js"
},
"dependencies": {
+ "@nextcloud/auth": "^1.2.1",
+ "@nextcloud/dialogs": "^1.0.0",
+ "@nextcloud/initial-state": "^1.1.0",
+ "@nextcloud/l10n": "^1.0.1",
+ "@nextcloud/paths": "^1.1.0",
+ "@nextcloud/router": "^1.0.0",
"@nextcloud/vue": "1.2.7",
"axios": "^0.19.1",
"cdav-library": "github:nextcloud/cdav-library",
@@ -43,11 +49,6 @@
"downloadjs": "^1.4.7",
"ical.js": "^1.3.0",
"moment": "^2.24.0",
- "nextcloud-auth": "0.0.3",
- "nextcloud-dialogs": "0.0.3",
- "nextcloud-initial-state": "0.0.3",
- "nextcloud-l10n": "0.1.1",
- "nextcloud-router": "0.0.9",
"p-limit": "^2.2.2",
"p-queue": "^6.2.1",
"qr-image": "^3.2.0",
diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue
index 703a3d4f..76433d41 100644
--- a/src/components/ContactDetails/ContactDetailsAvatar.vue
+++ b/src/components/ContactDetails/ContactDetailsAvatar.vue
@@ -88,8 +88,9 @@
import debounce from 'debounce'
import { ActionLink, ActionButton } from '@nextcloud/vue'
-import { getFilePickerBuilder } from 'nextcloud-dialogs'
-import { generateRemoteUrl } from 'nextcloud-router'
+import { getFilePickerBuilder } from '@nextcloud/dialogs'
+import { generateRemoteUrl } from '@nextcloud/router'
+import { getCurrentUser } from '@nextcloud/auth'
const axios = () => import('axios')
@@ -113,7 +114,7 @@ export default {
maximizeAvatar: false,
opened: false,
loading: false,
- root: generateRemoteUrl(`dav/files/${OC.getCurrentUser().uid}`),
+ root: generateRemoteUrl(`dav/files/${getCurrentUser().uid}`),
width: 0,
height: 0,
}
diff --git a/src/components/Properties/PropertyDateTime.vue b/src/components/Properties/PropertyDateTime.vue
index 03144509..cd0af107 100644
--- a/src/components/Properties/PropertyDateTime.vue
+++ b/src/components/Properties/PropertyDateTime.vue
@@ -85,7 +85,7 @@
import debounce from 'debounce'
import moment from 'moment'
import { DatetimePicker } from '@nextcloud/vue'
-import { getLocale } from 'nextcloud-l10n'
+import { getLocale } from '@nextcloud/l10n'
import { VCardTime } from 'ical.js'
import PropertyMixin from '../../mixins/PropertyMixin'
diff --git a/src/components/Properties/PropertyMultipleText.vue b/src/components/Properties/PropertyMultipleText.vue
index cd4107e9..896ff661 100644
--- a/src/components/Properties/PropertyMultipleText.vue
+++ b/src/components/Properties/PropertyMultipleText.vue
@@ -63,8 +63,8 @@
@input="updateValue">
<!-- props actions -->
- <PropertyActions class="property__actions--floating"
- v-if="!isReadOnly"
+ <PropertyActions v-if="!isReadOnly"
+ class="property__actions--floating"
:actions="actions"
:property-component="this"
@delete="deleteProperty" />
diff --git a/src/components/Settings/SettingsImportContacts.vue b/src/components/Settings/SettingsImportContacts.vue
index d7583a3a..f0b6f04c 100644
--- a/src/components/Settings/SettingsImportContacts.vue
+++ b/src/components/Settings/SettingsImportContacts.vue
@@ -23,21 +23,56 @@
<template>
<div class="import-contact">
<template v-if="!isNoAddressbookAvailable">
- <input id="contact-import"
- :disabled="isImporting"
- type="file"
- class="hidden-visually"
- @change="processFile">
- <label id="upload" for="contact-import" class="button import-contact__multiselect-label icon-upload">
- {{ isImporting ? t('contacts', 'Importing into') : t('contacts', 'Import into') }}
- </label>
- <multiselect
- v-model="selectedAddressbook"
- :options="options"
- :disabled="isSingleAddressbook || isImporting"
- :placeholder="t('contacts', 'Contacts')"
- label="displayName"
- class="import-contact__multiselect" />
+ <button class="import-contact__button-main" @click="toggleModal">
+ <span class="icon-upload" />
+ {{ t('contacts', 'Import contacts') }}
+ </button>
+ <Modal v-if="isOpened"
+ ref="modal"
+ class="import-contact__modal"
+ :title="t('contacts', 'Import contacts')"
+ @close="toggleModal">
+ <section class="import-contact__modal-addressbook">
+ <h3>{{ t('contacts', 'Import contacts') }}</h3>
+ <multiselect
+ v-if="!isSingleAddressbook"
+ id="select-addressbook"
+ v-model="selectedAddressbook"
+ :allow-empty="false"
+ :options="options"
+ :disabled="isSingleAddressbook || isImporting"
+ :placeholder="t('contacts', 'Contacts')"
+ label="displayName"
+ class="import-contact__multiselect">
+ <template slot="singleLabel" slot-scope="{ option }">
+ {{ t('contacts', `Import into the '{addressbookName}' addressbook`, { addressbookName: option.displayName }) }}
+ </template>
+ </multiselect>
+ </section>
+ <section class="import-contact__modal-pick">
+ <input id="contact-import"
+ ref="contact-import-input"
+ :disabled="loading || isImporting"
+ type="file"
+ class="hidden-visually"
+ @change="processFile">
+ <button
+ :disabled="loading"
+ class="button import-contact__button import-contact__button--local"
+ @click="clickImportInput">
+ <span class="import-contact__button-icon icon-upload" />
+ {{ t('contacts', 'Select local file') }}
+ </button>
+ <button
+ :class="{'icon-loading': loading}"
+ :disabled="loading"
+ class="button primary import-contact__button import-contact__button--files"
+ @click="openPicker">
+ <span class="import-contact__button-icon icon-folder-white" />
+ {{ t('contacts', 'Import from files') }}
+ </button>
+ </section>
+ </Modal>
</template>
<button v-else
id="upload"
@@ -49,12 +84,31 @@
</template>
<script>
+import { encodePath } from '@nextcloud/paths'
+import { getCurrentUser } from '@nextcloud/auth'
+import { generateRemoteUrl } from '@nextcloud/router'
+import { getFilePickerBuilder } from '@nextcloud/dialogs'
+import axios from 'axios'
+
+const CancelToken = axios.CancelToken
+
+const picker = getFilePickerBuilder(t('contacts', 'Choose a vcard file to import'))
+ .setMultiSelect(false)
+ .setModal(true)
+ .setType(1)
+ .allowDirectories(false)
+ .setMimeTypeFilter('text/vcard')
+ .build()
+
export default {
name: 'SettingsImportContacts',
data() {
return {
+ cancelRequest: () => {},
importDestination: false,
+ isOpened: false,
+ loading: false,
}
},
@@ -110,20 +164,126 @@ export default {
},
},
methods: {
+ /**
+ * Process input type file change
+ *
+ * @param {Event} event the input change event
+ */
processFile(event) {
+ this.loading = true
+ this.$store.dispatch('changeStage', 'parsing')
+
const file = event.target.files[0]
const reader = new FileReader()
const selectedAddressbook = this.selectedAddressbook
- this.$store.dispatch('changeStage', 'parsing')
- this.$store.dispatch('setAddressbook', selectedAddressbook.displayName)
+
const self = this
reader.onload = function(e) {
+ self.isOpened = false
self.$store.dispatch('importContactsIntoAddressbook', { vcf: reader.result, addressbook: selectedAddressbook })
+
// reset input
event.target.value = ''
+ self.resetState()
}
reader.readAsText(file)
},
+
+ toggleModal() {
+ this.isOpened = !this.isOpened
+ // cancel any ongoing request if closed
+ if (!this.isOpened) {
+ this.cancelRequest()
+ }
+ },
+
+ clickImportInput() {
+ this.$refs['contact-import-input'].click()
+ },
+
+ /**
+ * Open nextcloud file picker
+ */
+ async openPicker() {
+ try {
+ this.loading = true
+ // unlikely, but let's cancel any previous request
+ this.cancelRequest()
+
+ // prepare cancel token for axios request
+ const source = CancelToken.source()
+ this.cancelRequest = source.cancel
+
+ // pick and retrieve file
+ const path = await picker.pick()
+ const file = await axios.get(generateRemoteUrl(`dav/files/${getCurrentUser().uid}`) + encodePath(path), {
+ cancelToken: source.token,
+ })
+
+ this.$store.dispatch('changeStage', 'parsing')
+ this.$store.dispatch('setAddressbook', this.selectedAddressbook.displayName)
+
+ if (file.data) {
+ await this.$store.dispatch('importContactsIntoAddressbook', { vcf: file.data, addressbook: this.selectedAddressbook })
+ }
+ } catch (error) {
+ console.error('Something wrong happened while picking a file', error)
+ } finally {
+ this.resetState()
+ }
+ },
+
+ /**
+ * Reset default component state
+ */
+ resetState() {
+ this.cancelRequest = () => {}
+ this.importDestination = false
+ this.isOpened = false
+ this.loading = false
+ },
},
}
</script>
+
+<style lang="scss" scoped>
+.import-contact {
+ &__modal {
+ section {
+ padding: 22px;
+ // only one padding bewteen sections
+ &:not(:last-child) {
+ padding-bottom: 0;
+ }
+ }
+ &-pick {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ }
+ }
+ &__button {
+ display: flex;
+ align-items: center;
+ flex: 0 1 150px;
+ width: 150px;
+ // spread evenly
+ margin: 10px;
+ padding: 10px;
+ &-icon {
+ width: 32px;
+ height: 32px;
+ margin-right: 5px;
+ }
+ &-main {
+ width: 100%;
+ }
+ &--cancel:not(:focus):not(:hover) {
+ border-color: transparent;
+ background-color: transparent;
+ }
+ }
+}
+
+</style>
diff --git a/src/components/SettingsSection.vue b/src/components/SettingsSection.vue
index 7053356b..e0757716 100644
--- a/src/components/SettingsSection.vue
+++ b/src/components/SettingsSection.vue
@@ -26,11 +26,11 @@
<SettingsAddressbook v-for="addressbook in addressbooks" :key="addressbook.id" :addressbook="addressbook" />
</ul>
<SettingsNewAddressbook :addressbooks="addressbooks" />
+ <SettingsSortContacts class="settings-section" />
<SettingsImportContacts :addressbooks="addressbooks"
class="settings-section"
@clicked="onClickImport"
@fileLoaded="onLoad" />
- <SettingsSortContacts class="settings-section" />
</div>
</template>
diff --git a/src/main.js b/src/main.js
index 87e8c2db..47b4353e 100644
--- a/src/main.js
+++ b/src/main.js
@@ -28,8 +28,8 @@ import App from './ContactsRoot'
import router from './router'
import store from './store'
import { sync } from 'vuex-router-sync'
-import { generateFilePath } from 'nextcloud-router'
-import { getRequestToken } from 'nextcloud-auth'
+import { generateFilePath } from '@nextcloud/router'
+import { getRequestToken } from '@nextcloud/auth'
/** GLOBAL COMPONENTS AND DIRECTIVE */
import { Actions, DatetimePicker, Multiselect, PopoverMenu, Modal } from '@nextcloud/vue'
diff --git a/src/models/rfcProps.js b/src/models/rfcProps.js
index ca9b4e33..311f61f5 100644
--- a/src/models/rfcProps.js
+++ b/src/models/rfcProps.js
@@ -20,7 +20,7 @@
*
*/
import { VCardTime } from 'ical.js'
-// import { loadState } from 'nextcloud-initial-state'
+// import { loadState } from '@nextcloud/initial-state'
import { loadState } from '../services/initialstate'
import ActionCopyNtoFN from '../components/Actions/ActionCopyNtoFN'
diff --git a/src/router/index.js b/src/router/index.js
index 1f8fd221..eb2c9758 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -22,7 +22,7 @@
import Vue from 'vue'
import Router from 'vue-router'
-import { generateUrl } from 'nextcloud-router'
+import { generateUrl } from '@nextcloud/router'
import Contacts from '../views/Contacts'
Vue.use(Router)
diff --git a/src/services/cdav.js b/src/services/cdav.js
index da5c6549..82ba3759 100644
--- a/src/services/cdav.js
+++ b/src/services/cdav.js
@@ -21,8 +21,8 @@
*/
import DavClient from 'cdav-library'
-import { generateRemoteUrl } from 'nextcloud-router'
-import { getRequestToken } from 'nextcloud-auth'
+import { generateRemoteUrl } from '@nextcloud/router'
+import { getRequestToken } from '@nextcloud/auth'
function xhrProvider() {
const headers = {