aboutsummaryrefslogtreecommitdiff
path: root/vnext/src/ui/Message.js
diff options
context:
space:
mode:
Diffstat (limited to 'vnext/src/ui/Message.js')
-rw-r--r--vnext/src/ui/Message.js142
1 files changed, 142 insertions, 0 deletions
diff --git a/vnext/src/ui/Message.js b/vnext/src/ui/Message.js
new file mode 100644
index 00000000..eb008bfe
--- /dev/null
+++ b/vnext/src/ui/Message.js
@@ -0,0 +1,142 @@
+import React, { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router-dom';
+import moment from 'moment';
+
+import { UserType, MessageType } from './Types';
+import Icon from './Icon';
+import Avatar from './Avatar';
+import { UserLink } from './UserInfo';
+
+import { format, embedUrls } from '../utils/embed';
+
+import './Message.css';
+
+export default function Message({ data, visitor, children, ...rest }) {
+ const isCode = (data.tags || []).indexOf('code') >= 0;
+ const likesSummary = data.likes ? `${data.likes}` : 'Recommend';
+ const commentsSummary = data.replies ? `${data.replies}` : 'Comment';
+ const embedRef = useRef();
+ const msgRef = useRef();
+ useEffect(() => {
+ if (msgRef.current) {
+ embedUrls(msgRef.current.querySelectorAll('a'), embedRef.current);
+ if (!embedRef.current.hasChildNodes()) {
+ embedRef.current.style.display = 'none';
+ }
+ }
+ }, []);
+ 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>
+ </div>
+ </Avatar>
+ <TagsList user={data.user} data={data.tags || []} />
+ </header>
+ {
+ data.body &&
+ <div className="msg-txt" ref={msgRef}>
+ <MessageContainer isCode={isCode} data={{ __html: format(data.body, data.mid, isCode) }} />
+ </div>
+ }
+ {
+ data.photo &&
+ <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>
+ </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">
+ <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">
+ <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>
+ )}
+ {!data.ReadOnly | (visitor.uid === data.user.uid) && (
+ <>
+ <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>
+ </>
+ )}
+ </nav>
+ {children}
+ </div>
+ );
+}
+
+function MessageContainer({ isCode, data }) {
+ return isCode ? (<pre dangerouslySetInnerHTML={data} />) : (<p dangerouslySetInnerHTML={data} />);
+}
+
+function Tags({ data, user, ...rest }) {
+ return data.length > 0 && (
+ <div className="msg-tags">
+ {
+ data.map(tag => {
+ return (<Link key={tag} to={{ pathname: `/${user.uname}`, search: `?tag=${tag}` }} title={tag}>{tag}</Link>);
+ }).reduce((prev, curr) => [prev, ', ', curr])
+ }
+ </div>
+ );
+}
+
+const TagsList = React.memo(Tags);
+
+function Recommends({ forMessage, ...rest }) {
+ const { likes, recommendations } = forMessage;
+ 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])
+ }
+ {
+ likes > recommendations.length && (<span>&nbsp;and {likes - recommendations.length} others</span>)
+ }
+ </div>
+ ) || null;
+}
+
+const Recommendations = React.memo(Recommends);
+
+Message.propTypes = {
+ data: MessageType,
+ visitor: UserType.isRequired,
+ children: PropTypes.node
+};
+
+MessageContainer.propTypes = {
+ isCode: PropTypes.bool.isRequired,
+ data: PropTypes.object.isRequired
+};
+
+Tags.propTypes = {
+ user: UserType.isRequired,
+ data: PropTypes.array
+};
+