import React, { useEffect, useState, useRef, useCallback } from 'react'; import Message from './Message'; import MessageInput from './MessageInput'; import Spinner from './Spinner'; import Avatar from './Avatar'; import { UserLink } from './UserInfo'; import Button from './Button'; import { format, embedUrls } from '../utils/embed'; import { getMessages, comment, update, markReadTracker, fetchUserUri, updateAvatar } from '../api'; import { bubbleStyle, chatItemStyle } from './helpers/BubbleStyle'; import './Thread.css'; let isMounted; /** * @param {{ msg: import('../api').Message, draft: string, visitor: import('../api').User, active: number, setActive: function, onStartEditing: function, postComment: function }} props */ function Comment({ msg, draft, visitor, active, setActive, onStartEditing, postComment }) { const embedRef = useRef(); const msgRef = useRef(); const [author, setAuthor] = useState(msg.user); useEffect(() => { if (msgRef.current) { embedUrls(msgRef.current.querySelectorAll('a'), embedRef.current); if (!embedRef.current.hasChildNodes()) { embedRef.current.style.display = 'none'; } } }, []); useEffect(() => { isMounted = true; if (author.uri) { fetchUserUri(author.uri).then(response => { if (isMounted) { setAuthor(response.data); } }); } return () => { isMounted = false; }; }, [author.uri]); return (
{msg.replyto > 0 && ( )}
{ msg.body &&

= 0) }} />

} { msg.photo &&
}
{ active === msg.rid && Write a comment... }
{ visitor.uid > 0 ? ( <> {active === msg.rid || setActive(msg.rid)}>Reply} { visitor.uid == msg.user.uid && <>  ·  onStartEditing(msg)}>Edit } ) : ( <>  · {active === msg.rid || } ) }
); } /** * @param {{ match: import('react-router').match, location: import('history').Location, visitor: import('../api').SecureUser connection: EventSource }} props */ 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); const [editing, setEditing] = useState({}); const [hash, setHash] = useState(props.visitor.hash); const { mid } = props.match.params; let loadReplies = useCallback(() => { document.body.scrollTop = 0; document.documentElement.scrollTop = 0; setReplies([]); setLoading(true); let params = { mid: mid }; params.hash = hash; getMessages('/api/thread', params) .then(response => { if (!message.mid) { setMessage(response.data.shift()); } setReplies(response.data); setLoading(false); setActive(0); } ).catch(ex => { console.log(ex); }); }, [hash, message.mid, mid]); let onReply = useCallback((json) => { const msg = JSON.parse(json.data); if (msg.mid == message.mid) { setReplies(oldReplies => { return [...oldReplies, msg]; }); } }, [message]); let postComment = useCallback((template) => { const { mid, rid, body, attach } = template; let commentAction = editing.rid ? update(mid, editing.rid, body) : comment(mid, rid, body, attach); commentAction.then(res => { setEditing({}); loadReplies(); }) .catch(console.log); }, [editing.rid, loadReplies]); let startEditing = (reply) => { setActive(reply.replyto); setEditing(reply); }; 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]); const loaders = Math.min(message.replies || 0, 10); return ( <> { message.mid ? ( {active === (message.rid || 0) && Write a comment...} ) : ( ) } { message.replies && } ); } /** * @type React.CSSProperties */ const linkStyle = { cursor: 'pointer' };