mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-02-23 10:26:16 -05:00
218 lines
6.6 KiB
TypeScript
218 lines
6.6 KiB
TypeScript
import { Col } from 'web/components/layout/col'
|
|
import clsx from 'clsx'
|
|
import { Row } from 'web/components/layout/row'
|
|
import { Avatar } from 'web/components/widgets/avatar'
|
|
import { RelativeTimestamp } from 'web/components/relative-timestamp'
|
|
import { Content } from 'web/components/widgets/editor'
|
|
import { ChatMessage } from 'common/chat-message'
|
|
import { first, last } from 'lodash'
|
|
import { memo, useState } from 'react'
|
|
import { MultipleOrSingleAvatars } from 'web/components/multiple-or-single-avatars'
|
|
import { Modal, MODAL_CLASS } from 'web/components/layout/modal'
|
|
import { UserAvatarAndBadge } from 'web/components/widgets/user-link'
|
|
import Link from 'next/link'
|
|
import DropdownMenu from 'web/components/comments/dropdown-menu'
|
|
import { DotsHorizontalIcon, ReplyIcon } from '@heroicons/react/solid'
|
|
import { manifoldLoveUserId } from 'common/love/constants'
|
|
import { DisplayUser } from 'common/api/user-types'
|
|
|
|
export const ChatMessageItem = memo(function ChatMessageItem(props: {
|
|
chats: ChatMessage[]
|
|
currentUser: DisplayUser | undefined | null
|
|
otherUser?: DisplayUser | null
|
|
onReplyClick?: (chat: ChatMessage) => void
|
|
beforeSameUser: boolean
|
|
firstOfUser: boolean
|
|
}) {
|
|
const {
|
|
chats,
|
|
onReplyClick,
|
|
currentUser,
|
|
otherUser,
|
|
beforeSameUser,
|
|
firstOfUser,
|
|
} = props
|
|
const chat = first(chats)
|
|
if (!chat) return null
|
|
|
|
const isMe = currentUser?.id === chat.userId
|
|
const { username, avatarUrl, id, name } =
|
|
!isMe && otherUser
|
|
? otherUser
|
|
: isMe && currentUser
|
|
? currentUser
|
|
: { username: '', avatarUrl: undefined, name: '', id: '' }
|
|
|
|
return (
|
|
<Row
|
|
className={clsx(
|
|
'@container items-end justify-start gap-1',
|
|
isMe && 'flex-row-reverse',
|
|
firstOfUser ? 'mt-2' : 'mt-1'
|
|
)}
|
|
>
|
|
{!isMe && (
|
|
<MessageAvatar
|
|
beforeSameUser={beforeSameUser}
|
|
username={username}
|
|
userAvatarUrl={avatarUrl}
|
|
userId={id}
|
|
/>
|
|
)}
|
|
<Col className="@sm:max-w-[calc(100vw-6rem)] @md:max-w-[80%] max-w-[calc(100vw-2rem)]">
|
|
{firstOfUser && !isMe && chat.visibility !== 'system_status' && (
|
|
<Row className={'items-center gap-3'}>
|
|
<Link
|
|
href={'/' + username}
|
|
className="text-ink-500 dark:text-ink-600 pl-3 text-sm"
|
|
>
|
|
{name}
|
|
</Link>
|
|
{onReplyClick && (
|
|
<DropdownMenu
|
|
items={[
|
|
{
|
|
name: 'Reply',
|
|
icon: <ReplyIcon className=" h-5 w-5 " />,
|
|
onClick: () => onReplyClick(chat),
|
|
},
|
|
]}
|
|
icon={<DotsHorizontalIcon className="text-ink-400 h-4 w-4" />}
|
|
/>
|
|
)}
|
|
</Row>
|
|
)}
|
|
<Col className="gap-1">
|
|
{chats.map((chat) => (
|
|
<div
|
|
className={clsx(
|
|
'group flex items-end gap-1',
|
|
isMe && 'flex-row-reverse'
|
|
)}
|
|
key={chat.id}
|
|
>
|
|
<div
|
|
className={clsx(
|
|
'rounded-3xl px-3 py-2',
|
|
chat.visibility !== 'system_status' && '',
|
|
chat.visibility === 'system_status'
|
|
? 'bg-canvas-50 italic'
|
|
: isMe
|
|
? 'bg-primary-100 items-end self-end rounded-r-none group-first:rounded-tr-3xl'
|
|
: 'bg-canvas-0 items-start self-start rounded-l-none group-first:rounded-tl-3xl'
|
|
)}
|
|
>
|
|
<Content size={'sm'} content={chat.content} key={chat.id} />
|
|
</div>
|
|
<RelativeTimestamp
|
|
time={chat.createdTime}
|
|
shortened
|
|
className="mb-2 mr-1 hidden text-xs group-last:block"
|
|
/>
|
|
</div>
|
|
))}
|
|
</Col>
|
|
</Col>
|
|
<div className={clsx(isMe ? 'pr-1' : '', 'pb-2')}></div>
|
|
</Row>
|
|
)
|
|
})
|
|
|
|
export const SystemChatMessageItem = memo(
|
|
function SystemChatMessageItem(props: {
|
|
chats: ChatMessage[]
|
|
otherUsers: DisplayUser[] | undefined
|
|
}) {
|
|
const { chats, otherUsers } = props
|
|
const chat = last(chats)
|
|
const [showUsers, setShowUsers] = useState(false)
|
|
if (!chat) return null
|
|
const hideAvatar =
|
|
chat.visibility === 'system_status' &&
|
|
chat.userId === manifoldLoveUserId &&
|
|
chats.length === 1
|
|
const totalUsers = otherUsers?.length || 1
|
|
return (
|
|
<Row className={clsx('flex-row-reverse items-center gap-1')}>
|
|
<Row className="grow" />
|
|
<Col className={clsx('grow-y justify-end pb-2')}>
|
|
<RelativeTimestamp
|
|
time={chat.createdTime}
|
|
shortened
|
|
className="text-xs"
|
|
/>
|
|
</Col>
|
|
<Col className="max-w-[calc(100vw-6rem)] md:max-w-[80%]">
|
|
<Col className={clsx(' bg-canvas-50 px-1 py-2 text-sm italic')}>
|
|
<span>
|
|
{totalUsers > 1 ? (
|
|
<span>
|
|
{totalUsers} user{totalUsers > 1 ? 's' : ''} joined the chat!
|
|
</span>
|
|
) : (
|
|
<Content content={chat.content} size={'sm'} />
|
|
)}
|
|
</span>
|
|
</Col>
|
|
</Col>
|
|
{!hideAvatar && (
|
|
<MultipleOrSingleAvatars
|
|
size={'xs'}
|
|
spacing={0.3}
|
|
startLeft={0.6}
|
|
avatars={otherUsers || []}
|
|
onClick={() => setShowUsers(true)}
|
|
/>
|
|
)}
|
|
{showUsers && (
|
|
<MultiUserModal
|
|
showUsers={showUsers}
|
|
setShowUsers={setShowUsers}
|
|
otherUsers={otherUsers ?? []}
|
|
/>
|
|
)}
|
|
</Row>
|
|
)
|
|
}
|
|
)
|
|
export const MultiUserModal = (props: {
|
|
showUsers: boolean
|
|
setShowUsers: (show: boolean) => void
|
|
otherUsers: DisplayUser[]
|
|
}) => {
|
|
const { showUsers, setShowUsers, otherUsers } = props
|
|
return (
|
|
<Modal open={showUsers} setOpen={setShowUsers}>
|
|
<Col className={clsx(MODAL_CLASS)}>
|
|
{otherUsers?.map((user) => (
|
|
<Row
|
|
key={user.id}
|
|
className={'w-full items-center justify-start gap-2'}
|
|
>
|
|
<UserAvatarAndBadge user={user} />
|
|
</Row>
|
|
))}
|
|
</Col>
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
function MessageAvatar(props: {
|
|
beforeSameUser: boolean
|
|
userAvatarUrl?: string
|
|
username?: string
|
|
userId: string
|
|
}) {
|
|
const { beforeSameUser, userAvatarUrl, username } = props
|
|
return (
|
|
<Col
|
|
className={clsx(
|
|
beforeSameUser ? 'pointer-events-none invisible' : '',
|
|
'grow-y justify-end pb-2 pr-1'
|
|
)}
|
|
>
|
|
<Avatar avatarUrl={userAvatarUrl} username={username} size="xs" />
|
|
</Col>
|
|
)
|
|
}
|