From f6961f91fefda1513ddb65d65d71e2e1de9f4237 Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 9 Feb 2022 12:27:11 -0300 Subject: [PATCH] fix comment editing --- ui/component/comment/index.js | 37 ++-- ui/component/comment/view.jsx | 138 ++++++++------ ui/component/commentsList/index.js | 36 ++-- ui/component/commentsList/view.jsx | 261 +++++++++++++++----------- ui/component/commentsReplies/index.js | 5 +- ui/component/commentsReplies/view.jsx | 164 ++++++++-------- ui/component/common/comment-badge.jsx | 35 ++++ ui/page/ownComments/view.jsx | 24 +-- ui/redux/actions/content.js | 2 + ui/scss/component/_comment-badge.scss | 23 +++ ui/util/comments.js | 14 ++ 11 files changed, 433 insertions(+), 306 deletions(-) create mode 100644 ui/component/common/comment-badge.jsx create mode 100644 ui/scss/component/_comment-badge.scss diff --git a/ui/component/comment/index.js b/ui/component/comment/index.js index 553b10a57..4b04bac5a 100644 --- a/ui/component/comment/index.js +++ b/ui/component/comment/index.js @@ -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); diff --git a/ui/component/comment/view.jsx b/ui/component/comment/view.jsx index 772547fc5..9ec8925da 100644 --- a/ui/component/comment/view.jsx +++ b/ui/component/comment/view.jsx @@ -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, 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 (
  • @@ -240,21 +265,8 @@ function Comment(props: Props) {
    - {isGlobalMod && ( - - - - - - )} - - {isModerator && ( - - - - - - )} + {isGlobalMod && } + {isModerator && } {!author ? ( {__('Anonymous')} @@ -314,6 +326,7 @@ function Comment(props: Props) { charCount={charCount} onChange={handleEditMessageChanged} textAreaMaxLength={FF_MAX_CHARS_IN_COMMENT} + handleSubmit={handleSubmit} />
    + ) : ( +
    +
    + )} + {isExpanded && fetchedReplies && hasMore && ( +
    +
    + )} + {(isFetchingByParentId[parentId] || isResolvingReplies || !canDisplayComments) && ( +
    -
    - )} - {isExpanded && ( -
    -
    -
    -
    - )} - {isExpanded && fetchedReplies && hasMore && ( -
    -
    - )} - {isFetchingByParentId[parentId] && ( -
    -
    - -
    -
    - )} -
    - ) +
    + )} +
    ); } diff --git a/ui/component/common/comment-badge.jsx b/ui/component/common/comment-badge.jsx new file mode 100644 index 000000000..441394223 --- /dev/null +++ b/ui/component/common/comment-badge.jsx @@ -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 ( + + + + + + ); +} diff --git a/ui/page/ownComments/view.jsx b/ui/page/ownComments/view.jsx index 504b098f6..ef0e33912 100644 --- a/ui/page/ownComments/view.jsx +++ b/ui/page/ownComments/view.jsx @@ -75,21 +75,15 @@ export default function OwnComments(props: Props) { )} {!contentClaim && }
    - + + +
    ); diff --git a/ui/redux/actions/content.js b/ui/redux/actions/content.js index 04068526b..7c8a92839 100644 --- a/ui/redux/actions/content.js +++ b/ui/redux/actions/content.js @@ -110,6 +110,8 @@ export function doSetPrimaryUri(uri: ?string) { }; } +export const doClearPlayingUri = () => (dispatch: Dispatch) => dispatch(doSetPlayingUri({ uri: null })); + export function doSetPlayingUri({ uri, source, diff --git a/ui/scss/component/_comment-badge.scss b/ui/scss/component/_comment-badge.scss new file mode 100644 index 000000000..8810e846d --- /dev/null +++ b/ui/scss/component/_comment-badge.scss @@ -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; + } +} diff --git a/ui/util/comments.js b/ui/util/comments.js index 29b10c385..3e7e65104 100644 --- a/ui/util/comments.js +++ b/ui/util/comments.js @@ -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; +}