aboutsummaryrefslogtreecommitdiff
path: root/vnext/src/ui/Thread.js
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2019-05-04 21:13:12 +0300
committerGravatar Vitaly Takmazov2023-01-13 10:37:54 +0300
commitf470636a70943a8ecad8bddc791a1c2dddd28e1e (patch)
treec43d109d88adbde9a696084070cdd92c6b9a004b /vnext/src/ui/Thread.js
parent3d7d213e3ddc5bf4f71d536f31677b768aa3b7c0 (diff)
Components -> UI
Diffstat (limited to 'vnext/src/ui/Thread.js')
-rw-r--r--vnext/src/ui/Thread.js181
1 files changed, 181 insertions, 0 deletions
diff --git a/vnext/src/ui/Thread.js b/vnext/src/ui/Thread.js
new file mode 100644
index 00000000..e7ccb032
--- /dev/null
+++ b/vnext/src/ui/Thread.js
@@ -0,0 +1,181 @@
+import React, { useEffect, useState, useRef, useCallback } from 'react';
+import PropTypes from 'prop-types';
+
+import ReactRouterPropTypes from 'react-router-prop-types';
+import { UserType, MessageType } from './Types';
+
+import Message from './Message';
+import MessageInput from './MessageInput';
+import Spinner from './Spinner';
+import Avatar from './Avatar';
+import Button from './Button';
+
+import { format, embedUrls } from '../utils/embed';
+
+import { getMessages, comment, markReadTracker } from '../api';
+
+function Comment({ msg, visitor, active, setActive, postComment }) {
+ 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">
+ <div className="msg-header">
+ <Avatar user={msg.user}>
+ <div className="msg-ts">
+ {msg.replyto > 0 &&
+ (
+ <a href={`#${msg.replyto}`} className="info-avatar"><img src={msg.to.avatar} /> {msg.to.uname}&nbsp;</a>
+ )}
+ </div>
+ </Avatar>
+ </div>
+ {
+ msg.html ? <div className="msg-txt" dangerouslySetInnerHTML={{ __html: msg.body }} ref={msgRef} />
+ :
+ <div className="msg-txt" ref={msgRef}>
+ <p dangerouslySetInnerHTML={{ __html: format(msg.body, msg.mid, (msg.tags || []).indexOf('code') >= 0) }} />
+ </div>
+ }
+ {
+ msg.photo &&
+ <div className="msg-media">
+ <a href={`//i.juick.com/p/${msg.mid}-${msg.rid}.${msg.attach}`} data-fname={`${msg.mid}-${msg.rid}.${msg.attach}`}>
+ <img src={`//i.juick.com/p/${msg.mid}-${msg.rid}.${msg.attach}`} alt="" />
+ </a>
+ </div>
+ }
+ <div className="embedContainer" ref={embedRef} />
+ <div className="msg-links">
+ {
+ visitor.uid > 0 ? (
+ <>
+ {active === msg.rid || <span style={linkStyle} onClick={() => setActive(msg.rid)}>Reply</span>}
+ {active === msg.rid && <MessageInput data={msg} onSend={postComment}>Write a comment...</MessageInput>}
+ </>
+ ) : (
+ <>
+ <span>&nbsp;&middot;&nbsp;</span>{active === msg.rid || <Button>Reply</Button>}
+ </>
+ )
+ }
+ </div>
+ </div>
+ );
+}
+
+Comment.propTypes = {
+ msg: MessageType.isRequired,
+ visitor: UserType.isRequired,
+ active: PropTypes.number.isRequired,
+ setActive: PropTypes.func.isRequired,
+ postComment: PropTypes.func.isRequired
+};
+
+export default function Thread(props) {
+ const [message, setMessage] = useState((props.location.state || {}).msg || {});
+ const [replies, setReplies] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [active, setActive] = useState(0);
+ useEffect(() => {
+ setActive(0);
+ loadReplies();
+ }, [loadReplies]);
+ useEffect(() => {
+ if (props.connection.addEventListener && message.mid) {
+ props.connection.addEventListener('msg', onReply);
+ }
+ return () => {
+ if (props.connection.removeEventListener && message.mid) {
+ props.connection.removeEventListener('msg', onReply);
+ }
+ };
+ }, [props.connection, message.mid, onReply]);
+
+ let loadReplies = useCallback(() => {
+ document.body.scrollTop = 0;
+ document.documentElement.scrollTop = 0;
+
+ setReplies([]);
+ setLoading(true);
+ const { mid } = props.match.params;
+ let params = {
+ mid: mid
+ };
+ if (props.visitor && props.visitor.hash) {
+ params.hash = props.visitor.hash;
+ }
+ getMessages('/api/thread', params)
+ .then(response => {
+ setMessage(response.data.shift());
+ setReplies(response.data);
+ setLoading(false);
+ setActive(0);
+ }
+ ).catch(ex => {
+ console.log(ex);
+ });
+ }, [props.visitor, props.match.params]);
+ let onReply = useCallback((json) => {
+ const msg = JSON.parse(json.data);
+ if (msg.mid == message.mid) {
+ setReplies(oldReplies => {
+ return [...oldReplies, msg];
+ });
+ }
+ }, [message]);
+ let postComment = (template) => {
+ const { mid, rid, body, attach } = template;
+ comment(mid, rid, body, attach).then(res => {
+ loadReplies();
+ })
+ .catch(console.log);
+ };
+
+ const loaders = Math.min(message.replies || 0, 10);
+ return (
+ <>
+ {
+ message.mid ? (
+ <Message data={message} visitor={props.visitor}>
+ {active === (message.rid || 0) && <MessageInput data={message} onSend={postComment}>Write a comment...</MessageInput>}
+ </Message>
+ ) : (
+ <Spinner />
+ )
+ }
+ <ul id="replies">
+ {
+ !loading ? replies.map((msg) => (
+ <li id={msg.rid} key={msg.rid} className="msg">
+ <Comment msg={msg} visitor={props.visitor} active={active} setActive={setActive} postComment={postComment} />
+ </li>
+ )) : (
+ <>
+ {Array(loaders).fill().map((it, i) => <Spinner key={i} />)}
+ </>
+ )
+ }
+ </ul>
+ </>
+ );
+}
+
+const linkStyle = {
+ cursor: 'pointer'
+};
+
+Thread.propTypes = {
+ location: ReactRouterPropTypes.location,
+ history: ReactRouterPropTypes.history,
+ match: ReactRouterPropTypes.match,
+ visitor: UserType.isRequired,
+ connection: PropTypes.object.isRequired
+};