diff --git a/.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch b/.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch new file mode 100644 index 000000000..1bfd0c31a --- /dev/null +++ b/.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch @@ -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 ", + "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", diff --git a/frontend/src/components/common/user-avatar/default-avatar.png.license b/.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch.license similarity index 65% rename from frontend/src/components/common/user-avatar/default-avatar.png.license rename to .yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch.license index c223474fb..2053aa609 100644 --- a/frontend/src/components/common/user-avatar/default-avatar.png.license +++ b/.yarn/patches/@dicebear-converter-npm-7.0.1-cb9d17808e.patch.license @@ -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 diff --git a/frontend/package.json b/frontend/package.json index 433976ed7..16cf4eee8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/api/users/types.ts b/frontend/src/api/users/types.ts index 96e6f0d92..f4ad524e6 100644 --- a/frontend/src/api/users/types.ts +++ b/frontend/src/api/users/types.ts @@ -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 } diff --git a/frontend/src/components/common/user-avatar/__snapshots__/user-avatar.spec.tsx.snap b/frontend/src/components/common/user-avatar/__snapshots__/user-avatar.spec.tsx.snap index 7be911f06..ecddb7e72 100644 --- a/frontend/src/components/common/user-avatar/__snapshots__/user-avatar.spec.tsx.snap +++ b/frontend/src/components/common/user-avatar/__snapshots__/user-avatar.spec.tsx.snap @@ -104,3 +104,47 @@ exports[`UserAvatar renders the user avatar in size sm 1`] = ` `; + +exports[`UserAvatar uses identicon when empty photoUrl is given 1`] = ` +
+ + common.avatarOf + + test + + +
+`; + +exports[`UserAvatar uses identicon when no photoUrl is given 1`] = ` +
+ + common.avatarOf + + test + + +
+`; diff --git a/frontend/src/components/common/user-avatar/default-avatar.png b/frontend/src/components/common/user-avatar/default-avatar.png deleted file mode 100644 index 1f5de5740..000000000 Binary files a/frontend/src/components/common/user-avatar/default-avatar.png and /dev/null differ diff --git a/frontend/src/components/common/user-avatar/hooks/use-avatar-url.ts b/frontend/src/components/common/user-avatar/hooks/use-avatar-url.ts new file mode 100644 index 000000000..99f5506f4 --- /dev/null +++ b/frontend/src/components/common/user-avatar/hooks/use-avatar-url.ts @@ -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]) +} diff --git a/frontend/src/components/common/user-avatar/user-avatar-for-user.tsx b/frontend/src/components/common/user-avatar/user-avatar-for-user.tsx index 9af787f71..c576d76b9 100644 --- a/frontend/src/components/common/user-avatar/user-avatar-for-user.tsx +++ b/frontend/src/components/common/user-avatar/user-avatar-for-user.tsx @@ -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 = ({ user, ...props }) => { - return + return } diff --git a/frontend/src/components/common/user-avatar/user-avatar.spec.tsx b/frontend/src/components/common/user-avatar/user-avatar.spec.tsx index 3f59e1910..91cc659af 100644 --- a/frontend/src/components/common/user-avatar/user-avatar.spec.tsx +++ b/frontend/src/components/common/user-avatar/user-avatar.spec.tsx @@ -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() expect(view.container).toMatchSnapshot() }) + + it('uses identicon when no photoUrl is given', () => { + const view = render() + expect(view.container).toMatchSnapshot() + }) + + it('uses identicon when empty photoUrl is given', () => { + const view = render() + expect(view.container).toMatchSnapshot() + }) }) diff --git a/frontend/src/components/common/user-avatar/user-avatar.tsx b/frontend/src/components/common/user-avatar/user-avatar.tsx index 106e11a2e..51ea44a12 100644 --- a/frontend/src/components/common/user-avatar/user-avatar.tsx +++ b/frontend/src/components/common/user-avatar/user-avatar.tsx @@ -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 = ({ } }, [size]) - const avatarUrl = useMemo(() => { - return photoUrl || defaultAvatar.src - }, [photoUrl]) + const avatarUrl = useAvatarUrl(photoUrl, displayName) const imageTranslateOptions = useMemo( () => ({ diff --git a/frontend/src/components/login-page/utils/fetch-and-set-user.ts b/frontend/src/components/login-page/utils/fetch-and-set-user.ts index 6734bb3ee..6008b366e 100644 --- a/frontend/src/components/login-page/utils/fetch-and-set-user.ts +++ b/frontend/src/components/login-page/utils/fetch-and-set-user.ts @@ -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 = async () => { setUser({ username: me.username, displayName: me.displayName, - photo: me.photo, + photoUrl: me.photoUrl, authProvider: me.authProvider, email: me.email }) diff --git a/frontend/src/pages/api/private/me/index.ts b/frontend/src/pages/api/private/me/index.ts index 130ae8641..4432f3014 100644 --- a/frontend/src/pages/api/private/me/index.ts +++ b/frontend/src/pages/api/private/me/index.ts @@ -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(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' diff --git a/frontend/src/pages/api/private/users/erik.ts b/frontend/src/pages/api/private/users/erik.ts index f42614d44..bd3855b88 100644 --- a/frontend/src/pages/api/private/users/erik.ts +++ b/frontend/src/pages/api/private/users/erik.ts @@ -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(HttpMethod.GET, req, res, { username: 'erik', displayName: 'Erik', - photo: '/public/img/avatar.png' + photoUrl: '/public/img/avatar.png' }) } diff --git a/frontend/src/pages/api/private/users/molly.ts b/frontend/src/pages/api/private/users/molly.ts index 36741d757..ee8d58087 100644 --- a/frontend/src/pages/api/private/users/molly.ts +++ b/frontend/src/pages/api/private/users/molly.ts @@ -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(HttpMethod.GET, req, res, { username: 'molly', displayName: 'Molly', - photo: '/public/img/avatar.png' + photoUrl: '/public/img/avatar.png' }) } diff --git a/frontend/src/pages/api/private/users/tilman.ts b/frontend/src/pages/api/private/users/tilman.ts index 528617ad0..039860086 100644 --- a/frontend/src/pages/api/private/users/tilman.ts +++ b/frontend/src/pages/api/private/users/tilman.ts @@ -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(HttpMethod.GET, req, res, { username: 'tilman', displayName: 'Tilman', - photo: '/public/img/avatar.png' + photoUrl: '/public/img/avatar.png' }) } diff --git a/package.json b/package.json index d3b7c0cc7..d2758fd4e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 77b8df62e..68900e063 100644 --- a/yarn.lock +++ b/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: