feature: add identicon generation to users without photo

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2023-10-07 13:45:24 +02:00
parent 55398e2428
commit a8b3b117dc
17 changed files with 210 additions and 23 deletions

View file

@ -0,0 +1,17 @@
diff --git a/package.json b/package.json
index 4619da270df88b972b4f9ea95aeffc32aa52e341..944cadb5e2ab4e1be596e76255ee4f9ae7572b18 100644
--- a/package.json
+++ b/package.json
@@ -16,10 +16,10 @@
"license": "MIT",
"author": "Florian Körner <contact@florian-koerner.com>",
"type": "module",
- "main": "./lib/node/index.js",
+ "main": "./lib/index.js",
"browser": "./lib/index.js",
"exports": {
- "node": "./lib/node/index.js",
+ "node": "./lib/index.js",
"default": "./lib/index.js"
},
"types": "./lib/index.d.ts",

View file

@ -1,3 +1,3 @@
SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: CC0-1.0
SPDX-License-Identifier: AGPL-3.0-only

View file

@ -46,6 +46,8 @@
"@codemirror/state": "6.2.1",
"@codemirror/theme-one-dark": "6.1.2",
"@codemirror/view": "6.20.2",
"@dicebear/core": "7.0.1",
"@dicebear/identicon": "7.0.1",
"@fontsource/source-sans-pro": "5.0.8",
"@hedgedoc/commons": "workspace:commons",
"@hedgedoc/html-to-react": "workspace:html-to-react",

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -7,5 +7,5 @@
export interface UserInfo {
username: string
displayName: string
photo: string
photoUrl: string
}

View file

@ -104,3 +104,47 @@ exports[`UserAvatar renders the user avatar in size sm 1`] = `
</span>
</div>
`;
exports[`UserAvatar uses identicon when empty photoUrl is given 1`] = `
<div>
<span
class="d-inline-flex align-items-center "
>
<img
alt="common.avatarOf"
class="rounded user-image"
height="20"
src="data:image/x-other,identicon-mock"
title="common.avatarOf"
width="20"
/>
<span
class="ms-2 me-1 user-line-name"
>
test
</span>
</span>
</div>
`;
exports[`UserAvatar uses identicon when no photoUrl is given 1`] = `
<div>
<span
class="d-inline-flex align-items-center "
>
<img
alt="common.avatarOf"
class="rounded user-image"
height="20"
src="data:image/x-other,identicon-mock"
title="common.avatarOf"
width="20"
/>
<span
class="ms-2 me-1 user-line-name"
>
test
</span>
</span>
</div>
`;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useMemo } from 'react'
import { createAvatar } from '@dicebear/core'
import identicon from '@dicebear/identicon'
/**
* Returns the correct avatar url for a user.
* When an empty or no photoUrl is given, a random avatar is generated from the displayName.
*
* @param photoUrl The photo url of the user to use. Maybe empty or not set.
* @param displayName The display name of the user to use as input to the random avatar.
* @return The correct avatar url for the user.
*/
export const useAvatarUrl = (photoUrl: string | undefined, displayName: string): string => {
return useMemo(() => {
if (photoUrl && photoUrl.trim() !== '') {
return photoUrl
}
const avatar = createAvatar(identicon, {
seed: displayName
})
return avatar.toDataUriSync()
}, [photoUrl, displayName])
}

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -19,5 +19,5 @@ export interface UserAvatarForUserProps extends Omit<UserAvatarProps, 'photoUrl'
* @param props remaining avatar props
*/
export const UserAvatarForUser: React.FC<UserAvatarForUserProps> = ({ user, ...props }) => {
return <UserAvatar displayName={user.displayName} photoUrl={user.photo} {...props} />
return <UserAvatar displayName={user.displayName} photoUrl={user.photoUrl} {...props} />
}

View file

@ -7,12 +7,20 @@ import type { UserInfo } from '../../../api/users/types'
import { mockI18n } from '../../../test-utils/mock-i18n'
import { UserAvatarForUser } from './user-avatar-for-user'
import { render } from '@testing-library/react'
import { UserAvatar } from './user-avatar'
jest.mock('@dicebear/identicon', () => null)
jest.mock('@dicebear/core', () => ({
createAvatar: jest.fn(() => ({
toDataUriSync: jest.fn(() => 'data:image/x-other,identicon-mock')
}))
}))
describe('UserAvatar', () => {
const user: UserInfo = {
username: 'boatface',
displayName: 'Boaty McBoatFace',
photo: 'https://example.com/test.png'
photoUrl: 'https://example.com/test.png'
}
beforeEach(async () => {
@ -41,4 +49,14 @@ describe('UserAvatar', () => {
const view = render(<UserAvatarForUser user={user} showName={false} />)
expect(view.container).toMatchSnapshot()
})
it('uses identicon when no photoUrl is given', () => {
const view = render(<UserAvatar displayName={'test'} />)
expect(view.container).toMatchSnapshot()
})
it('uses identicon when empty photoUrl is given', () => {
const view = render(<UserAvatar displayName={'test'} photoUrl={''} />)
expect(view.container).toMatchSnapshot()
})
})

View file

@ -1,15 +1,15 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import { ShowIf } from '../show-if/show-if'
import defaultAvatar from './default-avatar.png'
import styles from './user-avatar.module.scss'
import React, { useCallback, useMemo } from 'react'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import type { OverlayInjectedProps } from 'react-bootstrap/Overlay'
import { useAvatarUrl } from './hooks/use-avatar-url'
export interface UserAvatarProps {
size?: 'sm' | 'lg'
@ -45,9 +45,7 @@ export const UserAvatar: React.FC<UserAvatarProps> = ({
}
}, [size])
const avatarUrl = useMemo(() => {
return photoUrl || defaultAvatar.src
}, [photoUrl])
const avatarUrl = useAvatarUrl(photoUrl, displayName)
const imageTranslateOptions = useMemo(
() => ({

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -15,7 +15,7 @@ export const fetchAndSetUser: () => Promise<void> = async () => {
setUser({
username: me.username,
displayName: me.displayName,
photo: me.photo,
photoUrl: me.photoUrl,
authProvider: me.authProvider,
email: me.email
})

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -15,7 +15,7 @@ const handler = (req: NextApiRequest, res: NextApiResponse): void => {
}
respondToMatchingRequest<LoginUserInfo>(HttpMethod.GET, req, res, {
username: 'mock',
photo: '/public/img/avatar.png',
photoUrl: '/public/img/avatar.png',
displayName: 'Mock User',
authProvider: 'local',
email: 'mock@hedgedoc.test'

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -11,7 +11,7 @@ const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
username: 'erik',
displayName: 'Erik',
photo: '/public/img/avatar.png'
photoUrl: '/public/img/avatar.png'
})
}

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -11,7 +11,7 @@ const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
username: 'molly',
displayName: 'Molly',
photo: '/public/img/avatar.png'
photoUrl: '/public/img/avatar.png'
})
}

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -11,7 +11,7 @@ const handler = (req: NextApiRequest, res: NextApiResponse): void => {
respondToMatchingRequest<UserInfo>(HttpMethod.GET, req, res, {
username: 'tilman',
displayName: 'Tilman',
photo: '/public/img/avatar.png'
photoUrl: '/public/img/avatar.png'
})
}

View file

@ -39,7 +39,8 @@
"eventemitter2@6.4.9": "patch:eventemitter2@npm%3A6.4.9#./.yarn/patches/eventemitter2-npm-6.4.9-ba37798a18.patch",
"yjs@13.6.8": "patch:yjs@npm%3A13.6.8#./.yarn/patches/yjs-remove-import-warning-in-test.patch",
"vega-canvas@^1.2.6": "patch:vega-canvas@npm%3A1.2.7#./.yarn/patches/remove-vega-canvas-node.patch",
"vega-canvas@^1.2.7": "patch:vega-canvas@npm%3A1.2.7#./.yarn/patches/remove-vega-canvas-node.patch"
"vega-canvas@^1.2.7": "patch:vega-canvas@npm%3A1.2.7#./.yarn/patches/remove-vega-canvas-node.patch",
"@dicebear/converter@7.0.1": "patch:@dicebear/converter@npm%3A7.0.1#./.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch"
},
"devDependencies": {
"dotenv-cli": "7.3.0",

View file

@ -2112,6 +2112,67 @@ __metadata:
languageName: node
linkType: hard
"@dicebear/converter@npm:7.0.1":
version: 7.0.1
resolution: "@dicebear/converter@npm:7.0.1"
dependencies:
"@types/json-schema": ^7.0.11
tmp-promise: ^3.0.3
peerDependencies:
"@resvg/resvg-js": ^2.4.1
exiftool-vendored: ^22.0.0
sharp: ^0.32.1
peerDependenciesMeta:
"@resvg/resvg-js":
optional: true
exiftool-vendored:
optional: true
sharp:
optional: true
checksum: ba7b631c5ce4bd14740035465e622c4a9daf6f67ddcbff635ca8f7486a9063fe9cca47f2a4db2df53913a063a1b52df2d8015c72ac8d8a57b4e69818411845ef
languageName: node
linkType: hard
"@dicebear/converter@patch:@dicebear/converter@npm%3A7.0.1#./.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch::locator=hedgedoc%40workspace%3A.":
version: 7.0.1
resolution: "@dicebear/converter@patch:@dicebear/converter@npm%3A7.0.1#./.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch::version=7.0.1&hash=659bc7&locator=hedgedoc%40workspace%3A."
dependencies:
"@types/json-schema": ^7.0.11
tmp-promise: ^3.0.3
peerDependencies:
"@resvg/resvg-js": ^2.4.1
exiftool-vendored: ^22.0.0
sharp: ^0.32.1
peerDependenciesMeta:
"@resvg/resvg-js":
optional: true
exiftool-vendored:
optional: true
sharp:
optional: true
checksum: f20da5a44bed334c788568dc12fd49887de93171f71a23ba37397cdc00e693ad2bead5bed158fb06bea78ffe67c3e5c7cb611982c42da37457f319090dfbcf12
languageName: node
linkType: hard
"@dicebear/core@npm:7.0.1":
version: 7.0.1
resolution: "@dicebear/core@npm:7.0.1"
dependencies:
"@dicebear/converter": 7.0.1
"@types/json-schema": ^7.0.11
checksum: 9ba71a4946ae0d4fdda9074e7f4db8c545cfb4cb13c0106517943c3de5c76618bc60eb61e01d909961c8f66f5c46b9dc03491a9f7e2075b7e9f3581980ffbac9
languageName: node
linkType: hard
"@dicebear/identicon@npm:7.0.1":
version: 7.0.1
resolution: "@dicebear/identicon@npm:7.0.1"
peerDependencies:
"@dicebear/core": ^7.0.0
checksum: 93eb3f19b2112bcf32d6f7768fa569c080100628f90b8ecb8e633a82cdf1e943fa6ab93dc627d3631c83cb81adb643c8637d1c0f924cc7629e7d070fac6ca16d
languageName: node
linkType: hard
"@emotion/cache@npm:^10.0.27":
version: 10.0.29
resolution: "@emotion/cache@npm:10.0.29"
@ -2412,6 +2473,8 @@ __metadata:
"@codemirror/state": 6.2.1
"@codemirror/theme-one-dark": 6.1.2
"@codemirror/view": 6.20.2
"@dicebear/core": 7.0.1
"@dicebear/identicon": 7.0.1
"@fontsource/source-sans-pro": 5.0.8
"@hedgedoc/commons": "workspace:commons"
"@hedgedoc/html-to-react": "workspace:html-to-react"
@ -4648,6 +4711,13 @@ __metadata:
languageName: node
linkType: hard
"@types/json-schema@npm:^7.0.11":
version: 7.0.13
resolution: "@types/json-schema@npm:7.0.13"
checksum: 345df21a678fa72fb389f35f33de77833d09d4a142bb2bcb27c18690efa4cf70fc2876e43843cefb3fbdb9fcb12cd3e970a90936df30f53bbee899865ff605ab
languageName: node
linkType: hard
"@types/json5@npm:^0.0.29":
version: 0.0.29
resolution: "@types/json5@npm:0.0.29"
@ -17183,6 +17253,15 @@ __metadata:
languageName: node
linkType: hard
"tmp-promise@npm:^3.0.3":
version: 3.0.3
resolution: "tmp-promise@npm:3.0.3"
dependencies:
tmp: ^0.2.0
checksum: f854f5307dcee6455927ec3da9398f139897faf715c5c6dcee6d9471ae85136983ea06662eba2edf2533bdcb0fca66d16648e79e14381e30c7fb20be9c1aa62c
languageName: node
linkType: hard
"tmp@npm:^0.0.33":
version: 0.0.33
resolution: "tmp@npm:0.0.33"
@ -17192,7 +17271,7 @@ __metadata:
languageName: node
linkType: hard
"tmp@npm:~0.2.1":
"tmp@npm:^0.2.0, tmp@npm:~0.2.1":
version: 0.2.1
resolution: "tmp@npm:0.2.1"
dependencies: