aboutsummaryrefslogtreecommitdiff
path: root/vnext/src/ui/Message.js
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2022-10-28 14:23:41 +0300
committerGravatar Vitaly Takmazov2023-01-13 10:37:58 +0300
commitffb5d1beae77661b505a110e717c88aa44e1c912 (patch)
treebdb1c9a79ac6cf183d5309e40c30a1fb272321f2 /vnext/src/ui/Message.js
parent06d14f94a998ef795c52e7950e5cc8828464ae8e (diff)
Merge `Message` and `MessageInput` components from Next version
Diffstat (limited to 'vnext/src/ui/Message.js')
-rw-r--r--vnext/src/ui/Message.js160
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>&nbsp;&middot;&nbsp;</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>&nbsp;&middot;&nbsp;</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>&nbsp;and {likes - recommendations.length} others</span>)