Added the ability to reply to comments
This commit is contained in:
parent
1bb61a0bbf
commit
613ba98676
5 changed files with 151 additions and 7 deletions
|
@ -915,11 +915,12 @@
|
||||||
"Support %amount% LBC": "Support %amount% LBC",
|
"Support %amount% LBC": "Support %amount% LBC",
|
||||||
"You deposited %amount% LBC as a support!": "You deposited %amount% LBC as a support!",
|
"You deposited %amount% LBC as a support!": "You deposited %amount% LBC as a support!",
|
||||||
"LBRY Link": "LBRY Link",
|
"LBRY Link": "LBRY Link",
|
||||||
"Publish to %uri%": "Publish to %uri%",
|
"Comment Acknowledgement": "Comment Acknowledgement",
|
||||||
"Your wallet": "Your wallet",
|
"A few things to know before making your comment:": "A few things to know before making your comment:",
|
||||||
"Publish a file, or create a channel": "Publish a file, or create a channel",
|
"Commenting is in alpha. During the alpha, all comments are sent to a LBRY, Inc. server, not the LBRY network itself.": "Commenting is in alpha. During the alpha, all comments are sent to a LBRY, Inc. server, not the LBRY network itself.",
|
||||||
"Your account": "Your account",
|
"Deleting or editing comments is not currently possible. Please be mindful of this when posting.": "Deleting or editing comments is not currently possible. Please be mindful of this when posting.",
|
||||||
"Channel profile picture": "Channel profile picture",
|
"When the alpha ends, we will attempt to transition comments, but do not promise to do so.": "When the alpha ends, we will attempt to transition comments, but do not promise to do so.",
|
||||||
|
"Reply": "Reply",
|
||||||
"refreshing the app": "refreshing the app",
|
"refreshing the app": "refreshing the app",
|
||||||
"Follower": "Follower",
|
"Follower": "Follower",
|
||||||
"%repost_channel_link% reposted": "%repost_channel_link% reposted",
|
"%repost_channel_link% reposted": "%repost_channel_link% reposted",
|
||||||
|
|
|
@ -10,11 +10,15 @@ import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import { FormField, Form } from 'component/common/form';
|
import { FormField, Form } from 'component/common/form';
|
||||||
|
import CommentReply from '../commentReply/index';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
uri: string,
|
||||||
author: ?string, // LBRY Channel Name, e.g. @channel
|
author: ?string, // LBRY Channel Name, e.g. @channel
|
||||||
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
|
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
|
||||||
commentId: string, // sha256 digest identifying the comment
|
commentId: string, // sha256 digest identifying the comment
|
||||||
|
parentId: string, // sha256 digest identifying the parent of the comment
|
||||||
message: string, // comment body
|
message: string, // comment body
|
||||||
timePosted: number, // Comment timestamp
|
timePosted: number, // Comment timestamp
|
||||||
channel: ?Claim, // Channel Claim, retrieved to obtain thumbnail
|
channel: ?Claim, // Channel Claim, retrieved to obtain thumbnail
|
||||||
|
@ -33,6 +37,7 @@ const ESCAPE_KEY = 27;
|
||||||
|
|
||||||
function Comment(props: Props) {
|
function Comment(props: Props) {
|
||||||
const {
|
const {
|
||||||
|
uri,
|
||||||
author,
|
author,
|
||||||
authorUri,
|
authorUri,
|
||||||
timePosted,
|
timePosted,
|
||||||
|
@ -44,6 +49,7 @@ function Comment(props: Props) {
|
||||||
channelIsBlocked,
|
channelIsBlocked,
|
||||||
commentIsMine,
|
commentIsMine,
|
||||||
commentId,
|
commentId,
|
||||||
|
parentId,
|
||||||
updateComment,
|
updateComment,
|
||||||
deleteComment,
|
deleteComment,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -55,6 +61,9 @@ function Comment(props: Props) {
|
||||||
// used for controlling the visibility of the menu icon
|
// used for controlling the visibility of the menu icon
|
||||||
const [mouseIsHovering, setMouseHover] = useState(false);
|
const [mouseIsHovering, setMouseHover] = useState(false);
|
||||||
|
|
||||||
|
// used for controlling visibility of reply comment component
|
||||||
|
const [isReplying, setReplying] = useState(false);
|
||||||
|
|
||||||
// to debounce subsequent requests
|
// to debounce subsequent requests
|
||||||
const shouldFetch =
|
const shouldFetch =
|
||||||
channel === undefined ||
|
channel === undefined ||
|
||||||
|
@ -96,12 +105,17 @@ function Comment(props: Props) {
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
updateComment(commentId, editedMessage);
|
updateComment(commentId, editedMessage);
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
|
setReplying(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteComment() {
|
function handleDeleteComment() {
|
||||||
deleteComment(commentId);
|
deleteComment(commentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleReply() {
|
||||||
|
setReplying(true);
|
||||||
|
}
|
||||||
|
|
||||||
function handleMouseOver() {
|
function handleMouseOver() {
|
||||||
setMouseHover(true);
|
setMouseHover(true);
|
||||||
}
|
}
|
||||||
|
@ -111,7 +125,11 @@ function Comment(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="comment" onMouseOver={handleMouseOver} onMouseOut={handleMouseOut}>
|
<li
|
||||||
|
className={classnames('comment', { comment__reply: parentId !== null })}
|
||||||
|
onMouseOver={handleMouseOver}
|
||||||
|
onMouseOut={handleMouseOut}
|
||||||
|
>
|
||||||
<div className="comment__author-thumbnail">
|
<div className="comment__author-thumbnail">
|
||||||
{authorUri ? <ChannelThumbnail uri={authorUri} obscure={channelIsBlocked} small /> : <ChannelThumbnail small />}
|
{authorUri ? <ChannelThumbnail uri={authorUri} obscure={channelIsBlocked} small /> : <ChannelThumbnail small />}
|
||||||
</div>
|
</div>
|
||||||
|
@ -149,6 +167,13 @@ function Comment(props: Props) {
|
||||||
<MenuItem className="comment__menu-option" onSelect={handleDeleteComment}>
|
<MenuItem className="comment__menu-option" onSelect={handleDeleteComment}>
|
||||||
{__('Delete')}
|
{__('Delete')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{parentId === null ? (
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleReply}>
|
||||||
|
{__('Reply')}
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
)}
|
)}
|
||||||
|
@ -187,6 +212,7 @@ function Comment(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div>{isReplying ? <CommentReply uri={uri} parentId={commentId} setReplying={setReplying} /> : ''}</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
88
ui/component/commentReply/view.jsx
Normal file
88
ui/component/commentReply/view.jsx
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// @flow
|
||||||
|
import { CHANNEL_NEW } from 'constants/claim';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { FormField, Form } from 'component/common/form';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import ChannelSection from 'component/selectChannel';
|
||||||
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
|
import * as MODALS from 'constants/modal_types';
|
||||||
|
import I18nMessage from '../i18nMessage/view';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
commentingEnabled: boolean,
|
||||||
|
claim: StreamClaim,
|
||||||
|
parentId: string,
|
||||||
|
openModal: (id: string, { onCommentAcknowledge: () => void }) => void,
|
||||||
|
replyComment: (string, string, string, string) => void,
|
||||||
|
setReplying: boolean => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CommentReply(props: Props) {
|
||||||
|
const { commentingEnabled, replyComment, setReplying, claim, openModal, parentId } = props;
|
||||||
|
const { claim_id: claimId } = claim;
|
||||||
|
const [commentValue, setCommentValue] = usePersistedState(`comment-${claimId}`, '');
|
||||||
|
const [commentAck, setCommentAck] = usePersistedState('comment-acknowledge', false);
|
||||||
|
const [channel, setChannel] = usePersistedState('comment-channel', 'anonymous');
|
||||||
|
const [charCount, setCharCount] = useState(commentValue.length);
|
||||||
|
|
||||||
|
function handleCommentChange(event) {
|
||||||
|
setCommentValue(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChannelChange(channel) {
|
||||||
|
setChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCommentAck() {
|
||||||
|
setCommentAck(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTextareaFocus() {
|
||||||
|
if (!commentAck) {
|
||||||
|
openModal(MODALS.COMMENT_ACKNOWEDGEMENT, { onCommentAcknowledge: handleCommentAck });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
if (channel !== CHANNEL_NEW && commentValue.length);
|
||||||
|
replyComment(commentValue, claimId, channel, parentId);
|
||||||
|
setCommentValue('');
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => setCharCount(commentValue.length), [commentValue]);
|
||||||
|
|
||||||
|
if (!commentingEnabled) {
|
||||||
|
return (
|
||||||
|
<I18nMessage tokens={{ sign_in_link: <Button button="link" requiresAuth label={__('sign in')} /> }}>
|
||||||
|
Please %sign_in_link% to comment.
|
||||||
|
</I18nMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form className="comment__reply-form" onSubmit={handleSubmit}>
|
||||||
|
<ChannelSection channel={channel} onChannelChange={handleChannelChange} />
|
||||||
|
<FormField
|
||||||
|
disabled={channel === CHANNEL_NEW}
|
||||||
|
type="textarea"
|
||||||
|
name="content_description"
|
||||||
|
label={__('Comment')}
|
||||||
|
onFocus={onTextareaFocus}
|
||||||
|
placeholder={__('Say something about this...')}
|
||||||
|
value={commentValue}
|
||||||
|
charCount={charCount}
|
||||||
|
onChange={handleCommentChange}
|
||||||
|
/>
|
||||||
|
<div className="section__actions">
|
||||||
|
<Button
|
||||||
|
button="primary"
|
||||||
|
disabled={channel === CHANNEL_NEW || !commentValue.length}
|
||||||
|
type="submit"
|
||||||
|
label={__('Post')}
|
||||||
|
requiresAuth={IS_WEB}
|
||||||
|
/>
|
||||||
|
<Button button="link" label={__('Cancel')} onClick={() => setReplying(false)} />
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
|
@ -29,12 +29,31 @@ function CommentList(props: Props) {
|
||||||
fetchComments(uri);
|
fetchComments(uri);
|
||||||
}, [fetchComments, uri]);
|
}, [fetchComments, uri]);
|
||||||
|
|
||||||
|
function sortByParent(arrayOfComments) {
|
||||||
|
let parentComments = arrayOfComments.filter(comment => comment.parent_id === undefined);
|
||||||
|
let childComments = arrayOfComments.filter(comment => comment.parent_id !== undefined);
|
||||||
|
let sortedArray = [];
|
||||||
|
|
||||||
|
parentComments.forEach(parentComment => {
|
||||||
|
sortedArray.push(parentComment);
|
||||||
|
|
||||||
|
childComments
|
||||||
|
.filter(childComment => childComment.parent_id === parentComment.comment_id)
|
||||||
|
.forEach(childComment => {
|
||||||
|
sortedArray.push(childComment);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortedArray;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="comments">
|
<ul className="comments">
|
||||||
{comments &&
|
{comments &&
|
||||||
comments.map(comment => {
|
sortByParent(comments).map(comment => {
|
||||||
return (
|
return (
|
||||||
<Comment
|
<Comment
|
||||||
|
uri={uri}
|
||||||
authorUri={comment.channel_url}
|
authorUri={comment.channel_url}
|
||||||
author={comment.channel_name}
|
author={comment.channel_name}
|
||||||
claimId={comment.claim_id}
|
claimId={comment.claim_id}
|
||||||
|
|
|
@ -19,6 +19,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment__reply {
|
||||||
|
border-left: 5px solid var(--color-border);
|
||||||
|
padding-left: var(--spacing-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment__reply-form {
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
margin-top: var(--spacing-small);
|
||||||
|
}
|
||||||
|
|
||||||
.comment__body_container {
|
.comment__body_container {
|
||||||
padding-right: var(--spacing-small);
|
padding-right: var(--spacing-small);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
Loading…
Reference in a new issue