fix comment editing
This commit is contained in:
parent
154b20c6c8
commit
f6961f91fe
11 changed files with 433 additions and 306 deletions
|
@ -1,14 +1,16 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectTotalStakedAmountForChannelUri,
|
||||
selectStakedLevelForChannelUri,
|
||||
makeSelectClaimForUri,
|
||||
selectThumbnailForUri,
|
||||
selectHasChannels,
|
||||
selectMyClaimIdsRaw,
|
||||
} from 'redux/selectors/claims';
|
||||
import { doCommentUpdate, doCommentList } from 'redux/actions/comments';
|
||||
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { doClearPlayingUri } from 'redux/actions/content';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import {
|
||||
selectLinkedCommentAncestors,
|
||||
selectOthersReactsForComment,
|
||||
|
@ -19,31 +21,34 @@ import { selectPlayingUri } from 'redux/selectors/content';
|
|||
import Comment from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { comment, uri } = props;
|
||||
const { comment_id, author_uri } = comment || {};
|
||||
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
|
||||
const reactionKey = activeChannelId ? `${props.commentId}:${activeChannelId}` : props.commentId;
|
||||
const reactionKey = activeChannelId ? `${comment_id}:${activeChannelId}` : comment_id;
|
||||
|
||||
return {
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
thumbnail: props.authorUri && selectThumbnailForUri(state, props.authorUri),
|
||||
channelIsBlocked: props.authorUri && makeSelectChannelIsMuted(props.authorUri)(state),
|
||||
commentingEnabled: true,
|
||||
myChannelIds: selectMyClaimIdsRaw(state),
|
||||
claim: makeSelectClaimForUri(uri)(state),
|
||||
thumbnail: author_uri && selectThumbnailForUri(state, author_uri),
|
||||
channelIsBlocked: author_uri && makeSelectChannelIsMuted(author_uri)(state),
|
||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||
othersReacts: selectOthersReactsForComment(state, reactionKey),
|
||||
activeChannelClaim,
|
||||
hasChannels: selectHasChannels(state),
|
||||
playingUri: selectPlayingUri(state),
|
||||
stakedLevel: selectTotalStakedAmountForChannelUri(state, props.authorUri),
|
||||
stakedLevel: selectStakedLevelForChannelUri(state, author_uri),
|
||||
linkedCommentAncestors: selectLinkedCommentAncestors(state),
|
||||
totalReplyPages: makeSelectTotalReplyPagesForParentId(props.commentId)(state),
|
||||
totalReplyPages: makeSelectTotalReplyPagesForParentId(comment_id)(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
clearPlayingUri: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||
updateComment: (commentId, comment) => dispatch(doCommentUpdate(commentId, comment)),
|
||||
fetchReplies: (uri, parentId, page, pageSize, sortBy) =>
|
||||
dispatch(doCommentList(uri, parentId, page, pageSize, sortBy)),
|
||||
doToast: (options) => dispatch(doToast(options)),
|
||||
});
|
||||
const perform = {
|
||||
doClearPlayingUri,
|
||||
doCommentUpdate,
|
||||
fetchReplies: doCommentList,
|
||||
doToast,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(Comment);
|
||||
|
|
|
@ -12,7 +12,7 @@ import DateTime from 'component/dateTime';
|
|||
import Button from 'component/button';
|
||||
import Expandable from 'component/expandable';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
import CommentBadge from 'component/common/comment-badge'; // have this?
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { Menu, MenuButton } from '@reach/menu-button';
|
||||
import Icon from 'component/common/icon';
|
||||
|
@ -27,23 +27,21 @@ import CommentMenuList from 'component/commentMenuList';
|
|||
import UriIndicator from 'component/uriIndicator';
|
||||
import CreditAmount from 'component/common/credit-amount';
|
||||
import OptimizedImage from 'component/optimizedImage';
|
||||
import { getChannelFromClaim } from 'util/claim';
|
||||
import { parseSticker } from 'util/comments';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
|
||||
const AUTO_EXPAND_ALL_REPLIES = false;
|
||||
|
||||
type Props = {
|
||||
comment: Comment,
|
||||
myChannelIds: ?Array<string>,
|
||||
clearPlayingUri: () => void,
|
||||
uri: string,
|
||||
claim: StreamClaim,
|
||||
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
|
||||
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,
|
||||
doCommentUpdate: (string, string) => void,
|
||||
fetchReplies: (string, string, number, number, number) => void,
|
||||
totalReplyPages: number,
|
||||
commentModBlock: (string) => void,
|
||||
|
@ -55,7 +53,6 @@ type Props = {
|
|||
isTopLevel?: boolean,
|
||||
threadDepth: number,
|
||||
hideActions?: boolean,
|
||||
isPinned: boolean,
|
||||
othersReacts: ?{
|
||||
like: number,
|
||||
dislike: number,
|
||||
|
@ -64,11 +61,6 @@ type Props = {
|
|||
activeChannelClaim: ?ChannelClaim,
|
||||
playingUri: ?PlayingUri,
|
||||
stakedLevel: number,
|
||||
supportAmount: number,
|
||||
numDirectReplies: number,
|
||||
isModerator: boolean,
|
||||
isGlobalMod: boolean,
|
||||
isFiat: boolean,
|
||||
supportDisabled: boolean,
|
||||
setQuickReply: (any) => void,
|
||||
quickReply: any,
|
||||
|
@ -76,19 +68,15 @@ type Props = {
|
|||
|
||||
const LENGTH_TO_COLLAPSE = 300;
|
||||
|
||||
function Comment(props: Props) {
|
||||
function CommentView(props: Props) {
|
||||
const {
|
||||
comment,
|
||||
myChannelIds,
|
||||
clearPlayingUri,
|
||||
claim,
|
||||
uri,
|
||||
author,
|
||||
authorUri,
|
||||
timePosted,
|
||||
message,
|
||||
channelIsBlocked,
|
||||
commentIsMine,
|
||||
commentId,
|
||||
updateComment,
|
||||
doCommentUpdate,
|
||||
fetchReplies,
|
||||
totalReplyPages,
|
||||
linkedCommentId,
|
||||
|
@ -99,39 +87,61 @@ function Comment(props: Props) {
|
|||
isTopLevel,
|
||||
threadDepth,
|
||||
hideActions,
|
||||
isPinned,
|
||||
othersReacts,
|
||||
playingUri,
|
||||
stakedLevel,
|
||||
supportAmount,
|
||||
numDirectReplies,
|
||||
isModerator,
|
||||
isGlobalMod,
|
||||
isFiat,
|
||||
supportDisabled,
|
||||
setQuickReply,
|
||||
quickReply,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
channel_url: authorUri,
|
||||
channel_name: author,
|
||||
channel_id: channelId,
|
||||
comment_id: commentId,
|
||||
comment: message,
|
||||
is_fiat: isFiat,
|
||||
is_global_mod: isGlobalMod,
|
||||
is_moderator: isModerator,
|
||||
is_pinned: isPinned,
|
||||
support_amount: supportAmount,
|
||||
replies: numDirectReplies,
|
||||
timestamp,
|
||||
} = comment;
|
||||
|
||||
const timePosted = timestamp * 1000;
|
||||
const commentIsMine = channelId && myChannelIds && myChannelIds.includes(channelId);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const {
|
||||
push,
|
||||
replace,
|
||||
location: { pathname, search },
|
||||
} = useHistory();
|
||||
|
||||
const isLinkedComment = linkedCommentId && linkedCommentId === commentId;
|
||||
const isInLinkedCommentChain =
|
||||
linkedCommentId &&
|
||||
linkedCommentAncestors[linkedCommentId] &&
|
||||
linkedCommentAncestors[linkedCommentId].includes(commentId);
|
||||
const showRepliesOnMount = isInLinkedCommentChain || AUTO_EXPAND_ALL_REPLIES;
|
||||
|
||||
const [isReplying, setReplying] = React.useState(false);
|
||||
const [isEditing, setEditing] = useState(false);
|
||||
const [editedMessage, setCommentValue] = useState(message);
|
||||
const [charCount, setCharCount] = useState(editedMessage.length);
|
||||
const [showReplies, setShowReplies] = useState(false);
|
||||
const [page, setPage] = useState(0);
|
||||
const [showReplies, setShowReplies] = useState(showRepliesOnMount); // on mount
|
||||
const [page, setPage] = useState(showRepliesOnMount ? 1 : 0);
|
||||
const [advancedEditor] = usePersistedState('comment-editor-mode', false);
|
||||
const [displayDeadComment, setDisplayDeadComment] = React.useState(false);
|
||||
const likesCount = (othersReacts && othersReacts.like) || 0;
|
||||
const dislikesCount = (othersReacts && othersReacts.dislike) || 0;
|
||||
const totalLikesAndDislikes = likesCount + dislikesCount;
|
||||
const slimedToDeath = totalLikesAndDislikes >= 5 && dislikesCount / totalLikesAndDislikes > 0.8;
|
||||
const commentByOwnerOfContent = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri;
|
||||
const contentChannelClaim = getChannelFromClaim(claim);
|
||||
const commentByOwnerOfContent = contentChannelClaim && contentChannelClaim.permanent_url === authorUri;
|
||||
const stickerFromMessage = parseSticker(message);
|
||||
|
||||
let channelOwnerOfContent;
|
||||
|
@ -142,19 +152,6 @@ function Comment(props: Props) {
|
|||
}
|
||||
} catch (e) {}
|
||||
|
||||
// Auto-expand (limited to linked-comments for now, but can be for all)
|
||||
useEffect(() => {
|
||||
const isInLinkedCommentChain =
|
||||
linkedCommentId &&
|
||||
linkedCommentAncestors[linkedCommentId] &&
|
||||
linkedCommentAncestors[linkedCommentId].includes(commentId);
|
||||
|
||||
if (isInLinkedCommentChain || AUTO_EXPAND_ALL_REPLIES) {
|
||||
setShowReplies(true);
|
||||
setPage(1);
|
||||
}
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing) {
|
||||
setCharCount(editedMessage.length);
|
||||
|
@ -193,7 +190,7 @@ function Comment(props: Props) {
|
|||
}
|
||||
|
||||
function handleSubmit() {
|
||||
updateComment(commentId, editedMessage);
|
||||
doCommentUpdate(commentId, editedMessage);
|
||||
if (setQuickReply) setQuickReply({ ...quickReply, comment_id: commentId, comment: editedMessage });
|
||||
setEditing(false);
|
||||
}
|
||||
|
@ -214,6 +211,34 @@ function Comment(props: Props) {
|
|||
replace(`${pathname}?${urlParams.toString()}`);
|
||||
}
|
||||
|
||||
// find a way to enable linked comments - probably use share url.
|
||||
// const linkedCommentRef = React.useCallback(
|
||||
// (node) => {
|
||||
// if (node !== null && window.pendingLinkedCommentScroll) {
|
||||
// const ROUGH_HEADER_HEIGHT = 125; // @see: --header-height
|
||||
// delete window.pendingLinkedCommentScroll;
|
||||
//
|
||||
// const mobileChatElem = document.querySelector('.MuiPaper-root .card--enable-overflow');
|
||||
// const drawerElem = document.querySelector('.MuiDrawer-root');
|
||||
// const elem = (isMobile && mobileChatElem) || window;
|
||||
//
|
||||
// if (elem) {
|
||||
// // $FlowFixMe
|
||||
// elem.scrollTo({
|
||||
// top:
|
||||
// node.getBoundingClientRect().top +
|
||||
// // $FlowFixMe
|
||||
// (mobileChatElem && drawerElem ? drawerElem.getBoundingClientRect().top * -1 : elem.scrollY) -
|
||||
// ROUGH_HEADER_HEIGHT,
|
||||
// left: 0,
|
||||
// behavior: 'smooth',
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// [isMobile]
|
||||
// );
|
||||
|
||||
return (
|
||||
<li
|
||||
className={classnames('comment', {
|
||||
|
@ -225,7 +250,7 @@ function Comment(props: Props) {
|
|||
>
|
||||
<div
|
||||
className={classnames('comment__content', {
|
||||
[COMMENT_HIGHLIGHTED]: linkedCommentId && linkedCommentId === commentId,
|
||||
[COMMENT_HIGHLIGHTED]: isLinkedComment,
|
||||
'comment--slimed': slimedToDeath && !displayDeadComment,
|
||||
})}
|
||||
>
|
||||
|
@ -240,21 +265,8 @@ function Comment(props: Props) {
|
|||
<div className="comment__body-container">
|
||||
<div className="comment__meta">
|
||||
<div className="comment__meta-information">
|
||||
{isGlobalMod && (
|
||||
<Tooltip title={__('Admin')}>
|
||||
<span className="comment__badge comment__badge--global-mod">
|
||||
<Icon icon={ICONS.BADGE_MOD} size={20} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{isModerator && (
|
||||
<Tooltip title={__('Moderator')}>
|
||||
<span className="comment__badge comment__badge--mod">
|
||||
<Icon icon={ICONS.BADGE_MOD} size={20} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isGlobalMod && <CommentBadge label={__('Admin')} icon={ICONS.BADGE_MOD} />}
|
||||
{isModerator && <CommentBadge label={__('Moderator')} icon={ICONS.BADGE_MOD} />}
|
||||
|
||||
{!author ? (
|
||||
<span className="comment__author">{__('Anonymous')}</span>
|
||||
|
@ -314,6 +326,7 @@ function Comment(props: Props) {
|
|||
charCount={charCount}
|
||||
onChange={handleEditMessageChanged}
|
||||
textAreaMaxLength={FF_MAX_CHARS_IN_COMMENT}
|
||||
handleSubmit={handleSubmit}
|
||||
/>
|
||||
<div className="section__actions section__actions--no-margin">
|
||||
<Button button="primary" type="submit" label={__('Done')} disabled={message === editedMessage} />
|
||||
|
@ -358,6 +371,7 @@ function Comment(props: Props) {
|
|||
className="comment__action"
|
||||
onClick={handleCommentReply}
|
||||
icon={ICONS.REPLY}
|
||||
iconSize={isMobile && 12}
|
||||
/>
|
||||
)}
|
||||
{ENABLE_COMMENT_REACTIONS && <CommentReactions uri={uri} commentId={commentId} />}
|
||||
|
@ -431,4 +445,4 @@ function Comment(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default Comment;
|
||||
export default CommentView;
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
makeSelectClaimForUri,
|
||||
selectClaimIsMine,
|
||||
selectFetchingMyChannels,
|
||||
selectMyClaimIdsRaw,
|
||||
} from 'redux/selectors/claims';
|
||||
import {
|
||||
selectTopLevelCommentsForUri,
|
||||
|
@ -22,12 +21,17 @@ import {
|
|||
} from 'redux/selectors/comments';
|
||||
import { doCommentReset, doCommentList, doCommentById, doCommentReactList } from 'redux/actions/comments';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { getChannelIdFromClaim } from 'util/claim';
|
||||
import CommentsList from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const claim = selectClaimForUri(state, props.uri);
|
||||
const { uri } = props;
|
||||
|
||||
const claim = selectClaimForUri(state, uri);
|
||||
const channelId = getChannelIdFromClaim(claim);
|
||||
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
const topLevelComments = selectTopLevelCommentsForUri(state, props.uri);
|
||||
const topLevelComments = selectTopLevelCommentsForUri(state, uri);
|
||||
|
||||
const resolvedComments =
|
||||
topLevelComments && topLevelComments.length > 0
|
||||
|
@ -37,12 +41,12 @@ const select = (state, props) => {
|
|||
return {
|
||||
topLevelComments,
|
||||
resolvedComments,
|
||||
myChannelIds: selectMyClaimIdsRaw(state),
|
||||
allCommentIds: selectCommentIdsForUri(state, props.uri),
|
||||
pinnedComments: selectPinnedCommentsForUri(state, props.uri),
|
||||
topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(props.uri)(state),
|
||||
totalComments: makeSelectTotalCommentsCountForUri(props.uri)(state),
|
||||
claim,
|
||||
allCommentIds: selectCommentIdsForUri(state, uri),
|
||||
pinnedComments: selectPinnedCommentsForUri(state, uri),
|
||||
topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(uri)(state),
|
||||
totalComments: makeSelectTotalCommentsCountForUri(uri)(state),
|
||||
claimId: claim && claim.claim_id,
|
||||
channelId,
|
||||
claimIsMine: selectClaimIsMine(state, claim),
|
||||
isFetchingComments: selectIsFetchingComments(state),
|
||||
isFetchingCommentsById: selectIsFetchingCommentsById(state),
|
||||
|
@ -55,12 +59,12 @@ const select = (state, props) => {
|
|||
};
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
fetchTopLevelComments: (uri, page, pageSize, sortBy) => dispatch(doCommentList(uri, '', page, pageSize, sortBy)),
|
||||
fetchComment: (commentId) => dispatch(doCommentById(commentId)),
|
||||
fetchReacts: (commentIds) => dispatch(doCommentReactList(commentIds)),
|
||||
resetComments: (claimId) => dispatch(doCommentReset(claimId)),
|
||||
doResolveUris: (uris) => dispatch(doResolveUris(uris, true)),
|
||||
});
|
||||
const perform = {
|
||||
fetchTopLevelComments: doCommentList,
|
||||
fetchComment: doCommentById,
|
||||
fetchReacts: doCommentReactList,
|
||||
resetComments: doCommentReset,
|
||||
doResolveUris,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(CommentsList);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
// @flow
|
||||
import { COMMENT_HIGHLIGHTED } from 'constants/classnames';
|
||||
import { COMMENT_PAGE_SIZE_TOP_LEVEL, SORT_BY } from 'constants/comment';
|
||||
import { ENABLE_COMMENT_REACTIONS } from 'config';
|
||||
import { getChannelIdFromClaim } from 'util/claim';
|
||||
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
|
||||
import { getCommentsListTitle } from 'util/comments';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as REACTION_TYPES from 'constants/reactions';
|
||||
import Button from 'component/button';
|
||||
|
@ -15,7 +14,6 @@ import debounce from 'util/debounce';
|
|||
import Empty from 'component/common/empty';
|
||||
import React, { useEffect } from 'react';
|
||||
import Spinner from 'component/spinner';
|
||||
import useFetched from 'effects/use-fetched';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
|
||||
const DEBOUNCE_SCROLL_HANDLER_MS = 200;
|
||||
|
@ -32,11 +30,12 @@ type Props = {
|
|||
allCommentIds: any,
|
||||
pinnedComments: Array<Comment>,
|
||||
topLevelComments: Array<Comment>,
|
||||
resolvedComments: Array<Comment>,
|
||||
topLevelTotalPages: number,
|
||||
uri: string,
|
||||
claim: ?Claim,
|
||||
claimId?: string,
|
||||
channelId?: string,
|
||||
claimIsMine: boolean,
|
||||
myChannels: ?Array<ChannelClaim>,
|
||||
isFetchingComments: boolean,
|
||||
isFetchingCommentsById: boolean,
|
||||
isFetchingReacts: boolean,
|
||||
|
@ -47,25 +46,26 @@ type Props = {
|
|||
othersReactsById: ?{ [string]: { [REACTION_TYPES.LIKE | REACTION_TYPES.DISLIKE]: number } },
|
||||
activeChannelId: ?string,
|
||||
settingsByChannelId: { [channelId: string]: PerChannelSettings },
|
||||
fetchReacts: (Array<string>) => Promise<any>,
|
||||
commentsAreExpanded?: boolean,
|
||||
fetchTopLevelComments: (string, number, number, number) => void,
|
||||
fetchComment: (string) => void,
|
||||
resetComments: (string) => void,
|
||||
fetchTopLevelComments: (uri: string, parentId: string, page: number, pageSize: number, sortBy: number) => void,
|
||||
fetchComment: (commentId: string) => void,
|
||||
fetchReacts: (commentIds: Array<string>) => Promise<any>,
|
||||
resetComments: (claimId: string) => void,
|
||||
doResolveUris: (uris: Array<string>, returnCachedClaims: boolean) => void,
|
||||
};
|
||||
|
||||
function CommentList(props: Props) {
|
||||
export default function CommentList(props: Props) {
|
||||
const {
|
||||
allCommentIds,
|
||||
uri,
|
||||
pinnedComments,
|
||||
topLevelComments,
|
||||
resolvedComments,
|
||||
topLevelTotalPages,
|
||||
claim,
|
||||
claimId,
|
||||
channelId,
|
||||
claimIsMine,
|
||||
myChannels,
|
||||
isFetchingComments,
|
||||
isFetchingCommentsById,
|
||||
isFetchingReacts,
|
||||
linkedCommentId,
|
||||
totalComments,
|
||||
|
@ -74,28 +74,33 @@ function CommentList(props: Props) {
|
|||
othersReactsById,
|
||||
activeChannelId,
|
||||
settingsByChannelId,
|
||||
fetchReacts,
|
||||
commentsAreExpanded,
|
||||
fetchTopLevelComments,
|
||||
fetchComment,
|
||||
fetchReacts,
|
||||
resetComments,
|
||||
doResolveUris,
|
||||
} = props;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const isMediumScreen = useIsMediumScreen();
|
||||
|
||||
const spinnerRef = React.useRef();
|
||||
const DEFAULT_SORT = ENABLE_COMMENT_REACTIONS ? SORT_BY.POPULARITY : SORT_BY.NEWEST;
|
||||
const [sort, setSort] = usePersistedState('comment-sort-by', DEFAULT_SORT);
|
||||
const [page, setPage] = React.useState(0);
|
||||
const fetchedCommentsOnce = useFetched(isFetchingComments);
|
||||
const fetchedReactsOnce = useFetched(isFetchingReacts);
|
||||
const fetchedLinkedComment = useFetched(isFetchingCommentsById);
|
||||
const hasDefaultExpansion = commentsAreExpanded || (!isMobile && !isMediumScreen);
|
||||
const [commentsToDisplay, setCommentsToDisplay] = React.useState(topLevelComments);
|
||||
const [didInitialPageFetch, setInitialPageFetch] = React.useState(false);
|
||||
const hasDefaultExpansion = commentsAreExpanded || !isMediumScreen || isMobile;
|
||||
const [expandedComments, setExpandedComments] = React.useState(hasDefaultExpansion);
|
||||
|
||||
const totalFetchedComments = allCommentIds ? allCommentIds.length : 0;
|
||||
const channelId = getChannelIdFromClaim(claim);
|
||||
const channelSettings = channelId ? settingsByChannelId[channelId] : undefined;
|
||||
const moreBelow = page < topLevelTotalPages;
|
||||
const isResolvingComments = topLevelComments && resolvedComments.length !== topLevelComments.length;
|
||||
const alreadyResolved = !isResolvingComments && resolvedComments.length !== 0;
|
||||
const canDisplayComments = commentsToDisplay && commentsToDisplay.length === topLevelComments.length;
|
||||
const title = getCommentsListTitle(totalComments);
|
||||
|
||||
// Display comments immediately if not fetching reactions
|
||||
// If not, wait to show comments until reactions are fetched
|
||||
|
@ -113,8 +118,8 @@ function CommentList(props: Props) {
|
|||
// Reset comments
|
||||
useEffect(() => {
|
||||
if (page === 0) {
|
||||
if (claim) {
|
||||
resetComments(claim.claim_id);
|
||||
if (claimId) {
|
||||
resetComments(claimId);
|
||||
}
|
||||
setPage(1);
|
||||
}
|
||||
|
@ -128,7 +133,7 @@ function CommentList(props: Props) {
|
|||
fetchComment(linkedCommentId);
|
||||
}
|
||||
|
||||
fetchTopLevelComments(uri, page, COMMENT_PAGE_SIZE_TOP_LEVEL, sort);
|
||||
fetchTopLevelComments(uri, '', page, COMMENT_PAGE_SIZE_TOP_LEVEL, sort);
|
||||
}
|
||||
}, [fetchComment, fetchTopLevelComments, linkedCommentId, page, sort, uri]);
|
||||
|
||||
|
@ -167,23 +172,17 @@ function CommentList(props: Props) {
|
|||
|
||||
// Scroll to linked-comment
|
||||
useEffect(() => {
|
||||
if (fetchedLinkedComment && fetchedCommentsOnce && fetchedReactsOnce) {
|
||||
const elems = document.getElementsByClassName(COMMENT_HIGHLIGHTED);
|
||||
if (elems.length > 0) {
|
||||
const ROUGH_HEADER_HEIGHT = 125; // @see: --header-height
|
||||
const linkedComment = elems[0];
|
||||
window.scrollTo({
|
||||
top: linkedComment.getBoundingClientRect().top + window.scrollY - ROUGH_HEADER_HEIGHT,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
if (linkedCommentId) {
|
||||
window.pendingLinkedCommentScroll = true;
|
||||
} else {
|
||||
delete window.pendingLinkedCommentScroll;
|
||||
}
|
||||
}, [fetchedLinkedComment, fetchedCommentsOnce, fetchedReactsOnce]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Infinite scroll
|
||||
useEffect(() => {
|
||||
function shouldFetchNextPage(page, topLevelTotalPages, window, document, yPrefetchPx = 1000) {
|
||||
function shouldFetchNextPage(page, topLevelTotalPages, yPrefetchPx = 1000) {
|
||||
if (!spinnerRef || !spinnerRef.current) return false;
|
||||
|
||||
const rect = spinnerRef.current.getBoundingClientRect(); // $FlowFixMe
|
||||
|
@ -206,86 +205,67 @@ function CommentList(props: Props) {
|
|||
}
|
||||
|
||||
const handleCommentScroll = debounce(() => {
|
||||
if (hasDefaultExpansion && shouldFetchNextPage(page, topLevelTotalPages, window, document)) {
|
||||
if (shouldFetchNextPage(page, topLevelTotalPages)) {
|
||||
setPage(page + 1);
|
||||
setInitialPageFetch(true);
|
||||
}
|
||||
}, DEBOUNCE_SCROLL_HANDLER_MS);
|
||||
|
||||
if (!isFetchingComments && readyToDisplayComments && moreBelow && spinnerRef && spinnerRef.current) {
|
||||
if (shouldFetchNextPage(page, topLevelTotalPages, window, document, 0)) {
|
||||
setPage(page + 1);
|
||||
} else {
|
||||
window.addEventListener('scroll', handleCommentScroll);
|
||||
return () => window.removeEventListener('scroll', handleCommentScroll);
|
||||
if (!didInitialPageFetch) {
|
||||
handleCommentScroll();
|
||||
setInitialPageFetch(true);
|
||||
}
|
||||
|
||||
if (hasDefaultExpansion && !isFetchingComments && canDisplayComments && readyToDisplayComments && moreBelow) {
|
||||
const commentsInDrawer = Boolean(document.querySelector('.MuiDrawer-root .card--enable-overflow'));
|
||||
const scrollingElement = commentsInDrawer ? document.querySelector('.card--enable-overflow') : window;
|
||||
|
||||
if (scrollingElement) {
|
||||
scrollingElement.addEventListener('scroll', handleCommentScroll);
|
||||
|
||||
return () => scrollingElement.removeEventListener('scroll', handleCommentScroll);
|
||||
}
|
||||
}
|
||||
}, [hasDefaultExpansion, isFetchingComments, moreBelow, page, readyToDisplayComments, topLevelTotalPages]);
|
||||
}, [
|
||||
canDisplayComments,
|
||||
hasDefaultExpansion,
|
||||
didInitialPageFetch,
|
||||
isFetchingComments,
|
||||
isMobile,
|
||||
moreBelow,
|
||||
page,
|
||||
readyToDisplayComments,
|
||||
topLevelTotalPages,
|
||||
]);
|
||||
|
||||
const getCommentElems = (comments) => {
|
||||
return comments.map((comment) => (
|
||||
<CommentView
|
||||
isTopLevel
|
||||
threadDepth={3}
|
||||
key={comment.comment_id}
|
||||
uri={uri}
|
||||
authorUri={comment.channel_url}
|
||||
author={comment.channel_name}
|
||||
claimId={comment.claim_id}
|
||||
commentId={comment.comment_id}
|
||||
message={comment.comment}
|
||||
timePosted={comment.timestamp * 1000}
|
||||
claimIsMine={claimIsMine}
|
||||
commentIsMine={
|
||||
comment.channel_id && myChannels && myChannels.some(({ claim_id }) => claim_id === comment.channel_id)
|
||||
}
|
||||
linkedCommentId={linkedCommentId}
|
||||
isPinned={comment.is_pinned}
|
||||
supportAmount={comment.support_amount}
|
||||
numDirectReplies={comment.replies}
|
||||
isModerator={comment.is_moderator}
|
||||
isGlobalMod={comment.is_global_mod}
|
||||
isFiat={comment.is_fiat}
|
||||
/>
|
||||
));
|
||||
};
|
||||
// Wait to only display topLevelComments after resolved or else
|
||||
// other components will try to resolve again, like channelThumbnail
|
||||
useEffect(() => {
|
||||
if (!isResolvingComments) setCommentsToDisplay(topLevelComments);
|
||||
}, [isResolvingComments, topLevelComments]);
|
||||
|
||||
const sortButton = (label, icon, sortOption) => {
|
||||
return (
|
||||
<Button
|
||||
button="alt"
|
||||
label={label}
|
||||
icon={icon}
|
||||
iconSize={18}
|
||||
onClick={() => changeSort(sortOption)}
|
||||
className={classnames(`button-toggle`, {
|
||||
'button-toggle--active': sort === sortOption,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
// Batch resolve comment channel urls
|
||||
useEffect(() => {
|
||||
if (!topLevelComments || alreadyResolved) return;
|
||||
|
||||
const urisToResolve = [];
|
||||
topLevelComments.map(({ channel_url }) => channel_url !== undefined && urisToResolve.push(channel_url));
|
||||
|
||||
if (urisToResolve.length > 0) doResolveUris(urisToResolve, true);
|
||||
}, [alreadyResolved, doResolveUris, topLevelComments]);
|
||||
|
||||
const commentProps = { isTopLevel: true, threadDepth: 3, uri, claimIsMine, linkedCommentId };
|
||||
const actionButtonsProps = { totalComments, sort, changeSort, setPage };
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="card--enable-overflow"
|
||||
title={
|
||||
(totalComments === 0 && __('Leave a comment')) ||
|
||||
(totalComments === 1 && __('1 comment')) ||
|
||||
__('%totalComments% comments', { totalComments })
|
||||
}
|
||||
titleActions={
|
||||
<>
|
||||
{totalComments > 1 && ENABLE_COMMENT_REACTIONS && (
|
||||
<span className="comment__sort">
|
||||
{sortButton(__('Best'), ICONS.BEST, SORT_BY.POPULARITY)}
|
||||
{sortButton(__('Controversial'), ICONS.CONTROVERSIAL, SORT_BY.CONTROVERSY)}
|
||||
{sortButton(__('New'), ICONS.NEW, SORT_BY.NEWEST)}
|
||||
</span>
|
||||
)}
|
||||
<Button button="alt" icon={ICONS.REFRESH} title={__('Refresh')} onClick={() => setPage(0)} />
|
||||
</>
|
||||
}
|
||||
title={!isMobile && title}
|
||||
titleActions={<CommentActionButtons {...actionButtonsProps} />}
|
||||
actions={
|
||||
<>
|
||||
{isMobile && <CommentActionButtons {...actionButtonsProps} />}
|
||||
|
||||
<CommentCreate uri={uri} />
|
||||
|
||||
{channelSettings && channelSettings.comments_enabled && !isFetchingComments && !totalComments && (
|
||||
|
@ -294,15 +274,20 @@ function CommentList(props: Props) {
|
|||
|
||||
<ul
|
||||
className={classnames({
|
||||
comments: expandedComments,
|
||||
'comments--contracted': !expandedComments,
|
||||
comments: !isMediumScreen || expandedComments,
|
||||
'comments--contracted': isMediumScreen && !expandedComments,
|
||||
})}
|
||||
>
|
||||
{readyToDisplayComments && pinnedComments && getCommentElems(pinnedComments)}
|
||||
{readyToDisplayComments && topLevelComments && getCommentElems(topLevelComments)}
|
||||
{readyToDisplayComments && (
|
||||
<>
|
||||
{pinnedComments && <CommentElements comments={pinnedComments} {...commentProps} />}
|
||||
|
||||
{commentsToDisplay && <CommentElements comments={commentsToDisplay} {...commentProps} />}
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
{!hasDefaultExpansion && topLevelComments && Boolean(topLevelComments.length) && (
|
||||
{!hasDefaultExpansion && (
|
||||
<div className="card__bottom-actions--comments">
|
||||
{(!expandedComments || moreBelow) && (
|
||||
<Button
|
||||
|
@ -323,7 +308,7 @@ function CommentList(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{(isFetchingComments || (hasDefaultExpansion && moreBelow)) && (
|
||||
{(isFetchingComments || (hasDefaultExpansion && moreBelow) || !canDisplayComments) && (
|
||||
<div className="main--empty" ref={spinnerRef}>
|
||||
<Spinner type="small" />
|
||||
</div>
|
||||
|
@ -334,4 +319,64 @@ function CommentList(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default CommentList;
|
||||
type CommentProps = {
|
||||
comments: Array<Comment>,
|
||||
};
|
||||
|
||||
const CommentElements = (commentProps: CommentProps) => {
|
||||
const { comments, ...commentsProps } = commentProps;
|
||||
|
||||
return comments.map((comment) => <CommentView key={comment.comment_id} comment={comment} {...commentsProps} />);
|
||||
};
|
||||
|
||||
type ActionButtonsProps = {
|
||||
totalComments: number,
|
||||
sort: string,
|
||||
changeSort: (string) => void,
|
||||
setPage: (number) => void,
|
||||
};
|
||||
|
||||
const CommentActionButtons = (actionButtonsProps: ActionButtonsProps) => {
|
||||
const { totalComments, sort, changeSort, setPage } = actionButtonsProps;
|
||||
|
||||
const sortButtonProps = { activeSort: sort, changeSort };
|
||||
|
||||
return (
|
||||
<>
|
||||
{totalComments > 1 && ENABLE_COMMENT_REACTIONS && (
|
||||
<span className="comment__sort">
|
||||
<SortButton {...sortButtonProps} label={__('Best')} icon={ICONS.BEST} sortOption={SORT_BY.POPULARITY} />
|
||||
<SortButton
|
||||
{...sortButtonProps}
|
||||
label={__('Controversial')}
|
||||
icon={ICONS.CONTROVERSIAL}
|
||||
sortOption={SORT_BY.CONTROVERSY}
|
||||
/>
|
||||
<SortButton {...sortButtonProps} label={__('New')} icon={ICONS.NEW} sortOption={SORT_BY.NEWEST} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
<Button button="alt" icon={ICONS.REFRESH} title={__('Refresh')} onClick={() => setPage(0)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type SortButtonProps = {
|
||||
activeSort: string,
|
||||
sortOption: string,
|
||||
changeSort: (string) => void,
|
||||
};
|
||||
|
||||
const SortButton = (sortButtonProps: SortButtonProps) => {
|
||||
const { activeSort, sortOption, changeSort, ...buttonProps } = sortButtonProps;
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...buttonProps}
|
||||
className={classnames(`button-toggle`, { 'button-toggle--active': activeSort === sortOption })}
|
||||
button="alt"
|
||||
iconSize={18}
|
||||
onClick={() => changeSort(sortOption)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doResolveUris } from 'redux/actions/claims';
|
||||
import { makeSelectClaimIsMine, selectMyChannelClaims, makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { selectClaimIsMineForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { selectIsFetchingCommentsByParentId, selectRepliesForParentId } from 'redux/selectors/comments';
|
||||
import CommentsReplies from './view';
|
||||
|
||||
|
@ -14,9 +14,8 @@ const select = (state, props) => {
|
|||
return {
|
||||
fetchedReplies,
|
||||
resolvedReplies,
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
claimIsMine: selectClaimIsMineForUri(state, props.uri),
|
||||
userCanComment: true,
|
||||
myChannels: selectMyChannelClaims(state),
|
||||
isFetchingByParentId: selectIsFetchingCommentsByParentId(state),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import Comment from 'component/comment';
|
||||
import Button from 'component/button';
|
||||
import Comment from 'component/comment';
|
||||
import React from 'react';
|
||||
import Spinner from 'component/spinner';
|
||||
|
||||
type Props = {
|
||||
fetchedReplies: Array<any>,
|
||||
fetchedReplies: Array<Comment>,
|
||||
resolvedReplies: Array<Comment>,
|
||||
uri: string,
|
||||
parentId: string,
|
||||
claimIsMine: boolean,
|
||||
myChannels: ?Array<ChannelClaim>,
|
||||
linkedCommentId?: string,
|
||||
commentingEnabled: boolean,
|
||||
userCanComment: boolean,
|
||||
threadDepth: number,
|
||||
numDirectReplies: number, // Total replies for parentId as reported by 'comment[replies]'. Includes blocked items.
|
||||
isFetchingByParentId: { [string]: boolean },
|
||||
onShowMore?: () => void,
|
||||
hasMore: boolean,
|
||||
supportDisabled: boolean,
|
||||
doResolveUris: (Array<string>) => void,
|
||||
onShowMore?: () => void,
|
||||
};
|
||||
|
||||
function CommentsReplies(props: Props) {
|
||||
|
@ -26,102 +27,93 @@ function CommentsReplies(props: Props) {
|
|||
uri,
|
||||
parentId,
|
||||
fetchedReplies,
|
||||
resolvedReplies,
|
||||
claimIsMine,
|
||||
myChannels,
|
||||
linkedCommentId,
|
||||
commentingEnabled,
|
||||
userCanComment,
|
||||
threadDepth,
|
||||
numDirectReplies,
|
||||
isFetchingByParentId,
|
||||
onShowMore,
|
||||
hasMore,
|
||||
supportDisabled,
|
||||
doResolveUris,
|
||||
onShowMore,
|
||||
} = props;
|
||||
|
||||
const [isExpanded, setExpanded] = React.useState(true);
|
||||
const [commentsToDisplay, setCommentsToDisplay] = React.useState(fetchedReplies);
|
||||
const isResolvingReplies = fetchedReplies && resolvedReplies.length !== fetchedReplies.length;
|
||||
const alreadyResolved = !isResolvingReplies && resolvedReplies.length !== 0;
|
||||
const canDisplayComments = commentsToDisplay && commentsToDisplay.length === fetchedReplies.length;
|
||||
|
||||
function showMore() {
|
||||
if (onShowMore) {
|
||||
onShowMore();
|
||||
}
|
||||
}
|
||||
// Batch resolve comment channel urls
|
||||
React.useEffect(() => {
|
||||
if (!fetchedReplies || alreadyResolved) return;
|
||||
|
||||
// todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine
|
||||
function isMyComment(channelId: string) {
|
||||
if (myChannels != null && channelId != null) {
|
||||
for (let i = 0; i < myChannels.length; i++) {
|
||||
if (myChannels[i].claim_id === channelId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const urisToResolve = [];
|
||||
fetchedReplies.map(({ channel_url }) => channel_url !== undefined && urisToResolve.push(channel_url));
|
||||
|
||||
const displayedComments = fetchedReplies;
|
||||
if (urisToResolve.length > 0) doResolveUris(urisToResolve);
|
||||
}, [alreadyResolved, doResolveUris, fetchedReplies]);
|
||||
|
||||
return (
|
||||
Boolean(numDirectReplies) && (
|
||||
<div className="comment__replies-container">
|
||||
{Boolean(numDirectReplies) && !isExpanded && (
|
||||
// Wait to only display topLevelComments after resolved or else
|
||||
// other components will try to resolve again, like channelThumbnail
|
||||
React.useEffect(() => {
|
||||
if (!isResolvingReplies) setCommentsToDisplay(fetchedReplies);
|
||||
}, [isResolvingReplies, fetchedReplies]);
|
||||
|
||||
return !numDirectReplies ? null : (
|
||||
<div className="comment__replies-container">
|
||||
{!isExpanded ? (
|
||||
<div className="comment__actions--nested">
|
||||
<Button
|
||||
className="comment__action"
|
||||
label={__('Show Replies')}
|
||||
onClick={() => setExpanded(!isExpanded)}
|
||||
icon={isExpanded ? ICONS.UP : ICONS.DOWN}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="comment__replies">
|
||||
<Button className="comment__threadline" aria-label="Hide Replies" onClick={() => setExpanded(false)} />
|
||||
|
||||
<ul className="comments--replies">
|
||||
{!isResolvingReplies &&
|
||||
commentsToDisplay &&
|
||||
commentsToDisplay.length > 0 &&
|
||||
commentsToDisplay.map((comment) => (
|
||||
<Comment
|
||||
key={comment.comment_id}
|
||||
threadDepth={threadDepth}
|
||||
uri={uri}
|
||||
comment={comment}
|
||||
claimIsMine={claimIsMine}
|
||||
linkedCommentId={linkedCommentId}
|
||||
commentingEnabled={userCanComment}
|
||||
supportDisabled={supportDisabled}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{isExpanded && fetchedReplies && hasMore && (
|
||||
<div className="comment__actions--nested">
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Show more')}
|
||||
onClick={() => onShowMore && onShowMore()}
|
||||
className="button--uri-indicator"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(isFetchingByParentId[parentId] || isResolvingReplies || !canDisplayComments) && (
|
||||
<div className="comment__replies-container">
|
||||
<div className="comment__actions--nested">
|
||||
<Button
|
||||
className="comment__action"
|
||||
label={__('Show Replies')}
|
||||
onClick={() => setExpanded(!isExpanded)}
|
||||
icon={isExpanded ? ICONS.UP : ICONS.DOWN}
|
||||
/>
|
||||
<Spinner type="small" />
|
||||
</div>
|
||||
)}
|
||||
{isExpanded && (
|
||||
<div>
|
||||
<div className="comment__replies">
|
||||
<Button className="comment__threadline" aria-label="Hide Replies" onClick={() => setExpanded(false)} />
|
||||
|
||||
<ul className="comments--replies">
|
||||
{displayedComments &&
|
||||
displayedComments.map((comment) => {
|
||||
return (
|
||||
<Comment
|
||||
threadDepth={threadDepth}
|
||||
uri={uri}
|
||||
authorUri={comment.channel_url}
|
||||
author={comment.channel_name}
|
||||
claimId={comment.claim_id}
|
||||
commentId={comment.comment_id}
|
||||
key={comment.comment_id}
|
||||
message={comment.comment}
|
||||
timePosted={comment.timestamp * 1000}
|
||||
claimIsMine={claimIsMine}
|
||||
commentIsMine={comment.channel_id && isMyComment(comment.channel_id)}
|
||||
linkedCommentId={linkedCommentId}
|
||||
commentingEnabled={commentingEnabled}
|
||||
supportAmount={comment.support_amount}
|
||||
numDirectReplies={comment.replies}
|
||||
isModerator={comment.is_moderator}
|
||||
isGlobalMod={comment.is_global_mod}
|
||||
supportDisabled={supportDisabled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isExpanded && fetchedReplies && hasMore && (
|
||||
<div className="comment__actions--nested">
|
||||
<Button button="link" label={__('Show more')} onClick={showMore} className="button--uri-indicator" />
|
||||
</div>
|
||||
)}
|
||||
{isFetchingByParentId[parentId] && (
|
||||
<div className="comment__replies-container">
|
||||
<div className="comment__actions--nested">
|
||||
<Spinner type="small" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
35
ui/component/common/comment-badge.jsx
Normal file
35
ui/component/common/comment-badge.jsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
// @flow
|
||||
import 'scss/component/_comment-badge.scss';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import Icon from 'component/common/icon';
|
||||
import React from 'react';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
|
||||
const LABEL_TYPES = {
|
||||
ADMIN: 'Admin',
|
||||
MOD: 'Moderator',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
icon: string,
|
||||
label: string,
|
||||
size?: number,
|
||||
};
|
||||
|
||||
export default function CommentBadge(props: Props) {
|
||||
const { icon, label, size = 20 } = props;
|
||||
|
||||
return (
|
||||
<Tooltip title={label} placement="top">
|
||||
<span
|
||||
className={classnames('comment__badge', {
|
||||
'comment__badge--globalMod': label === LABEL_TYPES.ADMIN,
|
||||
'comment__badge--mod': label === LABEL_TYPES.MOD,
|
||||
})}
|
||||
>
|
||||
<Icon icon={icon} size={size} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
|
@ -75,21 +75,15 @@ export default function OwnComments(props: Props) {
|
|||
)}
|
||||
{!contentClaim && <Empty text={__('Content or channel was deleted.')} />}
|
||||
</div>
|
||||
<Comment
|
||||
isTopLevel
|
||||
hideActions
|
||||
authorUri={comment.channel_url}
|
||||
author={comment.channel_name}
|
||||
commentId={comment.comment_id}
|
||||
message={comment.comment}
|
||||
timePosted={comment.timestamp * 1000}
|
||||
commentIsMine
|
||||
supportAmount={comment.support_amount}
|
||||
numDirectReplies={0} // Don't show replies here
|
||||
isModerator={comment.is_moderator}
|
||||
isGlobalMod={comment.is_global_mod}
|
||||
isFiat={comment.is_fiat}
|
||||
/>
|
||||
<React.Suspense fallback={null}>
|
||||
<Comment
|
||||
isTopLevel
|
||||
hideActions
|
||||
comment={comment}
|
||||
commentIsMine
|
||||
numDirectReplies={0} // Don't show replies here
|
||||
/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -110,6 +110,8 @@ export function doSetPrimaryUri(uri: ?string) {
|
|||
};
|
||||
}
|
||||
|
||||
export const doClearPlayingUri = () => (dispatch: Dispatch) => dispatch(doSetPlayingUri({ uri: null }));
|
||||
|
||||
export function doSetPlayingUri({
|
||||
uri,
|
||||
source,
|
||||
|
|
23
ui/scss/component/_comment-badge.scss
Normal file
23
ui/scss/component/_comment-badge.scss
Normal file
|
@ -0,0 +1,23 @@
|
|||
.comment__badge {
|
||||
padding-right: var(--spacing-xxs);
|
||||
|
||||
.icon {
|
||||
margin-bottom: -4px;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.comment__badge--globalMod {
|
||||
.st0 {
|
||||
// @see: ICONS.BADGE_MOD
|
||||
fill: #fe7500;
|
||||
}
|
||||
}
|
||||
|
||||
.comment__badge--mod {
|
||||
.st0 {
|
||||
// @see: ICONS.BADGE_MOD
|
||||
fill: #ff3850;
|
||||
}
|
||||
}
|
|
@ -108,3 +108,17 @@ export function parseSticker(comment: string) {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function getStickerUrl(comment: string) {
|
||||
const stickerFromComment = parseSticker(comment);
|
||||
return stickerFromComment && stickerFromComment.url;
|
||||
}
|
||||
|
||||
export function getCommentsListTitle(totalComments: number) {
|
||||
const title =
|
||||
(totalComments === 0 && __('Leave a comment')) ||
|
||||
(totalComments === 1 && __('1 comment')) ||
|
||||
__('%total_comments% comments', { total_comments: totalComments });
|
||||
|
||||
return title;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue