Use Gravatar if it is really defined (#2359)

Signed-off-by: Denis Bunakalya <denis.bunakalya@xored.com>
This commit is contained in:
Denis Bunakalya 2022-11-08 10:29:05 +03:00 committed by GitHub
parent 508ecc6a44
commit 23641ac365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 262 additions and 141 deletions

View File

@ -243,6 +243,7 @@ specifiers:
cors: ^2.8.5 cors: ^2.8.5
cropperjs: ~1.5.12 cropperjs: ~1.5.12
cross-env: ~7.0.3 cross-env: ~7.0.3
cross-fetch: ^3.1.5
crypto-js: ^4.1.1 crypto-js: ^4.1.1
css-loader: ^5.2.1 css-loader: ^5.2.1
csv-parse: ~5.1.0 csv-parse: ~5.1.0
@ -560,6 +561,7 @@ dependencies:
cors: 2.8.5 cors: 2.8.5
cropperjs: 1.5.12 cropperjs: 1.5.12
cross-env: 7.0.3 cross-env: 7.0.3
cross-fetch: 3.1.5
crypto-js: 4.1.1 crypto-js: 4.1.1
css-loader: 5.2.7_webpack@5.73.0 css-loader: 5.2.7_webpack@5.73.0
csv-parse: 5.1.0 csv-parse: 5.1.0
@ -3782,6 +3784,14 @@ packages:
cross-spawn: 7.0.3 cross-spawn: 7.0.3
dev: false dev: false
/cross-fetch/3.1.5:
resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
dependencies:
node-fetch: 2.6.7
transitivePeerDependencies:
- encoding
dev: false
/cross-spawn/7.0.3: /cross-spawn/7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -3889,6 +3899,11 @@ packages:
resolution: {integrity: sha512-JL+Q6YEikT2uoe57InjFFa6VejhSv0tDwOxeQ1bVQKeUC/NCnLAAZ8n3PzowPQQLuZ37fysDYZipB2UJkH9C6A==} resolution: {integrity: sha512-JL+Q6YEikT2uoe57InjFFa6VejhSv0tDwOxeQ1bVQKeUC/NCnLAAZ8n3PzowPQQLuZ37fysDYZipB2UJkH9C6A==}
dev: false dev: false
/data-uri-to-buffer/4.0.0:
resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
engines: {node: '>= 12'}
dev: false
/data-urls/2.0.0: /data-urls/2.0.0:
resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==} resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4943,6 +4958,14 @@ packages:
bser: 2.1.1 bser: 2.1.1
dev: false dev: false
/fetch-blob/3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.2.1
dev: false
/file-entry-cache/6.0.1: /file-entry-cache/6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
@ -5073,6 +5096,13 @@ packages:
mime-types: 2.1.35 mime-types: 2.1.35
dev: false dev: false
/formdata-polyfill/4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
dependencies:
fetch-blob: 3.2.0
dev: false
/forwarded-parse/2.1.2: /forwarded-parse/2.1.2:
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
dev: false dev: false
@ -7271,6 +7301,32 @@ packages:
resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==} resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==}
dev: false dev: false
/node-domexception/1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
dev: false
/node-fetch/2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
whatwg-url: 5.0.0
dev: false
/node-fetch/3.2.10:
resolution: {integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
data-uri-to-buffer: 4.0.0
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
dev: false
/node-forge/1.3.1: /node-forge/1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'} engines: {node: '>= 6.13.0'}
@ -9215,6 +9271,10 @@ packages:
universalify: 0.1.2 universalify: 0.1.2
dev: false dev: false
/tr46/0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
/tr46/2.1.0: /tr46/2.1.0:
resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==} resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -9557,6 +9617,15 @@ packages:
'@zxing/text-encoding': 0.9.0 '@zxing/text-encoding': 0.9.0
dev: false dev: false
/web-streams-polyfill/3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
engines: {node: '>= 8'}
dev: false
/webidl-conversions/3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false
/webidl-conversions/5.0.0: /webidl-conversions/5.0.0:
resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -9777,6 +9846,13 @@ packages:
webidl-conversions: 7.0.0 webidl-conversions: 7.0.0
dev: false dev: false
/whatwg-url/5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
dev: false
/whatwg-url/8.7.0: /whatwg-url/8.7.0:
resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==} resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -10633,20 +10709,23 @@ packages:
dev: false dev: false
file:projects/contact.tgz: file:projects/contact.tgz:
resolution: {integrity: sha512-XEyVNwWUMw6+kqGTrKrdY5qIKl+AXkFTL42bbgC5egcrvSZaMfnKqfq4ZwoWcYUFhLAa8VOJ6BJRZbxW0UfLcA==, tarball: file:projects/contact.tgz} resolution: {integrity: sha512-PZ/ICZGSAaO5SiOpbIAc9mwxHq8UhCtnjEAf59gO6fP1+UNyj+Oy8zdtJ/v0LobOzhYLHUikeezgJdFSIqFirA==, tarball: file:projects/contact.tgz}
name: '@rush-temp/contact' name: '@rush-temp/contact'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
'@rushstack/heft': 0.47.9 '@rushstack/heft': 0.47.9
'@types/crypto-js': 4.1.1
'@types/heft-jest': 1.0.3 '@types/heft-jest': 1.0.3
'@typescript-eslint/eslint-plugin': 5.42.0_8b6083565a963e7484743e25607ce89c '@typescript-eslint/eslint-plugin': 5.42.0_8b6083565a963e7484743e25607ce89c
'@typescript-eslint/parser': 5.42.0_eslint@8.26.0+typescript@4.7.4 '@typescript-eslint/parser': 5.42.0_eslint@8.26.0+typescript@4.7.4
crypto-js: 4.1.1
eslint: 8.26.0 eslint: 8.26.0
eslint-config-standard-with-typescript: 23.0.0_35db0d754f34ccffcc0e5a361183072e eslint-config-standard-with-typescript: 23.0.0_35db0d754f34ccffcc0e5a361183072e
eslint-plugin-import: 2.26.0_eslint@8.26.0 eslint-plugin-import: 2.26.0_eslint@8.26.0
eslint-plugin-n: 15.4.0_eslint@8.26.0 eslint-plugin-n: 15.4.0_eslint@8.26.0
eslint-plugin-node: 11.1.0_eslint@8.26.0 eslint-plugin-node: 11.1.0_eslint@8.26.0
eslint-plugin-promise: 6.1.1_eslint@8.26.0 eslint-plugin-promise: 6.1.1_eslint@8.26.0
node-fetch: 3.2.10
prettier: 2.7.1 prettier: 2.7.1
typescript: 4.7.4 typescript: 4.7.4
transitivePeerDependencies: transitivePeerDependencies:
@ -11727,7 +11806,7 @@ packages:
dev: false dev: false
file:projects/model-contact.tgz_typescript@4.7.4: file:projects/model-contact.tgz_typescript@4.7.4:
resolution: {integrity: sha512-/KNyMsjF/sEWcLixUTYNy530r/IgRQeikYoX3aDxRyXWlofDTZEGME03o/qqiNSxw34WTzu6mHUE3+6dTUFexg==, tarball: file:projects/model-contact.tgz} resolution: {integrity: sha512-1LXDgoRC+p48CfA8Dhk/JuUttYtwkJXaXADwFcvDJGLmWN062SEHy5mOCB56RcYxbBZvMOuRbKKkdZ7u678jzw==, tarball: file:projects/model-contact.tgz}
id: file:projects/model-contact.tgz id: file:projects/model-contact.tgz
name: '@rush-temp/model-contact' name: '@rush-temp/model-contact'
version: 0.0.0 version: 0.0.0
@ -11739,6 +11818,7 @@ packages:
'@types/node': 16.11.42 '@types/node': 16.11.42
'@typescript-eslint/eslint-plugin': 5.42.0_8b6083565a963e7484743e25607ce89c '@typescript-eslint/eslint-plugin': 5.42.0_8b6083565a963e7484743e25607ce89c
'@typescript-eslint/parser': 5.42.0_eslint@8.26.0+typescript@4.7.4 '@typescript-eslint/parser': 5.42.0_eslint@8.26.0+typescript@4.7.4
cross-fetch: 3.1.5
crypto-js: 4.1.1 crypto-js: 4.1.1
eslint: 8.26.0 eslint: 8.26.0
eslint-config-standard-with-typescript: 23.0.0_35db0d754f34ccffcc0e5a361183072e eslint-config-standard-with-typescript: 23.0.0_35db0d754f34ccffcc0e5a361183072e
@ -11748,8 +11828,10 @@ packages:
eslint-plugin-promise: 6.1.1_eslint@8.26.0 eslint-plugin-promise: 6.1.1_eslint@8.26.0
md5: 2.3.0 md5: 2.3.0
md5.js: 1.3.5 md5.js: 1.3.5
node-fetch: 3.2.10
prettier: 2.7.1 prettier: 2.7.1
transitivePeerDependencies: transitivePeerDependencies:
- encoding
- supports-color - supports-color
- typescript - typescript
dev: false dev: false
@ -12942,7 +13024,7 @@ packages:
dev: false dev: false
file:projects/presentation.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c: file:projects/presentation.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c:
resolution: {integrity: sha512-/eG/9sTCMrZJrJmVa0ePYhfOtw2omr+HgzqeCSmawUzczU8M1zoSHIJxkvhI/Vk4n8p7cTMfI6GFL7MN4sfiqg==, tarball: file:projects/presentation.tgz} resolution: {integrity: sha512-BRrMCOYZQWq7l4bHtorlsbF3Vd1xC3d1+HFwxAapz+2dWVdzTZa9LWbUnxdgOdVKnoPNEBshBb7yweuAz+QYvQ==, tarball: file:projects/presentation.tgz}
id: file:projects/presentation.tgz id: file:projects/presentation.tgz
name: '@rush-temp/presentation' name: '@rush-temp/presentation'
version: 0.0.0 version: 0.0.0
@ -14632,7 +14714,7 @@ packages:
dev: false dev: false
file:projects/ui.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c: file:projects/ui.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c:
resolution: {integrity: sha512-46COtAECm8v7tfsMRV9ol2UMMwpXYPwvp//qyVSlCIhNrWA7Mk7rBzMKeyDwi28EmjxasIbNhRQScOa5DTccig==, tarball: file:projects/ui.tgz} resolution: {integrity: sha512-tEGe2W6LdHgwewLIjCYDQwX+tSqWYivga5zeLBin75rsNZYJVujyTyDx7opwnVDZvCDmoVYRDCjv08X1XHzNEA==, tarball: file:projects/ui.tgz}
id: file:projects/ui.tgz id: file:projects/ui.tgz
name: '@rush-temp/ui' name: '@rush-temp/ui'
version: 0.0.0 version: 0.0.0
@ -14647,6 +14729,7 @@ packages:
eslint-plugin-node: 11.1.0_eslint@8.26.0 eslint-plugin-node: 11.1.0_eslint@8.26.0
eslint-plugin-promise: 6.1.1_eslint@8.26.0 eslint-plugin-promise: 6.1.1_eslint@8.26.0
eslint-plugin-svelte3: 4.0.0_eslint@8.26.0+svelte@3.48.0 eslint-plugin-svelte3: 4.0.0_eslint@8.26.0+svelte@3.48.0
fast-equals: 2.0.4
prettier: 2.7.1 prettier: 2.7.1
prettier-plugin-svelte: 2.8.0_prettier@2.7.1+svelte@3.48.0 prettier-plugin-svelte: 2.8.0_prettier@2.7.1+svelte@3.48.0
sass: 1.53.0 sass: 1.53.0

View File

@ -18,7 +18,6 @@
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0", "eslint-plugin-n": "^15.4.0",
"eslint": "^8.26.0", "eslint": "^8.26.0",
"@types/crypto-js": "^4.1.1",
"@types/heft-jest": "^1.0.3", "@types/heft-jest": "^1.0.3",
"@typescript-eslint/parser": "^5.41.0", "@typescript-eslint/parser": "^5.41.0",
"eslint-config-standard-with-typescript": "^23.0.0", "eslint-config-standard-with-typescript": "^23.0.0",
@ -40,6 +39,6 @@
"@hcengineering/contact": "~0.6.5", "@hcengineering/contact": "~0.6.5",
"@hcengineering/contact-resources": "~0.6.0", "@hcengineering/contact-resources": "~0.6.0",
"@hcengineering/view": "^0.6.1", "@hcengineering/view": "^0.6.1",
"crypto-js": "^4.1.1" "cross-fetch": "^3.1.5"
} }
} }

View File

@ -13,12 +13,20 @@
// limitations under the License. // limitations under the License.
// //
import { Employee, EmployeeAccount, AvatarType } from '@hcengineering/contact' import fetch from 'cross-fetch'
import {
Employee,
EmployeeAccount,
AvatarType,
buildGravatarId,
checkHasGravatar,
getAvatarColorForId
} from '@hcengineering/contact'
import { AccountRole, DOMAIN_TX, TxCreateDoc, TxOperations } from '@hcengineering/core' import { AccountRole, DOMAIN_TX, TxCreateDoc, TxOperations } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import core from '@hcengineering/model-core' import core from '@hcengineering/model-core'
import contact from './index' import contact from './index'
import MD5 from 'crypto-js/md5'
async function createSpace (tx: TxOperations): Promise<void> { async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, { const current = await tx.findOne(core.class.Space, {
@ -96,9 +104,13 @@ async function updateEmployeeAvatar (tx: TxOperations): Promise<void> {
if (employee === undefined) return if (employee === undefined) return
if (employee.avatar != null && employee.avatar !== undefined) return if (employee.avatar != null && employee.avatar !== undefined) return
const gravatarId = MD5(account.email.trim().toLowerCase()).toString() const gravatarId = buildGravatarId(account.email)
const hasGravatar = await checkHasGravatar(gravatarId, fetch)
await tx.update(employee, { await tx.update(employee, {
avatar: `${AvatarType.GRAVATAR}://${gravatarId}` avatar: hasGravatar
? `${AvatarType.GRAVATAR}://${gravatarId}`
: `${AvatarType.COLOR}://${getAvatarColorForId(employee._id)}`
}) })
}) })
await Promise.all(promises) await Promise.all(promises)

View File

@ -19,7 +19,6 @@
"@hcengineering/platform-rig": "~0.6.0", "@hcengineering/platform-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.41.0", "@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0", "@typescript-eslint/parser": "^5.41.0",
"@types/crypto-js": "^4.1.1",
"eslint-config-standard-with-typescript": "^23.0.0", "eslint-config-standard-with-typescript": "^23.0.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0", "eslint-plugin-n": "^15.4.0",
@ -44,7 +43,6 @@
"@hcengineering/image-cropper": "~0.6.0", "@hcengineering/image-cropper": "~0.6.0",
"@hcengineering/client": "^0.6.3", "@hcengineering/client": "^0.6.3",
"@hcengineering/setting": "~0.6.1", "@hcengineering/setting": "~0.6.1",
"fast-equals": "^2.0.3", "fast-equals": "^2.0.3"
"crypto-js": "^4.1.1"
} }
} }

View File

@ -16,12 +16,11 @@
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { DropdownLabelsIntl, AnySvelteComponent, showPopup, Label } from '@hcengineering/ui' import { DropdownLabelsIntl, AnySvelteComponent, showPopup, Label } from '@hcengineering/ui'
import { AvatarType } from '@hcengineering/contact' import { AvatarType, buildGravatarId, checkHasGravatar, getAvatarColorForId } from '@hcengineering/contact'
import { Asset } from '@hcengineering/platform' import { Asset } from '@hcengineering/platform'
import presentation from '..' import presentation from '..'
import { getAvatarTypeDropdownItems, getFileUrl, getAvatarColorForId } from '../utils' import { getAvatarTypeDropdownItems, getFileUrl } from '../utils'
import { buildGravatarId } from '../gravatar'
import Card from './Card.svelte' import Card from './Card.svelte'
import AvatarComponent from './Avatar.svelte' import AvatarComponent from './Avatar.svelte'
import EditAvatarPopup from './EditAvatarPopup.svelte' import EditAvatarPopup from './EditAvatarPopup.svelte'
@ -55,6 +54,12 @@
let selectedAvatar: string = initialSelectedAvatar let selectedAvatar: string = initialSelectedAvatar
let selectedFile: Blob | undefined = file let selectedFile: Blob | undefined = file
let hasGravatar = false
async function updateHasGravatar (email?: string) {
hasGravatar = !!email && (await checkHasGravatar(buildGravatarId(email)))
}
$: updateHasGravatar(email)
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
function submit () { function submit () {
@ -152,7 +157,7 @@
}} }}
> >
<DropdownLabelsIntl <DropdownLabelsIntl
items={getAvatarTypeDropdownItems(!!email)} items={getAvatarTypeDropdownItems(hasGravatar)}
label={presentation.string.SelectAvatar} label={presentation.string.SelectAvatar}
bind:selected={selectedAvatarType} bind:selected={selectedAvatarType}
on:selected={handleDropdownSelection} on:selected={handleDropdownSelection}

View File

@ -1,64 +0,0 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { IconSize } from '@hcengineering/ui'
import MD5 from 'crypto-js/md5'
/**
* @public
*/
export type GravatarPlaceholderType =
| '404'
| 'mp'
| 'identicon'
| 'monsterid'
| 'wavatar'
| 'retro'
| 'robohash'
| 'blank'
/**
* @public
*/
export function buildGravatarId (email: string): string {
return MD5(email.trim().toLowerCase()).toString()
}
/**
* @public
*/
export function getGravatarUrl (
gravatarId: string,
size: IconSize = 'full',
placeholder: GravatarPlaceholderType = 'identicon'
): string {
let width = 64
switch (size) {
case 'inline':
case 'tiny':
case 'x-small':
case 'small':
case 'medium':
width = 64
break
case 'large':
width = 256
break
case 'x-large':
width = 512
break
}
return `https://gravatar.com/avatar/${gravatarId}?s=${width}&d=${placeholder}`
}

View File

@ -47,7 +47,6 @@ export { connect, versionError } from './connect'
export { default } from './plugin' export { default } from './plugin'
export * from './types' export * from './types'
export * from './utils' export * from './utils'
export * from './gravatar'
export { presentationId } export { presentationId }
addStringsLoader(presentationId, async (lang: string) => { addStringsLoader(presentationId, async (lang: string) => {

View File

@ -235,7 +235,7 @@ export function getAttributePresenterClass (
return { attrClass, category } return { attrClass, category }
} }
export function getAvatarTypeDropdownItems (hasEmail: boolean): DropdownIntlItem[] { export function getAvatarTypeDropdownItems (hasGravatar: boolean): DropdownIntlItem[] {
return [ return [
{ {
id: AvatarType.COLOR, id: AvatarType.COLOR,
@ -245,7 +245,7 @@ export function getAvatarTypeDropdownItems (hasEmail: boolean): DropdownIntlItem
id: AvatarType.IMAGE, id: AvatarType.IMAGE,
label: contact.string.UseImage label: contact.string.UseImage
}, },
...(hasEmail ...(hasGravatar
? [ ? [
{ {
id: AvatarType.GRAVATAR, id: AvatarType.GRAVATAR,
@ -256,31 +256,6 @@ export function getAvatarTypeDropdownItems (hasEmail: boolean): DropdownIntlItem
] ]
} }
const AVATAR_COLORS = [
'#4674ca', // blue
'#315cac', // blue_dark
'#57be8c', // green
'#3fa372', // green_dark
'#f9a66d', // yellow_orange
'#ec5e44', // red
'#e63717', // red_dark
'#f868bc', // pink
'#6c5fc7', // purple
'#4e3fb4', // purple_dark
'#57b1be', // teal
'#847a8c' // gray
]
export function getAvatarColorForId (id: string): string {
let hash = 0
for (let i = 0; i < id.length; i++) {
hash += id.charCodeAt(i)
}
return AVATAR_COLORS[hash % AVATAR_COLORS.length]
}
export function getAvatarProviderId (avatar?: string | null): Ref<AvatarProvider> | undefined { export function getAvatarProviderId (avatar?: string | null): Ref<AvatarProvider> | undefined {
if (avatar === null || avatar === undefined || avatar === '') { if (avatar === null || avatar === undefined || avatar === '') {
return return

View File

@ -36,7 +36,8 @@
"@hcengineering/platform": "^0.6.7", "@hcengineering/platform": "^0.6.7",
"@hcengineering/theme": "^0.6.1", "@hcengineering/theme": "^0.6.1",
"@hcengineering/core": "^0.6.17", "@hcengineering/core": "^0.6.17",
"svelte": "^3.47" "svelte": "^3.47",
"fast-equals": "^2.0.3"
}, },
"repository": "https://github.com/hcenginneing/anticrm", "repository": "https://github.com/hcenginneing/anticrm",
"publishConfig": { "publishConfig": {

View File

@ -15,8 +15,10 @@
<script lang="ts"> <script lang="ts">
import { IntlString, Asset } from '@hcengineering/platform' import { IntlString, Asset } from '@hcengineering/platform'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { deepEqual } from 'fast-equals'
import type { AnySvelteComponent, TooltipAlignment, ButtonKind, ButtonSize, DropdownIntlItem } from '../types' import type { AnySvelteComponent, TooltipAlignment, ButtonKind, ButtonSize, DropdownIntlItem } from '../types'
import { showPopup } from '../popups' import { showPopup, closePopup } from '../popups'
import Button from './Button.svelte' import Button from './Button.svelte'
import DropdownLabelsPopupIntl from './DropdownLabelsPopupIntl.svelte' import DropdownLabelsPopupIntl from './DropdownLabelsPopupIntl.svelte'
import Label from './Label.svelte' import Label from './Label.svelte'
@ -42,6 +44,30 @@
} }
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
function openPopup () {
if (!opened) {
opened = true
showPopup(DropdownLabelsPopupIntl, { items, selected }, container, (result) => {
if (result) {
selected = result
dispatch('selected', result)
}
opened = false
})
}
}
let prevItems: DropdownIntlItem[]
$: if (!deepEqual(items, prevItems)) {
prevItems = items
if (opened) {
closePopup()
opened = false
openPopup()
}
}
</script> </script>
<div bind:this={container} class="min-w-0"> <div bind:this={container} class="min-w-0">
@ -53,18 +79,7 @@
{disabled} {disabled}
{justify} {justify}
showTooltip={{ label, direction: labelDirection }} showTooltip={{ label, direction: labelDirection }}
on:click={() => { on:click={openPopup}
if (!opened) {
opened = true
showPopup(DropdownLabelsPopupIntl, { items, selected }, container, (result) => {
if (result) {
selected = result
dispatch('selected', result)
}
opened = false
})
}
}}
> >
<span slot="content" class="overflow-label disabled"> <span slot="content" class="overflow-label disabled">
<Label label={selectedItem ? selectedItem.label : label} /> <Label label={selectedItem ? selectedItem.label : label} />

View File

@ -14,19 +14,11 @@
// limitations under the License. // limitations under the License.
// //
import { Channel, Contact, Employee, formatName } from '@hcengineering/contact' import { Channel, Contact, Employee, formatName, getGravatarUrl } from '@hcengineering/contact'
import { Class, Client, DocumentQuery, Ref, RelatedDocument, WithLookup } from '@hcengineering/core' import { Class, Client, DocumentQuery, Ref, RelatedDocument, WithLookup } from '@hcengineering/core'
import { leaveWorkspace } from '@hcengineering/login-resources' import { leaveWorkspace } from '@hcengineering/login-resources'
import { Resources } from '@hcengineering/platform' import { Resources } from '@hcengineering/platform'
import { import { Avatar, getClient, MessageBox, ObjectSearchResult, UserInfo, getFileUrl } from '@hcengineering/presentation'
Avatar,
getClient,
MessageBox,
ObjectSearchResult,
UserInfo,
getFileUrl,
getGravatarUrl
} from '@hcengineering/presentation'
import { showPopup } from '@hcengineering/ui' import { showPopup } from '@hcengineering/ui'
import Channels from './components/Channels.svelte' import Channels from './components/Channels.svelte'
import ChannelsDropdown from './components/ChannelsDropdown.svelte' import ChannelsDropdown from './components/ChannelsDropdown.svelte'

View File

@ -13,6 +13,7 @@
}, },
"devDependencies": { "devDependencies": {
"@hcengineering/platform-rig": "~0.6.0", "@hcengineering/platform-rig": "~0.6.0",
"@types/crypto-js": "^4.1.1",
"@types/heft-jest": "^1.0.3", "@types/heft-jest": "^1.0.3",
"@typescript-eslint/eslint-plugin": "^5.41.0", "@typescript-eslint/eslint-plugin": "^5.41.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
@ -29,7 +30,8 @@
"@hcengineering/platform": "^0.6.7", "@hcengineering/platform": "^0.6.7",
"@hcengineering/ui": "^0.6.2", "@hcengineering/ui": "^0.6.2",
"@hcengineering/core": "^0.6.17", "@hcengineering/core": "^0.6.17",
"@hcengineering/view": "^0.6.1" "@hcengineering/view": "^0.6.1",
"crypto-js": "^4.1.1"
}, },
"repository": "https://github.com/hcengineering/anticrm", "repository": "https://github.com/hcengineering/anticrm",
"publishConfig": { "publishConfig": {

View File

@ -13,6 +13,8 @@
// limitations under the License. // limitations under the License.
// //
import MD5 from 'crypto-js/md5'
import { import {
Account, Account,
AttachedData, AttachedData,
@ -353,7 +355,6 @@ export async function findContacts (
/** /**
* @public * @public
*/ */
export async function findPerson ( export async function findPerson (
client: Client, client: Client,
@ -363,3 +364,89 @@ export async function findPerson (
const result = await findContacts(client, contactPlugin.class.Person, person, channels) const result = await findContacts(client, contactPlugin.class.Person, person, channels)
return result.contacts as Person[] return result.contacts as Person[]
} }
/**
* @public
*/
export type GravatarPlaceholderType =
| '404'
| 'mp'
| 'identicon'
| 'monsterid'
| 'wavatar'
| 'retro'
| 'robohash'
| 'blank'
/**
* @public
*/
export function buildGravatarId (email: string): string {
return MD5(email.trim().toLowerCase()).toString()
}
/**
* @public
*/
export function getGravatarUrl (
gravatarId: string,
size: IconSize = 'full',
placeholder: GravatarPlaceholderType = 'identicon'
): string {
let width = 64
switch (size) {
case 'inline':
case 'tiny':
case 'x-small':
case 'small':
case 'medium':
width = 64
break
case 'large':
width = 256
break
case 'x-large':
width = 512
break
}
return `https://gravatar.com/avatar/${gravatarId}?s=${width}&d=${placeholder}`
}
/**
* @public
*/
export async function checkHasGravatar (gravatarId: string, fetch?: typeof window.fetch): Promise<boolean> {
try {
return (await (fetch ?? window.fetch)(getGravatarUrl(gravatarId, 'full', '404'))).ok
} catch {
return false
}
}
const AVATAR_COLORS = [
'#4674ca', // blue
'#315cac', // blue_dark
'#57be8c', // green
'#3fa372', // green_dark
'#f9a66d', // yellow_orange
'#ec5e44', // red
'#e63717', // red_dark
'#f868bc', // pink
'#6c5fc7', // purple
'#4e3fb4', // purple_dark
'#57b1be', // teal
'#847a8c' // gray
]
/**
* @public
*/
export function getAvatarColorForId (id: string): string {
let hash = 0
for (let i = 0; i < id.length; i++) {
hash += id.charCodeAt(i)
}
return AVATAR_COLORS[hash % AVATAR_COLORS.length]
}

View File

@ -4,6 +4,7 @@
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./lib", "outDir": "./lib",
"lib": ["esnext", "dom"] "lib": ["esnext", "dom"],
"esModuleInterop": true
} }
} }

View File

@ -13,7 +13,14 @@
// limitations under the f. // limitations under the f.
// //
import contact, { AvatarType, combineName, Employee } from '@hcengineering/contact' import contact, {
AvatarType,
combineName,
Employee,
buildGravatarId,
checkHasGravatar,
getAvatarColorForId
} from '@hcengineering/contact'
import core, { AccountRole, Ref, TxOperations } from '@hcengineering/core' import core, { AccountRole, Ref, TxOperations } from '@hcengineering/core'
import platform, { import platform, {
getMetadata, getMetadata,
@ -28,7 +35,7 @@ import platform, {
} from '@hcengineering/platform' } from '@hcengineering/platform'
import { decodeToken, generateToken } from '@hcengineering/server-token' import { decodeToken, generateToken } from '@hcengineering/server-token'
import toolPlugin, { connect, initModel, upgradeModel, version } from '@hcengineering/server-tool' import toolPlugin, { connect, initModel, upgradeModel, version } from '@hcengineering/server-tool'
import { createHash, pbkdf2Sync, randomBytes } from 'crypto' import { pbkdf2Sync, randomBytes } from 'crypto'
import { Binary, Db, ObjectId } from 'mongodb' import { Binary, Db, ObjectId } from 'mongodb'
const WORKSPACE_COLLECTION = 'workspace' const WORKSPACE_COLLECTION = 'workspace'
@ -484,13 +491,22 @@ export async function assignWorkspace (db: Db, email: string, workspace: string)
} }
async function createEmployee (ops: TxOperations, name: string, email: string): Promise<Ref<Employee>> { async function createEmployee (ops: TxOperations, name: string, email: string): Promise<Ref<Employee>> {
const gravatarId = createHash('md5').update(email.trim().toLowerCase()).digest('hex') const gravatarId = buildGravatarId(email)
return await ops.createDoc(contact.class.Employee, contact.space.Employee, { const hasGravatar = await checkHasGravatar(gravatarId)
const id = await ops.createDoc(contact.class.Employee, contact.space.Employee, {
name, name,
city: '', city: '',
avatar: `${AvatarType.GRAVATAR}://${gravatarId}`, ...(hasGravatar ? { avatar: `${AvatarType.GRAVATAR}://${gravatarId}` } : {}),
active: true active: true
}) })
if (!hasGravatar) {
await ops.updateDoc(contact.class.Employee, contact.space.Employee, id, {
avatar: `${AvatarType.COLOR}://${getAvatarColorForId(id)}`
})
}
return id
} }
async function createEmployeeAccount (account: Account, workspace: string): Promise<void> { async function createEmployeeAccount (account: Account, workspace: string): Promise<void> {