diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-10-12 14:23:53 +0200 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-10-20 09:02:22 +0200 |
commit | fefe82209461c5a78b239935d64590a1c83b3e9c (patch) | |
tree | 0361c2b2db9fc17a025d5d08d7d6da5e6bdd8a1a | |
parent | 5c2eb89b0fe280eaf819b9690cf15146a81d8e0a (diff) |
Fix avatar display on read-only contacts and use global Avatar component
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
-rw-r--r-- | package-lock.json | 124 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | src/components/ContactDetails/ContactDetailsAvatar.vue | 42 | ||||
-rw-r--r-- | src/components/ContactsList/ContactsListItem.vue | 59 | ||||
-rw-r--r-- | src/models/contact.js | 24 |
5 files changed, 166 insertions, 86 deletions
diff --git a/package-lock.json b/package-lock.json index 8b2e38b5..bf62bbea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2414,6 +2414,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", @@ -2517,24 +2532,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", @@ -2542,19 +2562,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": { @@ -3215,6 +3222,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", @@ -3705,6 +3717,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", @@ -4675,6 +4692,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", @@ -4689,8 +4707,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" + } + } } } } @@ -4710,6 +4750,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", @@ -7178,6 +7223,11 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -9990,6 +10040,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", @@ -10074,6 +10148,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", @@ -10963,6 +11042,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", @@ -11658,9 +11742,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 d3638347..aee1dfa3 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 } } |