mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-12-22 17:41:32 +00:00
fix(permissions): show guest avatar when note owner is anonymous
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
62dfe4df72
commit
ebf8e3a759
7 changed files with 127 additions and 11 deletions
|
@ -0,0 +1,16 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GuestUserAvatar renders the guest user avatar correctly 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="d-inline-flex align-items-center "
|
||||
>
|
||||
BootstrapIconMock_Person
|
||||
<span
|
||||
class="ms-2 me-1 user-line-name"
|
||||
>
|
||||
common.guestUser
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
|
@ -105,6 +105,40 @@ exports[`UserAvatar renders the user avatar in size sm 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserAvatar uses custom photo component if provided 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="d-inline-flex align-items-center "
|
||||
>
|
||||
<div>
|
||||
Custom Photo
|
||||
</div>
|
||||
<span
|
||||
class="ms-2 me-1 user-line-name"
|
||||
>
|
||||
test
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserAvatar uses custom photo component preferred over photoUrl 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="d-inline-flex align-items-center "
|
||||
>
|
||||
<div>
|
||||
Custom Photo
|
||||
</div>
|
||||
<span
|
||||
class="ms-2 me-1 user-line-name"
|
||||
>
|
||||
test
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`UserAvatar uses identicon when empty photoUrl is given 1`] = `
|
||||
<div>
|
||||
<span
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { mockI18n } from '../../../test-utils/mock-i18n'
|
||||
import { render } from '@testing-library/react'
|
||||
import { GuestUserAvatar } from './guest-user-avatar'
|
||||
|
||||
jest.mock('@dicebear/identicon', () => null)
|
||||
jest.mock('@dicebear/core', () => ({
|
||||
createAvatar: jest.fn(() => ({
|
||||
toDataUri: jest.fn(() => 'data:image/x-other,identicon-mock')
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('GuestUserAvatar', () => {
|
||||
beforeEach(async () => {
|
||||
await mockI18n()
|
||||
})
|
||||
|
||||
it('renders the guest user avatar correctly', () => {
|
||||
const view = render(<GuestUserAvatar />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React from 'react'
|
||||
import type { UserAvatarProps } from './user-avatar'
|
||||
import { UserAvatar } from './user-avatar'
|
||||
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
|
||||
import { Person as IconPerson } from 'react-bootstrap-icons'
|
||||
|
||||
export type GuestUserAvatarProps = Omit<UserAvatarProps, 'displayName' | 'photoUrl' | 'username'>
|
||||
|
||||
/**
|
||||
* The avatar component for an anonymous user.
|
||||
* @param props The properties of the guest user avatar ({@link UserAvatarProps})
|
||||
*/
|
||||
export const GuestUserAvatar: React.FC<GuestUserAvatarProps> = (props) => {
|
||||
const label = useTranslatedText('common.guestUser')
|
||||
return <UserAvatar displayName={label} photoComponent={<IconPerson />} {...props} />
|
||||
}
|
|
@ -59,4 +59,16 @@ describe('UserAvatar', () => {
|
|||
const view = render(<UserAvatar displayName={'test'} photoUrl={''} />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('uses custom photo component if provided', () => {
|
||||
const view = render(<UserAvatar displayName={'test'} photoComponent={<div>Custom Photo</div>} />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('uses custom photo component preferred over photoUrl', () => {
|
||||
const view = render(
|
||||
<UserAvatar displayName={'test'} photoComponent={<div>Custom Photo</div>} photoUrl={user.photoUrl} />
|
||||
)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface UserAvatarProps {
|
|||
photoUrl?: string
|
||||
displayName: string
|
||||
username?: string | null
|
||||
photoComponent?: React.ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,6 +25,8 @@ export interface UserAvatarProps {
|
|||
* @param size The size in which the user image should be shown.
|
||||
* @param additionalClasses Additional CSS classes that will be added to the container.
|
||||
* @param showName true when the name should be displayed alongside the image, false otherwise. Defaults to true.
|
||||
* @param username The username to use for generating the fallback avatar image.
|
||||
* @param photoComponent A custom component to use as the user's photo.
|
||||
*/
|
||||
export const UserAvatar: React.FC<UserAvatarProps> = ({
|
||||
photoUrl,
|
||||
|
@ -31,7 +34,8 @@ export const UserAvatar: React.FC<UserAvatarProps> = ({
|
|||
size,
|
||||
additionalClasses = '',
|
||||
showName = true,
|
||||
username
|
||||
username,
|
||||
photoComponent
|
||||
}) => {
|
||||
const imageSize = useMemo(() => {
|
||||
switch (size) {
|
||||
|
@ -56,15 +60,17 @@ export const UserAvatar: React.FC<UserAvatarProps> = ({
|
|||
|
||||
return (
|
||||
<span className={'d-inline-flex align-items-center ' + additionalClasses}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={avatarUrl}
|
||||
className={`rounded ${styles['user-image']}`}
|
||||
alt={imgDescription}
|
||||
title={imgDescription}
|
||||
height={imageSize}
|
||||
width={imageSize}
|
||||
/>
|
||||
{photoComponent ?? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={avatarUrl}
|
||||
className={`rounded ${styles['user-image']}`}
|
||||
alt={imgDescription}
|
||||
title={imgDescription}
|
||||
height={imageSize}
|
||||
width={imageSize}
|
||||
/>
|
||||
)}
|
||||
{showName && <span className={`ms-2 me-1 ${styles['user-line-name']}`}>{displayName}</span>}
|
||||
</span>
|
||||
)
|
||||
|
|
|
@ -11,6 +11,7 @@ import type { PermissionDisabledProps } from './permission-disabled.prop'
|
|||
import React, { Fragment } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { Pencil as IconPencil } from 'react-bootstrap-icons'
|
||||
import { GuestUserAvatar } from '../../../../../common/user-avatar/guest-user-avatar'
|
||||
|
||||
export interface PermissionOwnerInfoProps {
|
||||
onEditOwner: () => void
|
||||
|
@ -30,7 +31,7 @@ export const PermissionOwnerInfo: React.FC<PermissionOwnerInfoProps & Permission
|
|||
const buttonTitle = useTranslatedText('editor.modal.permissions.ownerChange.button')
|
||||
|
||||
if (!noteOwner) {
|
||||
return null
|
||||
return <GuestUserAvatar />
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
Loading…
Reference in a new issue