diff options
Diffstat (limited to 'vnext/src/ui/Message.js')
-rw-r--r-- | vnext/src/ui/Message.js | 160 |
1 files changed, 93 insertions, 67 deletions
diff --git a/vnext/src/ui/Message.js b/vnext/src/ui/Message.js index 620fe017..f8d3dc51 100644 --- a/vnext/src/ui/Message.js +++ b/vnext/src/ui/Message.js @@ -1,104 +1,117 @@ -import { memo, useEffect, useRef } from 'react'; +import React, { Fragment, memo, useEffect, useRef } from 'react'; -import { Link } from 'react-router-dom'; import moment from 'moment'; +import { Link } from 'react-router-dom'; import Icon from './Icon'; import Avatar from './Avatar'; import { UserLink } from './UserInfo'; import { format, embedUrls } from '../utils/embed'; -import './Message.css'; +/** + * @typedef {object} MessageProps + * @property { import('../client').Message } data data + * @property { import('../client').SecureUser } visitor visitor + */ /** * Message component - * @param {{data: import('../api').Message, visitor: import('../api').User, children: React.ReactElement}} props + * + * @param {React.PropsWithChildren<{}> & MessageProps} props props */ -export default function Message({ data, visitor, children }) { +export default function Message({ visitor, data, children }) { const isCode = (data.tags || []).indexOf('code') >= 0; const likesSummary = data.likes ? `${data.likes}` : 'Recommend'; const commentsSummary = data.replies ? `${data.replies}` : 'Comment'; /** - * @type {React.RefObject<HTMLDivElement>} + * @type {React.MutableRefObject<HTMLDivElement?>} */ - const embedRef = useRef(); + const embedRef = useRef(null); /** - * @type {React.RefObject<HTMLDivElement>} + * @type {React.MutableRefObject<HTMLDivElement?>} */ - const msgRef = useRef(); + const msgRef = useRef(null); useEffect(() => { - if (msgRef.current) { - embedUrls(msgRef.current.querySelectorAll('a'), embedRef.current); - if (!embedRef.current.hasChildNodes()) { - embedRef.current.style.display = 'none'; + const msg = msgRef.current; + const embed = embedRef.current; + if (msg && embed) { + embedUrls(msg.querySelectorAll('a'), embed); + if (!embed.hasChildNodes()) { + embed.style.display = 'none'; } } }, []); - const canComment = visitor.uid === data.user.uid || !data.ReadOnly; + const canComment = data.user && visitor.uid === data.user.uid || !data.ReadOnly; return ( <div className="msg-cont"> <Recommendations forMessage={data} /> <header className="h"> - <Avatar user={data.user}> - <div className="msg-ts"> - <Link to={{ pathname: `/${data.user.uname}/${data.mid}`, state: { msg: data } }}> - <time dateTime={data.timestamp} - title={moment.utc(data.timestamp).local().format('lll')}> - {moment.utc(data.timestamp).fromNow()} - </time> - </Link> - { - visitor.uid == data.user.uid && - <> - <span> · </span> - <Link to={{ pathname: '/post', state: { draft: data }}}>Edit</Link> - </> - } - </div> - - </Avatar> - <TagsList user={data.user} data={data.tags || []} /> + { + data.user && + <Avatar user={data.user}> + <div className="msg-ts"> + <Link to={`/${data.user.uname}/${data.mid}`}> + <time dateTime={data.timestamp} + title={moment.utc(data.timestamp).local().format('lll')}> + {moment.utc(data.timestamp).fromNow()} + </time> + </Link> + { + visitor.uid == data.user.uid && + <> + <span> · </span> + <Link to={{ + pathname: '/post', + query: { + mid: data.mid, + body: data.body + } + }}>Edit</Link> + </> + } + </div> + </Avatar> + } </header> { - data.body && + data.body && data.user && data.mid && <div className="msg-txt" ref={msgRef}> + <TagsList user={data.user} data={data.tags || []} /> <MessageContainer isCode={isCode} data={{ __html: format(data.body, data.mid.toString(), isCode) }} /> </div> } { - data.photo && + data.photo && data.attachment && data.attachment.small && <div className="msg-media"> - <a href={`//i.juick.com/p/${data.mid}.${data.attach}`} data-fname={`${data.mid}.${data.attach}`}> - <img src={`//i.juick.com/photos-512/${data.mid}.${data.attach}`} alt="" /> + <a href={`https://i.juick.com/p/${data.mid}.${data.attach}`} data-fname={`${data.mid}.${data.attach}`}> + <img src={data.attachment.small.url} width={data.attachment.small.width} height={data.attachment.small.height} alt="Message media" /> </a> </div> } <div className="embedContainer" ref={embedRef} /> <nav className="l"> - {visitor.uid === data.user.uid ? ( - <Link to={{ pathname: `/${data.user.uname}/${data.mid}` }} className="a-like msg-button"> + {data.user && visitor.uid === data.user.uid ? ( + <Link to={`/${data.user.uname}/${data.mid}`} className="a-like msg-button"> <Icon name="ei-heart" size="s" /> <span>{likesSummary}</span> </Link> ) : visitor.uid > 0 ? ( - <Link to={{ pathname: '/post', search: `?body=!+%23${data.mid}` }} className="a-like msg-button"> + <Link to={'/post'} className="a-like msg-button"> <Icon name="ei-heart" size="s" /> <span>{likesSummary}</span> </Link> ) : ( - <a href="/login" className="a-login msg-button"> - <Icon name="ei-heart" size="s" /> - <span>{likesSummary}</span> - </a> - )} - {canComment && ( - <> - <Link to={{ pathname: `/${data.user.uname}/${data.mid}`, state: { msg: data } }} className="a-comment msg-button"> - <Icon name="ei-comment" size="s" /> - <span>{commentsSummary}</span> - </Link> - </> + <Link to="/login" className="a-login msg-button"> + <Icon name="ei-heart" size="s" /> + <span>{likesSummary}</span> + </Link> + )} + {data.user && canComment && ( + <Link to={`/${data.user.uname}/${data.mid}`} className="a-comment msg-button"> + <Icon name="ei-comment" size="s" /> + <span>{commentsSummary}</span> + </Link> )} </nav> {children} @@ -107,39 +120,52 @@ export default function Message({ data, visitor, children }) { } /** - * @param {{isCode: boolean, data: {__html: string}}} props + * @param {{isCode: boolean, data: {__html: string}}} props props */ function MessageContainer({ isCode, data }) { - return isCode ? (<pre dangerouslySetInnerHTML={data} />) : (<p dangerouslySetInnerHTML={data} />); + return isCode ? (<pre dangerouslySetInnerHTML={data} />) : (<span dangerouslySetInnerHTML={data} />); } /** * Tags component - * @param {{user: import('../api').User, data: string[]}} props + * + * @param {{user: import('../client').User, data: string[]}} props props */ function Tags({ data, user }) { - return data.length > 0 && ( - <div className="msg-tags"> + return data.length > 0 ? ( + <span className="msg-tags"> { - data.map(tag => { - return (<Link key={tag} to={{ pathname: `/${user.uname}`, search: `?tag=${tag}` }} title={tag}>{tag}</Link>); - // @ts-ignore - }).reduce((prev, curr) => [prev, ', ', curr]) + data.map((tag, index) => ( + <Fragment key={tag}> + {index > 0 && ' '} + <Link key={tag} to={`/${user.uname}?tag=${tag}`} title={tag}> + {tag} + </Link> + </Fragment> + )) } - </div> - ); + </span> + ) : null; } const TagsList = memo(Tags); +/** + * + * @param {{forMessage: import('../client').Message}} props props + */ function Recommends({ forMessage }) { - const { likes, recommendations } = forMessage; + const { recommendations } = forMessage; + const likes = forMessage.likes || 0; return recommendations && recommendations.length > 0 && ( <div className="msg-recomms">{'♡ by '} { - recommendations.map(it => ( - <UserLink key={it.uri || it.uid} user={it} /> - )).reduce((prev, curr) => [prev, ', ', curr]) + recommendations.map((it, index) => ( + <Fragment key={it.uri || it.uid}> + {index > 0 && ', '} + <UserLink key={it.uri || it.uid} user={it} /> + </Fragment> + )) } { likes > recommendations.length && (<span> and {likes - recommendations.length} others</span>) |