// @flow import * as ICONS from 'constants/icons'; import * as PAGES from 'constants/pages'; import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field'; import { SITE_NAME, SIMPLE_SITE, ENABLE_COMMENT_REACTIONS } from 'config'; import React, { useEffect, useState } from 'react'; import { isEmpty } from 'util/object'; import DateTime from 'component/dateTime'; import Button from 'component/button'; import Expandable from 'component/expandable'; import MarkdownPreview from 'component/common/markdown-preview'; import ChannelThumbnail from 'component/channelThumbnail'; import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button'; import Icon from 'component/common/icon'; import { FormField, Form } from 'component/common/form'; import classnames from 'classnames'; import usePersistedState from 'effects/use-persisted-state'; import CommentReactions from 'component/commentReactions'; import CommentsReplies from 'component/commentsReplies'; import { useHistory } from 'react-router'; import CommentCreate from 'component/commentCreate'; 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 message: string, // comment body timePosted: number, // Comment timestamp channel: ?Claim, // Channel Claim, retrieved to obtain thumbnail pending?: boolean, resolveUri: string => void, // resolves the URI isResolvingUri: boolean, // if the URI is currently being resolved channelIsBlocked: boolean, // if the channel is blacklisted in the app claimIsMine: boolean, // if you control the claim which this comment was posted on commentIsMine: boolean, // if this comment was signed by an owned channel updateComment: (string, string) => void, deleteComment: string => void, blockChannel: string => void, linkedComment?: any, myChannels: ?Array, commentingEnabled: boolean, doToast: ({ message: string }) => void, isTopLevel?: boolean, threadDepth: number, }; const LENGTH_TO_COLLAPSE = 300; const ESCAPE_KEY = 27; function Comment(props: Props) { const { uri, author, authorUri, timePosted, message, pending, channel, isResolvingUri, resolveUri, channelIsBlocked, commentIsMine, commentId, updateComment, deleteComment, blockChannel, linkedComment, commentingEnabled, myChannels, doToast, isTopLevel, threadDepth, } = props; const { push, location: { pathname }, } = useHistory(); const [isReplying, setReplying] = React.useState(false); const [isEditing, setEditing] = useState(false); const [editedMessage, setCommentValue] = useState(message); const [charCount, setCharCount] = useState(editedMessage.length); // used for controlling the visibility of the menu icon const [mouseIsHovering, setMouseHover] = useState(false); const [advancedEditor] = usePersistedState('comment-editor-mode', false); const hasChannels = myChannels && myChannels.length > 0; // to debounce subsequent requests const shouldFetch = channel === undefined || (channel !== null && channel.value_type === 'channel' && isEmpty(channel.meta) && !pending); useEffect(() => { // If author was extracted from the URI, then it must be valid. if (authorUri && author && !isResolvingUri && shouldFetch) { resolveUri(authorUri); } if (isEditing) { setCharCount(editedMessage.length); // a user will try and press the escape key to cancel editing their comment const handleEscape = event => { if (event.keyCode === ESCAPE_KEY) { setEditing(false); } }; window.addEventListener('keydown', handleEscape); // removes the listener so it doesn't cause problems elsewhere in the app return () => { window.removeEventListener('keydown', handleEscape); }; } }, [isResolvingUri, shouldFetch, author, authorUri, resolveUri, editedMessage, isEditing, setEditing]); function handleEditMessageChanged(event) { setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value); } function handleSubmit() { updateComment(commentId, editedMessage); setEditing(false); } function handleCommentReply() { if (!hasChannels) { push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`); doToast({ message: __('A channel is required to comment on %SITE_NAME%', { SITE_NAME }) }); } else { setReplying(!isReplying); } } return (
  • setMouseHover(true)} onMouseOut={() => setMouseHover(false)} >
    {authorUri ? ( ) : ( )}
    {!author ? ( {__('Anonymous')} ) : (
    {commentIsMine ? ( <> setEditing(true)}> {__('Edit')} deleteComment(commentId)}> {__('Delete')} ) : ( blockChannel(authorUri)}> {__('Block Channel')} )}
    {isEditing ? (
    ) : ( <>
    {editedMessage.length >= LENGTH_TO_COLLAPSE ? ( ) : ( )}
    {threadDepth !== 0 && (
    {isReplying && ( setReplying(false)} onCancelReplying={() => setReplying(false)} /> )} )}
  • ); } export default Comment;