summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@users.noreply.github.com>2020-10-20 20:38:45 +0200
committerGitHub <noreply@github.com>2020-10-20 20:38:45 +0200
commitc3f8056e1f446437d093e9bc502fbf41e966a939 (patch)
treeaf13e7c424f2d947d7d3256b82f1f2dd108efc93
parentf2ee7431a578418aabba5187b77378ae40673d58 (diff)
parentfefe82209461c5a78b239935d64590a1c83b3e9c (diff)
Merge pull request #1846 from nextcloud/fix/avatar
-rw-r--r--package-lock.json119
-rw-r--r--package.json3
-rw-r--r--src/components/ContactDetails/ContactDetailsAvatar.vue42
-rw-r--r--src/components/ContactsList/ContactsListItem.vue59
-rw-r--r--src/models/contact.js24
5 files changed, 161 insertions, 86 deletions
diff --git a/package-lock.json b/package-lock.json
index 36d00350..ce1aa1e8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3542,6 +3542,21 @@
"core-js": "^3.6.4"
}
},
+ "@nextcloud/browser-storage": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@nextcloud/browser-storage/-/browser-storage-0.1.1.tgz",
+ "integrity": "sha512-bWzs/A44rEK8b3CMOFw0ZhsenagrWdsB902LOEwmlMCcFysiFgWiOPbF4/0/ODlOYjvPrO02wf6RigWtb8P+gA==",
+ "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/browserslist-config": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-1.0.0.tgz",
@@ -3645,24 +3660,29 @@
}
},
"@nextcloud/vue": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-2.7.0.tgz",
- "integrity": "sha512-iZxTUWsDvfQdi3FRqEBfv5MGqJPPISk9FEXhX98wnWX37YaDwx9qOpvAQo+/6bjVVwwu8E7oWJN24NE4rxWhBg==",
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-2.8.1.tgz",
+ "integrity": "sha512-6a11iUyOs8cUbmzLdK+WZG7g7gTH2oDESU9o/J16+nKn6g3BlfH0iI/dWaqGiPuNiLy5CAqeQPvhx1UxaL/f5A==",
"requires": {
"@nextcloud/auth": "^1.2.3",
"@nextcloud/axios": "^1.3.2",
+ "@nextcloud/browser-storage": "^0.1.1",
"@nextcloud/capabilities": "^1.0.2",
- "@nextcloud/dialogs": "^2.0.1",
+ "@nextcloud/dialogs": "^3.0.0",
"@nextcloud/event-bus": "^1.1.4",
"@nextcloud/l10n": "^1.2.3",
"@nextcloud/router": "^1.0.2",
"core-js": "^3.6.5",
"debounce": "1.2.0",
"emoji-mart-vue-fast": "^7.0.4",
+ "escape-html": "^1.0.3",
"hammerjs": "^2.0.8",
"linkifyjs": "~2.1.9",
"md5": "^2.2.1",
"regenerator-runtime": "^0.13.5",
+ "string-length": "^4.0.1",
+ "striptags": "^3.1.1",
+ "tributejs": "^5.1.3",
"v-click-outside": "^3.0.1",
"v-tooltip": "^2.0.3",
"vue": "^2.6.11",
@@ -3670,19 +3690,6 @@
"vue-multiselect": "^2.1.6",
"vue-visible": "^1.0.2",
"vue2-datepicker": "^3.6.2"
- },
- "dependencies": {
- "@nextcloud/dialogs": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-2.0.1.tgz",
- "integrity": "sha512-Bme8vcs8n4XT5spBgkDEv1z9zNOE23AIbr5jF1WJ1A2XNMNj5Zvy29RosIh0k7H+1lN0PlU38u+eMV1Ets3E4A==",
- "requires": {
- "@nextcloud/l10n": "^1.3.0",
- "@nextcloud/typings": "^0.2.2",
- "core-js": "^3.6.4",
- "toastify-js": "^1.9.1"
- }
- }
}
},
"@nextcloud/webpack-vue-config": {
@@ -4343,6 +4350,11 @@
"follow-redirects": "^1.10.0"
}
},
+ "b64-to-blob": {
+ "version": "1.2.19",
+ "resolved": "https://registry.npmjs.org/b64-to-blob/-/b64-to-blob-1.2.19.tgz",
+ "integrity": "sha512-L3nSu8GgF4iEyNYakCQSfL2F5GI5aCXcot9mNTf+4N0/BMhpxqqHyOb6jIR24iq2xLjQZLG8FOt3gnUcV+9NVg=="
+ },
"babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
@@ -4833,6 +4845,11 @@
"supports-color": "^5.3.0"
}
},
+ "char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="
+ },
"character-entities": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
@@ -5803,6 +5820,7 @@
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
+ "is-negative-zero": "^2.0.0",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
@@ -5817,8 +5835,30 @@
"integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==",
"requires": {
"define-properties": "^1.1.3",
+ "es-abstract": "^1.18.0-next.0",
"has-symbols": "^1.0.1",
"object-keys": "^1.1.1"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.18.0-next.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz",
+ "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==",
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.2.2",
+ "is-negative-zero": "^2.0.0",
+ "is-regex": "^1.1.1",
+ "object-inspect": "^1.8.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.1",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.1"
+ }
+ }
}
}
}
@@ -5838,6 +5878,11 @@
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz",
"integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ=="
},
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -11115,6 +11160,30 @@
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
"dev": true
},
+ "string-length": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz",
+ "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==",
+ "requires": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ }
+ }
+ },
"string-natural-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz",
@@ -11199,6 +11268,11 @@
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
"dev": true
},
+ "striptags": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz",
+ "integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0="
+ },
"style-search": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
@@ -12123,6 +12197,11 @@
"punycode": "^2.1.1"
}
},
+ "tributejs": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/tributejs/-/tributejs-5.1.3.tgz",
+ "integrity": "sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ=="
+ },
"trim": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
@@ -12818,9 +12897,9 @@
"integrity": "sha512-yaX2its9XAJKGuQqf7LsiZHHSkxsIK8rmCOQOvEGEoF41blKRK8qr9my4qYoD6ikdLss4n8tKqYBecmaY0+WJg=="
},
"vue2-datepicker": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/vue2-datepicker/-/vue2-datepicker-3.6.2.tgz",
- "integrity": "sha512-J2fCwUmCxIOPUvwQ12e8evFY9cCv6vJmgxRD9fGeUv6JeMMeLwkdpeQZOcqbMf/4mk1cSrY2/9Fr8DaB30LBpA==",
+ "version": "3.6.3",
+ "resolved": "https://registry.npmjs.org/vue2-datepicker/-/vue2-datepicker-3.6.3.tgz",
+ "integrity": "sha512-887cTxbEKTt35CvA58/Xh1n2I403UxutyjCoG8lAF/IjHsEVv4tYCa0cC27VvT2U9ABThg9pzkT0IWU3zDmB/w==",
"requires": {
"date-fns": "^2.0.1",
"date-format-parse": "^0.2.5"
diff --git a/package.json b/package.json
index 14bce289..bc648018 100644
--- a/package.json
+++ b/package.json
@@ -41,8 +41,9 @@
"@nextcloud/l10n": "^1.4.1",
"@nextcloud/paths": "^1.1.2",
"@nextcloud/router": "^1.2.0",
- "@nextcloud/vue": "2.7.0",
+ "@nextcloud/vue": "^2.8.1",
"axios": "^0.20.0",
+ "b64-to-blob": "^1.2.19",
"cdav-library": "git+https://github.com/nextcloud/cdav-library.git",
"core-js": "^3.6.5",
"debounce": "^1.2.0",
diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue
index cd5a71b1..aa8af0e8 100644
--- a/src/components/ContactDetails/ContactDetailsAvatar.vue
+++ b/src/components/ContactDetails/ContactDetailsAvatar.vue
@@ -37,12 +37,20 @@
:style="{ 'backgroundImage': `url(${contact.photoUrl})` }"
class="contact-header-avatar__photo"
@click="toggleModal" />
+ <Avatar v-else
+ :disable-tooltip="true"
+ :display-name="contact.displayName"
+ :is-no-user="true"
+ :size="75"
+ class="contact-header-avatar__photo" />
<!-- attention, this menu exists twice in this file -->
<Actions
- default-icon="icon-picture-force-white"
+ v-if="!isReadOnly || contact.photo"
+ :force-menu="true"
:open.sync="opened"
- class="contact-header-avatar__menu">
+ class="contact-header-avatar__menu"
+ default-icon="icon-picture-force-white">
<template v-if="!isReadOnly">
<ActionButton
icon="icon-upload"
@@ -90,20 +98,18 @@
@close="toggleModal">
<!-- attention, this menu exists twice in this file -->
<template #actions>
- <ActionButton
- v-if="!isReadOnly"
- icon="icon-upload"
- @click="selectFileInput">
- {{ t('contacts', 'Upload a new picture') }}
- </ActionButton>
- <ActionButton
- v-if="!isReadOnly"
- icon="icon-folder"
- @click="selectFilePicker">
- {{ t('contacts', 'Choose from files') }}
- </ActionButton>
<template v-if="!isReadOnly">
<ActionButton
+ icon="icon-upload"
+ @click="selectFileInput">
+ {{ t('contacts', 'Upload a new picture') }}
+ </ActionButton>
+ <ActionButton
+ icon="icon-folder"
+ @click="selectFilePicker">
+ {{ t('contacts', 'Choose from files') }}
+ </ActionButton>
+ <ActionButton
v-for="network in supportedSocial"
:key="network"
:icon="'icon-' + network.toLowerCase()"
@@ -111,6 +117,7 @@
{{ t('contacts', 'Get from ' + network) }}
</ActionButton>
</template>
+
<!-- FIXME: the link seems to have a bigger font size than the button caption -->
<ActionLink
v-if="contact.photo"
@@ -126,6 +133,7 @@
{{ t('contacts', 'Delete picture') }}
</ActionButton>
</template>
+
<img ref="img"
:src="contact.photoUrl"
class="contact-header-modal__photo">
@@ -134,6 +142,7 @@
</template>
<script>
+import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
@@ -153,10 +162,11 @@ export default {
name: 'ContactDetailsAvatar',
components: {
- Modal,
- Actions,
ActionButton,
ActionLink,
+ Actions,
+ Avatar,
+ Modal,
},
props: {
diff --git a/src/components/ContactsList/ContactsListItem.vue b/src/components/ContactsList/ContactsListItem.vue
index fef0a9ca..14efc25b 100644
--- a/src/components/ContactsList/ContactsListItem.vue
+++ b/src/components/ContactsList/ContactsListItem.vue
@@ -11,13 +11,14 @@
class="app-content-list-item-checkbox checkbox" @keypress.enter.space.prevent.stop="toggleSelect">
<label :for="contact.key" @click.prevent.stop="toggleSelect" @keypress.enter.space.prevent.stop="toggleSelect" />
-->
- <div :style="avatarStyle" class="app-content-list-item-icon">
- <!-- try to fetch the avatar only if the contact exists on the server -->
- <div v-if="hasPhoto" :style="{ 'backgroundImage': avatarUrl }" class="app-content-list-item-icon__avatar" />
- <template v-else>
- {{ contact.displayName | firstLetter }}
- </template>
- </div>
+ <Avatar
+ :disable-menu="true"
+ :disable-tooltip="true"
+ :display-name="contact.displayName"
+ :is-no-user="true"
+ :size="40"
+ :url="avatarUrl"
+ class="app-content-list-item-icon" />
<!-- contact data -->
<div class="app-content-list-item-line-one">
@@ -30,13 +31,15 @@
</template>
<script>
+import Avatar from '@nextcloud/vue/dist/Components/Avatar'
+
export default {
name: 'ContactsListItem',
- filters: {
- firstLetter(value) {
- return value.charAt(0)
- },
+
+ components: {
+ Avatar,
},
+
props: {
index: {
type: Number,
@@ -47,6 +50,7 @@ export default {
required: true,
},
},
+
computed: {
selectedGroup() {
return this.$route.params.selectedGroup
@@ -60,37 +64,14 @@ export default {
return window.btoa(this.contact.key).slice(0, -2)
},
- hasPhoto() {
- return this.contact.dav && (this.contact.dav.hasphoto || this.contact.photo)
- },
-
- avatarStyle() {
- if (this.hasPhoto) {
- return {
- backgroundColor: '#fff',
- // The contact photo gets cropped in a circular shape, which might look odd with transparent photos.
- // This box shadow ensures that there's always a very faint edge hinting at the circle.
- boxShadow: '0 0 5px rgba(0, 0, 0, 0.05) inset',
- }
- }
-
- try {
- const color = this.contact.uid.toRgb()
- return {
- backgroundColor: `rgb(${color.r}, ${color.g}, ${color.b})`,
- }
- } catch (e) {
- return {
- backgroundColor: 'grey',
- }
- }
- },
-
avatarUrl() {
if (this.contact.photo) {
- return `url(${this.contact.photoUrl})`
+ return `${this.contact.photoUrl}`
+ }
+ if (this.contact.url) {
+ return `${this.contact.url}?photo`
}
- return `url(${this.contact.url}?photo)`
+ return undefined
},
},
methods: {
diff --git a/src/models/contact.js b/src/models/contact.js
index 70802626..3b3ace90 100644
--- a/src/models/contact.js
+++ b/src/models/contact.js
@@ -22,6 +22,7 @@
import { v4 as uuid } from 'uuid'
import ICAL from 'ical.js'
+import b64toBlob from 'b64-to-blob'
import store from '../store'
import updateDesignSet from '../services/updateDesignSet'
@@ -225,6 +226,7 @@ export default class Contact {
/**
* Return the photo usable url
+ * We cannot fetch external url because of csp policies
*
* @readonly
* @memberof Contact
@@ -232,22 +234,24 @@ export default class Contact {
get photoUrl() {
const photo = this.vCard.getFirstProperty('photo')
const encoding = photo.getFirstParameter('encoding')
- const type = photo.getFirstParameter('type')
+ let photoType = photo.getFirstParameter('type')
+ let photoB64 = this.photo
const isBinary = photo.type === 'binary' || encoding === 'b'
- if (photo && !this.photo.startsWith('data') && isBinary) {
- // split on coma in case of any leftover base64 data and retrieve last part
- // usually we come to this part when the base64 image type is unknown
- return `data:image/${type};base64,${this.photo.split(',').pop()}`
+ if (photo && photoB64.startsWith('data') && !isBinary) {
+ // get the last part = base64
+ photoB64 = photoB64.split(',').pop()
+ // 'data:image/png' => 'png'
+ photoType = photoB64.split(';')[0].split('/')
}
- // could be just an url of the already encoded `data:image...`
+
try {
- // eslint-disable-next-line no-new
- new URL(this.photo)
- return this.photo
+ // Create blob from url
+ const blob = b64toBlob(photoB64, `image/${photoType}`)
+ return URL.createObjectURL(blob)
} catch {
- console.error('Invalid photo for the following contact. Ignoring...', this.contact)
+ console.error('Invalid photo for the following contact. Ignoring...', this.contact, { photoB64, photoType })
return false
}
}