165 lines
4.7 KiB
TypeScript
Raw Normal View History

2022-08-27 21:25:38 -05:00
import { HTMLAttributes } from 'react'
import YouTube from 'react-youtube'
2022-08-27 21:25:38 -05:00
import Box from '@mui/material/Box'
import Tooltip from '@mui/material/Tooltip'
2022-08-27 21:25:38 -05:00
import Typography, { TypographyProps } from '@mui/material/Typography'
import Link, { LinkProps } from '@mui/material/Link'
2022-08-28 18:19:14 -05:00
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
2022-09-19 11:51:49 -05:00
// These imports need to be ts-ignored to prevent spurious errors that look
// like this:
//
// Module 'react-markdown' cannot be imported using this construct. The
// specifier only resolves to an ES module, which cannot be imported
// synchronously. Use dynamic import instead. (tsserver 1471)
//
// @ts-ignore
import Markdown from 'react-markdown'
// @ts-ignore
import { CodeProps } from 'react-markdown/lib/ast-to-react'
// @ts-ignore
import remarkGfm from 'remark-gfm'
2022-08-23 21:46:07 -05:00
import {
InlineMedia as I_InlineMedia,
Message as IMessage,
isMessageReceived,
isInlineMedia,
} from 'models/chat'
2022-09-04 09:39:18 -05:00
import { PeerNameDisplay } from 'components/PeerNameDisplay'
2022-08-23 21:46:07 -05:00
import { InlineMedia } from './InlineMedia'
import './Message.sass'
2022-08-23 21:46:07 -05:00
export interface MessageProps {
message: IMessage | I_InlineMedia
showAuthor: boolean
2022-08-23 21:46:07 -05:00
userId: string
}
2022-08-27 21:25:38 -05:00
const typographyFactory =
(overrides: TypographyProps) => (args: HTMLAttributes<HTMLElement>) => {
return <Typography {...args} {...overrides} />
}
const linkFactory =
(overrides: LinkProps) => (args: HTMLAttributes<HTMLElement>) => {
return <Link {...args} {...overrides} />
}
const componentMap = {
h1: typographyFactory({ variant: 'h1' }),
h2: typographyFactory({ variant: 'h2' }),
h3: typographyFactory({ variant: 'h3' }),
h4: typographyFactory({ variant: 'h4' }),
h5: typographyFactory({ variant: 'h5' }),
h6: typographyFactory({ variant: 'h6' }),
p: typographyFactory({ variant: 'body1' }),
a: linkFactory({
variant: 'body1',
underline: 'always',
color: 'primary.contrastText',
2022-08-27 21:25:38 -05:00
}),
2022-08-28 18:19:14 -05:00
// https://github.com/remarkjs/react-markdown#use-custom-components-syntax-highlight
code({ node, inline, className, children, style, ...props }: CodeProps) {
const match = /language-(\w+)/.exec(className || '')
return !inline && match ? (
<SyntaxHighlighter
children={String(children).replace(/\n$/, '')}
language={match[1]}
style={materialDark}
PreTag="div"
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
)
},
2022-08-27 21:25:38 -05:00
}
const spaceNeededForSideDateTooltip = 850
const getYouTubeVideoId = (videoUrl: string) => {
const trimmedMessage = videoUrl.trim()
const matchArray =
trimmedMessage.match(/https:\/\/www.youtube.com\/watch\?v=(\S{8,})$/) ||
trimmedMessage.match(/https:\/\/youtu.be\/(\S{8,})$/)
return matchArray?.pop()
}
const isYouTubeLink = (message: IMessage) => {
return typeof getYouTubeVideoId(message.text) === 'string'
}
export const Message = ({ message, showAuthor, userId }: MessageProps) => {
2022-08-23 21:46:07 -05:00
let backgroundColor: string
if (message.authorId === userId) {
backgroundColor = isMessageReceived(message)
2022-09-04 11:04:54 -05:00
? 'primary.main'
2022-09-02 09:49:00 -05:00
: 'primary.light'
2022-08-23 21:46:07 -05:00
} else {
2022-09-04 11:04:54 -05:00
backgroundColor = 'secondary.main'
2022-08-23 21:46:07 -05:00
}
return (
2022-08-27 21:38:08 -05:00
<Box className="Message">
{showAuthor && (
<Typography
variant="caption"
display="block"
sx={{
textAlign: message.authorId === userId ? 'right' : 'left',
}}
>
<PeerNameDisplay>{message.authorId}</PeerNameDisplay>
</Typography>
)}
<Tooltip
placement={
window.innerWidth >= spaceNeededForSideDateTooltip ? 'left' : 'top'
}
title={String(
Intl.DateTimeFormat(undefined, {
dateStyle: 'short',
timeStyle: 'short',
}).format(message.timeSent)
)}
2022-08-23 21:46:07 -05:00
>
<Box
sx={{
color: 'primary.contrastText',
backgroundColor,
margin: 0.5,
padding: '0.5em 0.75em',
borderRadius: 6,
float: message.authorId === userId ? 'right' : 'left',
transition: 'background-color 1s',
wordBreak: 'break-word',
}}
maxWidth="85%"
>
{isInlineMedia(message) ? (
<InlineMedia magnetURI={message.magnetURI} />
) : isYouTubeLink(message) ? (
<YouTube videoId={getYouTubeVideoId(message.text)} />
) : (
<Markdown
components={componentMap}
remarkPlugins={[remarkGfm]}
linkTarget="_blank"
>
{message.text}
</Markdown>
)}
</Box>
</Tooltip>
2022-08-27 21:38:08 -05:00
</Box>
2022-08-23 21:46:07 -05:00
)
}