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",
|
||||
"You deposited %amount% LBC as a support!": "You deposited %amount% LBC as a support!",
|
||||
"LBRY Link": "LBRY Link",
|
||||
"Publish to %uri%": "Publish to %uri%",
|
||||
"Your wallet": "Your wallet",
|
||||
"Publish a file, or create a channel": "Publish a file, or create a channel",
|
||||
"Your account": "Your account",
|
||||
"Channel profile picture": "Channel profile picture",
|
||||
"Comment Acknowledgement": "Comment Acknowledgement",
|
||||
"A few things to know before making your comment:": "A few things to know before making your comment:",
|
||||
"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.",
|
||||
"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.",
|
||||
"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",
|
||||
"Follower": "Follower",
|
||||
"%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 * as ICONS from 'constants/icons';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import CommentReply from '../commentReply/index';
|
||||
import classnames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
author: ?string, // LBRY Channel Name, e.g. @channel
|
||||
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
|
||||
commentId: string, // sha256 digest identifying the comment
|
||||
parentId: string, // sha256 digest identifying the parent of the comment
|
||||
message: string, // comment body
|
||||
timePosted: number, // Comment timestamp
|
||||
channel: ?Claim, // Channel Claim, retrieved to obtain thumbnail
|
||||
|
@ -33,6 +37,7 @@ const ESCAPE_KEY = 27;
|
|||
|
||||
function Comment(props: Props) {
|
||||
const {
|
||||
uri,
|
||||
author,
|
||||
authorUri,
|
||||
timePosted,
|
||||
|
@ -44,6 +49,7 @@ function Comment(props: Props) {
|
|||
channelIsBlocked,
|
||||
commentIsMine,
|
||||
commentId,
|
||||
parentId,
|
||||
updateComment,
|
||||
deleteComment,
|
||||
} = props;
|
||||
|
@ -55,6 +61,9 @@ function Comment(props: Props) {
|
|||
// used for controlling the visibility of the menu icon
|
||||
const [mouseIsHovering, setMouseHover] = useState(false);
|
||||
|
||||
// used for controlling visibility of reply comment component
|
||||
const [isReplying, setReplying] = useState(false);
|
||||
|
||||
// to debounce subsequent requests
|
||||
const shouldFetch =
|
||||
channel === undefined ||
|
||||
|
@ -96,12 +105,17 @@ function Comment(props: Props) {
|
|||
function handleSubmit() {
|
||||
updateComment(commentId, editedMessage);
|
||||
setEditing(false);
|
||||
setReplying(false);
|
||||
}
|
||||
|
||||
function handleDeleteComment() {
|
||||
deleteComment(commentId);
|
||||
}
|
||||
|
||||
function handleReply() {
|
||||
setReplying(true);
|
||||
}
|
||||
|
||||
function handleMouseOver() {
|
||||
setMouseHover(true);
|
||||
}
|
||||
|
@ -111,7 +125,11 @@ function Comment(props: Props) {
|
|||
}
|
||||
|
||||
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">
|
||||
{authorUri ? <ChannelThumbnail uri={authorUri} obscure={channelIsBlocked} small /> : <ChannelThumbnail small />}
|
||||
</div>
|
||||
|
@ -149,6 +167,13 @@ function Comment(props: Props) {
|
|||
<MenuItem className="comment__menu-option" onSelect={handleDeleteComment}>
|
||||
{__('Delete')}
|
||||
</MenuItem>
|
||||
{parentId === null ? (
|
||||
<MenuItem className="comment__menu-option" onSelect={handleReply}>
|
||||
{__('Reply')}
|
||||
</MenuItem>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
|
@ -187,6 +212,7 @@ function Comment(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>{isReplying ? <CommentReply uri={uri} parentId={commentId} setReplying={setReplying} /> : ''}</div>
|
||||
</div>
|
||||
</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]);
|
||||
|
||||
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 (
|
||||
<ul className="comments">
|
||||
{comments &&
|
||||
comments.map(comment => {
|
||||
sortByParent(comments).map(comment => {
|
||||
return (
|
||||
<Comment
|
||||
uri={uri}
|
||||
authorUri={comment.channel_url}
|
||||
author={comment.channel_name}
|
||||
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 {
|
||||
padding-right: var(--spacing-small);
|
||||
flex: 1;
|
||||
|
|
Loading…
Reference in a new issue