diff options
-rw-r--r-- | vnext/src/ui/Comment.js | 124 | ||||
-rw-r--r-- | vnext/src/ui/Thread.js | 127 |
2 files changed, 127 insertions, 124 deletions
diff --git a/vnext/src/ui/Comment.js b/vnext/src/ui/Comment.js new file mode 100644 index 00000000..756b3487 --- /dev/null +++ b/vnext/src/ui/Comment.js @@ -0,0 +1,124 @@ +import { useEffect, useRef, useState } from 'react'; + +import Avatar from './Avatar'; +import { UserLink } from './UserInfo'; +import Button from './Button'; +import defaultAvatar from '../assets/av-96.png'; + +import MessageInput from './MessageInput'; +import { fetchUserUri } from '../api'; +import { chatItemStyle } from './helpers/BubbleStyle'; +import { format, embedUrls } from '../utils/embed'; + +let isMounted; + +/** + * @param {{ + msg: import('../api').Message, + draft: string, + visitor: import('../api').User, + active: number, + setActive: function, + onStartEditing: function, + postComment: function + }} props + */ +export default 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'; + } + } + }, []); + const userRef = useRef(author); + useEffect(() => { + isMounted = true; + if (userRef.current.uri) { + fetchUserUri(userRef.current.uri).then(remote_user => { + if (isMounted) { + setAuthor({ + uid: 0, + uname: remote_user.preferredUsername, + avatar: remote_user.icon && remote_user.icon.url, + uri: author.uri + }); + } + }).catch(e => { + setAuthor({ + uid: 0, + uname: userRef.current.uri, + uri: author.uri, + avatar: defaultAvatar + }); + }); + } + return () => { + isMounted = false; + }; + }, [author.uri, msg.user]); + return ( + <div style={chatItemStyle(visitor, msg)}> + <div className="msg-header"> + <Avatar user={author} link={author.uri}> + <div className="msg-ts"> + {msg.replyto > 0 && + ( + <UserLink user={msg.to} /> + )} + </div> + </Avatar> + </div> + { + msg.body && + <div className={visitor.uid === msg.user.uid ? 'msg-bubble msg-bubble-my' : 'msg-bubble'}> + <div ref={msgRef}> + <p dangerouslySetInnerHTML={{ __html: format(msg.body, msg.mid.toString(), (msg.tags || []).indexOf('code') >= 0) }} /> + </div> + </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/photos-512/${msg.mid}-${msg.rid}.${msg.attach}`} alt="" /> + </a> + </div> + } + <div className="embedContainer" ref={embedRef} /> + { + active === msg.rid && <MessageInput data={msg} text={draft || ''} onSend={postComment}>Write a comment...</MessageInput> + } + <div className="msg-links"> + { + visitor.uid > 0 ? ( + <> + {active === msg.rid || <span style={linkStyle} onClick={() => setActive(msg.rid)}>Reply</span>} + { + visitor.uid == msg.user.uid && + <> + <span> · </span> + <span style={linkStyle} onClick={() => onStartEditing(msg)}>Edit</span> + </> + } + </> + ) : ( + <> + <span> · </span>{active === msg.rid || <Button>Reply</Button>} + </> + ) + } + </div> + </div> + ); +} +/** + * @type React.CSSProperties + */ + const linkStyle = { + cursor: 'pointer' +}; diff --git a/vnext/src/ui/Thread.js b/vnext/src/ui/Thread.js index 15c169fc..52a2baf1 100644 --- a/vnext/src/ui/Thread.js +++ b/vnext/src/ui/Thread.js @@ -1,23 +1,14 @@ -import { useEffect, useState, useRef, useCallback } from 'react'; +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 Avatar from './Avatar'; -import { UserLink } from './UserInfo'; -import Button from './Button'; -import defaultAvatar from '../assets/av-96.png'; -import { format, embedUrls } from '../utils/embed'; - -import { getMessages, comment, update, fetchUserUri } from '../api'; - -import { chatItemStyle } from './helpers/BubbleStyle'; +import { getMessages, comment, update } from '../api'; import './Thread.css'; - -let isMounted; /** * @type import('../api').Message */ @@ -25,111 +16,6 @@ const emptyMessage = {}; /** * @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'; - } - } - }, []); - const userRef = useRef(author); - useEffect(() => { - isMounted = true; - if (userRef.current.uri) { - fetchUserUri(userRef.current.uri).then(remote_user => { - if (isMounted) { - setAuthor({ - uid: 0, - uname: remote_user.preferredUsername, - avatar: remote_user.icon && remote_user.icon.url, - uri: author.uri - }); - } - }).catch(e => { - setAuthor({ - uid: 0, - uname: userRef.current.uri, - uri: author.uri, - avatar: defaultAvatar - }); - }); - } - return () => { - isMounted = false; - }; - }, [author.uri, msg.user]); - return ( - <div style={chatItemStyle(visitor, msg)}> - <div className="msg-header"> - <Avatar user={author} link={author.uri}> - <div className="msg-ts"> - {msg.replyto > 0 && - ( - <UserLink user={msg.to} /> - )} - </div> - </Avatar> - </div> - { - msg.body && - <div className={visitor.uid === msg.user.uid ? 'msg-bubble msg-bubble-my' : 'msg-bubble'}> - <div ref={msgRef}> - <p dangerouslySetInnerHTML={{ __html: format(msg.body, msg.mid.toString(), (msg.tags || []).indexOf('code') >= 0) }} /> - </div> - </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/photos-512/${msg.mid}-${msg.rid}.${msg.attach}`} alt="" /> - </a> - </div> - } - <div className="embedContainer" ref={embedRef} /> - { - active === msg.rid && <MessageInput data={msg} text={draft || ''} onSend={postComment}>Write a comment...</MessageInput> - } - <div className="msg-links"> - { - visitor.uid > 0 ? ( - <> - {active === msg.rid || <span style={linkStyle} onClick={() => setActive(msg.rid)}>Reply</span>} - { - visitor.uid == msg.user.uid && - <> - <span> · </span> - <span style={linkStyle} onClick={() => onStartEditing(msg)}>Edit</span> - </> - } - </> - ) : ( - <> - <span> · </span>{active === msg.rid || <Button>Reply</Button>} - </> - ) - } - </div> - </div> - ); -} - -/** - * @param {{ visitor: import('../api').SecureUser connection: EventSource? }} props @@ -241,10 +127,3 @@ export default function Thread(props) { </> ); } - -/** - * @type React.CSSProperties - */ -const linkStyle = { - cursor: 'pointer' -}; |