mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-23 18:26:32 -05:00
feature: add identicon generation to users without photo
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
55398e2428
commit
a8b3b117dc
17 changed files with 210 additions and 23 deletions
17
.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch
Normal file
17
.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch
Normal 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",
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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])
|
||||
}
|
|
@ -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} />
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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(
|
||||
() => ({
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
81
yarn.lock
81
yarn.lock
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue