2019-06-27 01:59:27 +02:00
|
|
|
// @flow
|
2020-07-24 16:13:42 +02:00
|
|
|
import * as ICONS from 'constants/icons';
|
|
|
|
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
|
|
|
|
import { SIMPLE_SITE } from 'config';
|
2020-01-30 02:02:21 +01:00
|
|
|
import React, { useEffect, useState } from 'react';
|
2019-10-23 09:04:40 +02:00
|
|
|
import { isEmpty } from 'util/object';
|
2020-05-11 09:57:03 +02:00
|
|
|
import DateTime from 'component/dateTime';
|
2019-07-21 22:46:30 +02:00
|
|
|
import Button from 'component/button';
|
2020-08-24 19:35:21 +02:00
|
|
|
// import Expandable from 'component/expandable';
|
2019-10-13 06:04:16 +02:00
|
|
|
import MarkdownPreview from 'component/common/markdown-preview';
|
2019-10-23 09:04:40 +02:00
|
|
|
import ChannelThumbnail from 'component/channelThumbnail';
|
2020-01-30 02:02:21 +01:00
|
|
|
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
|
|
|
import Icon from 'component/common/icon';
|
|
|
|
import { FormField, Form } from 'component/common/form';
|
2020-02-05 04:55:00 +01:00
|
|
|
import classnames from 'classnames';
|
2020-05-21 10:53:21 +02:00
|
|
|
import usePersistedState from 'effects/use-persisted-state';
|
2019-06-27 01:59:27 +02:00
|
|
|
|
|
|
|
type Props = {
|
2020-02-05 04:55:00 +01:00
|
|
|
uri: string,
|
2020-01-30 02:02:21 +01:00
|
|
|
author: ?string, // LBRY Channel Name, e.g. @channel
|
|
|
|
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
|
|
|
|
commentId: string, // sha256 digest identifying the comment
|
2020-02-05 04:55:00 +01:00
|
|
|
parentId: string, // sha256 digest identifying the parent of the comment
|
2020-01-30 02:02:21 +01:00
|
|
|
message: string, // comment body
|
|
|
|
timePosted: number, // Comment timestamp
|
|
|
|
channel: ?Claim, // Channel Claim, retrieved to obtain thumbnail
|
2019-10-23 09:04:40 +02:00
|
|
|
pending?: boolean,
|
2020-01-30 02:02:21 +01:00
|
|
|
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,
|
2020-07-15 18:36:40 +02:00
|
|
|
blockChannel: string => void,
|
2020-08-24 19:35:21 +02:00
|
|
|
linkedComment?: any,
|
2019-06-27 01:59:27 +02:00
|
|
|
};
|
|
|
|
|
2019-11-27 22:08:43 +01:00
|
|
|
const LENGTH_TO_COLLAPSE = 300;
|
2020-01-30 02:02:21 +01:00
|
|
|
const ESCAPE_KEY = 27;
|
2019-11-27 22:08:43 +01:00
|
|
|
|
2019-06-27 01:59:27 +02:00
|
|
|
function Comment(props: Props) {
|
2019-10-23 09:04:40 +02:00
|
|
|
const {
|
2020-02-05 04:55:00 +01:00
|
|
|
uri,
|
2019-10-23 09:04:40 +02:00
|
|
|
author,
|
|
|
|
authorUri,
|
|
|
|
timePosted,
|
|
|
|
message,
|
|
|
|
pending,
|
2020-01-30 02:02:21 +01:00
|
|
|
channel,
|
2019-10-23 09:04:40 +02:00
|
|
|
isResolvingUri,
|
|
|
|
resolveUri,
|
|
|
|
channelIsBlocked,
|
2020-01-30 02:02:21 +01:00
|
|
|
commentIsMine,
|
|
|
|
commentId,
|
2020-02-05 04:55:00 +01:00
|
|
|
parentId,
|
2020-01-30 02:02:21 +01:00
|
|
|
updateComment,
|
|
|
|
deleteComment,
|
2020-07-15 18:36:40 +02:00
|
|
|
blockChannel,
|
2020-08-24 19:35:21 +02:00
|
|
|
linkedComment,
|
2019-10-23 09:04:40 +02:00
|
|
|
} = props;
|
2020-01-30 02:02:21 +01:00
|
|
|
|
|
|
|
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);
|
2020-08-24 19:35:21 +02:00
|
|
|
const [advancedEditor] = usePersistedState('comment-editor-mode', false);
|
2020-05-21 10:53:21 +02:00
|
|
|
|
2019-10-23 09:04:40 +02:00
|
|
|
// to debounce subsequent requests
|
|
|
|
const shouldFetch =
|
2020-01-30 02:02:21 +01:00
|
|
|
channel === undefined ||
|
|
|
|
(channel !== null && channel.value_type === 'channel' && isEmpty(channel.meta) && !pending);
|
2019-12-04 19:07:40 +01:00
|
|
|
|
2019-10-23 09:04:40 +02:00
|
|
|
useEffect(() => {
|
|
|
|
// If author was extracted from the URI, then it must be valid.
|
|
|
|
if (authorUri && author && !isResolvingUri && shouldFetch) {
|
|
|
|
resolveUri(authorUri);
|
|
|
|
}
|
2020-01-30 02:02:21 +01:00
|
|
|
|
|
|
|
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) {
|
2020-07-24 16:13:42 +02:00
|
|
|
setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value);
|
2020-01-30 02:02:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function handleSubmit() {
|
|
|
|
updateComment(commentId, editedMessage);
|
|
|
|
setEditing(false);
|
|
|
|
}
|
|
|
|
|
2019-06-27 01:59:27 +02:00
|
|
|
return (
|
2020-02-05 04:55:00 +01:00
|
|
|
<li
|
2020-08-24 19:35:21 +02:00
|
|
|
className={classnames('comment', {
|
|
|
|
comment__reply: parentId !== null,
|
|
|
|
comment__highlighted: linkedComment && linkedComment.comment_id === commentId,
|
|
|
|
})}
|
|
|
|
id={commentId}
|
2020-07-15 18:36:40 +02:00
|
|
|
onMouseOver={() => setMouseHover(true)}
|
|
|
|
onMouseOut={() => setMouseHover(false)}
|
2020-02-05 04:55:00 +01:00
|
|
|
>
|
2019-10-24 19:24:53 +02:00
|
|
|
<div className="comment__author-thumbnail">
|
2019-12-04 19:07:40 +01:00
|
|
|
{authorUri ? <ChannelThumbnail uri={authorUri} obscure={channelIsBlocked} small /> : <ChannelThumbnail small />}
|
2019-06-27 01:59:27 +02:00
|
|
|
</div>
|
2019-10-24 19:24:53 +02:00
|
|
|
|
|
|
|
<div className="comment__body_container">
|
2020-01-30 02:02:21 +01:00
|
|
|
<div className="comment__meta">
|
|
|
|
<div className="comment__meta-information">
|
|
|
|
{!author ? (
|
|
|
|
<span className="comment__author">{__('Anonymous')}</span>
|
|
|
|
) : (
|
|
|
|
<Button
|
|
|
|
className="button--uri-indicator truncated-text comment__author"
|
|
|
|
navigate={authorUri}
|
|
|
|
label={author}
|
|
|
|
/>
|
|
|
|
)}
|
2020-08-24 19:35:21 +02:00
|
|
|
{/* // link here */}
|
|
|
|
<Button
|
|
|
|
navigate={`${uri}?lc=${commentId}`}
|
|
|
|
label={
|
|
|
|
<time className="comment__time" dateTime={timePosted}>
|
|
|
|
{DateTime.getTimeAgoStr(timePosted)}
|
|
|
|
</time>
|
|
|
|
}
|
|
|
|
className="button--uri-indicator"
|
|
|
|
/>
|
2020-01-30 02:02:21 +01:00
|
|
|
</div>
|
|
|
|
<div className="comment__menu">
|
2020-07-15 18:36:40 +02:00
|
|
|
<Menu>
|
|
|
|
<MenuButton>
|
|
|
|
<Icon
|
|
|
|
size={18}
|
|
|
|
className={mouseIsHovering ? 'comment__menu-icon--hovering' : 'comment__menu-icon'}
|
|
|
|
icon={ICONS.MORE_VERTICAL}
|
|
|
|
/>
|
|
|
|
</MenuButton>
|
|
|
|
<MenuList className="menu__list--comments">
|
|
|
|
{commentIsMine ? (
|
|
|
|
<>
|
|
|
|
<MenuItem className="comment__menu-option" onSelect={() => setEditing(true)}>
|
|
|
|
{__('Edit')}
|
|
|
|
</MenuItem>
|
|
|
|
<MenuItem className="comment__menu-option" onSelect={() => deleteComment(commentId)}>
|
|
|
|
{__('Delete')}
|
|
|
|
</MenuItem>
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<MenuItem className="comment__menu-option" onSelect={() => blockChannel(authorUri)}>
|
|
|
|
{__('Block Channel')}
|
|
|
|
</MenuItem>
|
|
|
|
)}
|
|
|
|
</MenuList>
|
|
|
|
</Menu>
|
2020-01-30 02:02:21 +01:00
|
|
|
</div>
|
|
|
|
</div>
|
2019-10-24 19:24:53 +02:00
|
|
|
<div>
|
2020-01-30 02:02:21 +01:00
|
|
|
{isEditing ? (
|
|
|
|
<Form onSubmit={handleSubmit}>
|
|
|
|
<FormField
|
2020-07-24 16:13:42 +02:00
|
|
|
type={!SIMPLE_SITE && advancedEditor ? 'markdown' : 'textarea'}
|
2020-01-30 02:02:21 +01:00
|
|
|
name="editing_comment"
|
|
|
|
value={editedMessage}
|
|
|
|
charCount={charCount}
|
|
|
|
onChange={handleEditMessageChanged}
|
2020-06-12 17:18:04 +02:00
|
|
|
textAreaMaxLength={FF_MAX_CHARS_IN_COMMENT}
|
2020-01-30 02:02:21 +01:00
|
|
|
/>
|
|
|
|
<div className="section__actions">
|
|
|
|
<Button
|
|
|
|
button="primary"
|
|
|
|
type="submit"
|
|
|
|
label={__('Done')}
|
|
|
|
requiresAuth={IS_WEB}
|
|
|
|
disabled={message === editedMessage}
|
|
|
|
/>
|
|
|
|
<Button button="link" label={__('Cancel')} onClick={() => setEditing(false)} />
|
|
|
|
</div>
|
|
|
|
</Form>
|
|
|
|
) : editedMessage.length >= LENGTH_TO_COLLAPSE ? (
|
2019-12-04 19:07:40 +01:00
|
|
|
<div className="comment__message">
|
2020-08-24 19:35:21 +02:00
|
|
|
{/* <Expandable> */}
|
|
|
|
<MarkdownPreview content={message} />
|
|
|
|
{/* </Expandable> */}
|
2019-12-04 19:07:40 +01:00
|
|
|
</div>
|
2019-11-27 22:08:43 +01:00
|
|
|
) : (
|
2019-12-04 19:07:40 +01:00
|
|
|
<div className="comment__message">
|
|
|
|
<MarkdownPreview content={message} />
|
|
|
|
</div>
|
2019-11-27 22:08:43 +01:00
|
|
|
)}
|
2019-10-24 19:24:53 +02:00
|
|
|
</div>
|
2019-07-24 23:02:35 +02:00
|
|
|
</div>
|
2019-06-27 01:59:27 +02:00
|
|
|
</li>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default Comment;
|