diff options
author | Vitaly Takmazov | 2019-05-04 21:13:12 +0300 |
---|---|---|
committer | Vitaly Takmazov | 2023-01-13 10:37:54 +0300 |
commit | f470636a70943a8ecad8bddc791a1c2dddd28e1e (patch) | |
tree | c43d109d88adbde9a696084070cdd92c6b9a004b /vnext/src/ui/Thread.js | |
parent | 3d7d213e3ddc5bf4f71d536f31677b768aa3b7c0 (diff) |
Components -> UI
Diffstat (limited to 'vnext/src/ui/Thread.js')
-rw-r--r-- | vnext/src/ui/Thread.js | 181 |
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} </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> · </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 +}; |