import { useEffect, useState, useCallback } from 'react' import { useLocation, useParams } from 'react-router-dom' import Comment from './Comment' import Message from './Message' import MessageInput from './MessageInput' import Spinner from './Spinner' import { getMessages, comment, update, post } from '../api' import { useVisitor } from './VisitorContext' import { Helmet } from 'react-helmet' /** * @type { import('../api').Message } */ const emptyMessage = {} /** * Thread component * @param {import('react').PropsWithChildren<{}>} props */ export default function Thread(props) { const location = useLocation() const params = useParams() const [message, setMessage] = useState((location.state || {}).data || {}) const [replies, setReplies] = useState([]) const [loading, setLoading] = useState(false) const [active, setActive] = useState(0) const [editing, setEditing] = useState(emptyMessage) const [visitor] = useVisitor() const [hash] = useState(visitor.hash) const { mid } = 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 => { let updatedMessage = response.data.shift() if (!message.mid) { setMessage(updatedMessage) } setReplies(response.data) setLoading(false) setActive(0) } ).catch(ex => { console.log(ex) }) }, [hash, message.mid, mid]) let postComment = async ({ body, attach }) => { try { let res = editing.rid ? await update(mid, editing.rid, body) : await comment(mid, active, body, attach) let result = res.status == 200 if (result) { setEditing(emptyMessage) } return result } catch (e) { console.error(e) } return false } let startEditing = (reply) => { setActive(reply.to.rid || 0) setEditing(reply) } useEffect(() => { setActive(0) loadReplies() }, [loadReplies]) useEffect(() => { let onReply = (json) => { const msg = JSON.parse(json.data) if (msg.mid == message.mid) { setReplies(oldReplies => { return [...oldReplies, msg] }) setActive(prev => { return prev + 1 }) } } 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]) const loaders = Math.min(message.replies || 0, 10) const pageTitle = `${params.user} ${message && message.tags || 'thread'}` /** @type { import('./Message').ToggleSubscriptionCallback } */ const handleSubsciptionToggle = (message) => { if (message.subscribed) { if (confirm('Unsubscribe?')) { post(`U #${message.mid}`).then((response) => { if (response.status === 200) { setMessage({...message, subscribed: false}) } }).catch(console.error) } } else { if (confirm('Subscribe?')) { post(`S #${message.mid}`).then((response) => { if (response.status === 200) { setMessage({...message, subscribed: true}) } }).catch(console.error) } } } return ( <> {pageTitle} { message.mid ? ( {active === (message.rid || 0) && visitor.uid > 0 && Write a comment...} ) : ( ) } { message.replies && } ) }