From c43eff8587b5905f383a221b95eb91f4ad000faf Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Wed, 7 Oct 2020 15:14:52 -0400 Subject: [PATCH] comments v69 --- static/app-strings.json | 3 +- ui/component/comment/view.jsx | 40 ++++++++++------------- ui/component/commentCreate/index.js | 4 ++- ui/component/commentCreate/view.jsx | 25 +++++++++++---- ui/component/commentsList/view.jsx | 5 ++- ui/component/commentsReplies/index.js | 2 +- ui/component/commentsReplies/view.jsx | 46 +++++---------------------- ui/scss/component/_comments.scss | 22 +++++++++---- ui/scss/themes/dark.scss | 2 ++ ui/scss/themes/light.scss | 3 +- 10 files changed, 70 insertions(+), 82 deletions(-) diff --git a/static/app-strings.json b/static/app-strings.json index 252a28d2b..2d83c801b 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -1308,8 +1308,7 @@ "Downvote": "Downvote", "Best": "Best", "Controversial": "Controversial", - "Hide %number% Replies": "Hide %number% Replies", - "Show %number% Replies": "Show %number% Replies", + "Show Replies": "Show Replies", "Unable to create comment, please try again later.": "Unable to create comment, please try again later.", "Your channel is still being setup, try again in a few moments.": "Your channel is still being setup, try again in a few moments.", "Unable to delete this comment, please try again later.": "Unable to delete this comment, please try again later.", diff --git a/ui/component/comment/view.jsx b/ui/component/comment/view.jsx index 1050eb386..5cc60c653 100644 --- a/ui/component/comment/view.jsx +++ b/ui/component/comment/view.jsx @@ -18,13 +18,13 @@ 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 - topLevelId: 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 @@ -41,10 +41,8 @@ type Props = { myChannels: ?Array, commentingEnabled: boolean, doToast: ({ message: string }) => void, - hideReplyButton?: boolean, isTopLevel?: boolean, - topLevelIsReplying: boolean, - setTopLevelIsReplying: boolean => void, + threadDepth: number, }; const LENGTH_TO_COLLAPSE = 300; @@ -71,11 +69,8 @@ function Comment(props: Props) { commentingEnabled, myChannels, doToast, - hideReplyButton, isTopLevel, - topLevelIsReplying, - setTopLevelIsReplying, - topLevelId, + threadDepth, } = props; const { push, @@ -134,17 +129,14 @@ function Comment(props: Props) { push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`); doToast({ message: __('A channel is required to comment on %SITE_NAME%', { SITE_NAME }) }); } else { - if (setTopLevelIsReplying) { - setTopLevelIsReplying(!topLevelIsReplying); - } else { - setReplying(!isReplying); - } + setReplying(!isReplying); } } return (
  • - {!hideReplyButton && ( + {threadDepth !== 0 && (
    + + {isReplying && ( + setReplying(false)} + onCancelReplying={() => setReplying(false)} + /> + )} )} - {isTopLevel && ( - - )} +
  • ); } diff --git a/ui/component/commentCreate/index.js b/ui/component/commentCreate/index.js index e1545e376..7dae8990a 100644 --- a/ui/component/commentCreate/index.js +++ b/ui/component/commentCreate/index.js @@ -1,15 +1,17 @@ import { connect } from 'react-redux'; import { makeSelectClaimForUri, selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux'; +import { selectIsPostingComment } from 'redux/selectors/comments'; import { doOpenModal } from 'redux/actions/app'; import { doCommentCreate } from 'redux/actions/comments'; -import { CommentCreate } from './view'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; +import { CommentCreate } from './view'; const select = (state, props) => ({ commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, claim: makeSelectClaimForUri(props.uri)(state), channels: selectMyChannelClaims(state), isFetchingChannels: selectFetchingMyChannels(state), + isPostingComment: selectIsPostingComment(state), }); const perform = (dispatch, ownProps) => ({ diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 43e47e3f8..45bb59720 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -17,11 +17,13 @@ type Props = { claim: StreamClaim, createComment: (string, string, string, ?string) => Promise, channels: ?Array, - topLevelId?: string, onDoneReplying?: () => void, onCancelReplying?: () => void, isNested: boolean, isFetchingChannels: boolean, + parentId: string, + isReply: boolean, + isPostingComment: boolean, }; export function CommentCreate(props: Props) { @@ -29,23 +31,23 @@ export function CommentCreate(props: Props) { createComment, claim, channels, - topLevelId, onDoneReplying, onCancelReplying, isNested, isFetchingChannels, + isReply, + parentId, + isPostingComment, } = props; const buttonref: ElementRef = React.useRef(); const { push } = useHistory(); const { claim_id: claimId } = claim; - const isReply = !!topLevelId; const [commentValue, setCommentValue] = React.useState(''); const [channel, setChannel] = usePersistedState('comment-channel', ''); const [charCount, setCharCount] = useState(commentValue.length); const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false); const hasChannels = channels && channels.length; - const disabled = channel === CHANNEL_NEW || !commentValue.length; - + const disabled = isPostingComment || channel === CHANNEL_NEW || !commentValue.length; const topChannel = channels && channels.reduce((top, channel) => { @@ -90,9 +92,10 @@ export function CommentCreate(props: Props) { function handleSubmit() { if (channel !== CHANNEL_NEW && commentValue.length) { - createComment(commentValue, claimId, channel, topLevelId).then(res => { + createComment(commentValue, claimId, channel, parentId).then(res => { if (res && res.signature) { setCommentValue(''); + if (onDoneReplying) { onDoneReplying(); } @@ -160,7 +163,15 @@ export function CommentCreate(props: Props) { button="primary" disabled={disabled} type="submit" - label={isReply ? __('Reply') : __('Post')} + label={ + isReply + ? isPostingComment + ? __('Replying...') + : __('Reply') + : isPostingComment + ? __('Posting...') + : __('Post') + } requiresAuth={IS_WEB} /> {isReply && ( diff --git a/ui/component/commentsList/view.jsx b/ui/component/commentsList/view.jsx index e92d74f5f..3d6a3c6be 100644 --- a/ui/component/commentsList/view.jsx +++ b/ui/component/commentsList/view.jsx @@ -193,7 +193,9 @@ function CommentList(props: Props) { actions={ <> + {!isFetchingComments && hasNoComments &&
    {__('Be the first to comment!')}
    } +
      {!isFetchingComments && comments && @@ -202,13 +204,13 @@ function CommentList(props: Props) { return ( + {(isFetchingComments || moreBelow) && (
      diff --git a/ui/component/commentsReplies/index.js b/ui/component/commentsReplies/index.js index a8e7de77c..5679ea25b 100644 --- a/ui/component/commentsReplies/index.js +++ b/ui/component/commentsReplies/index.js @@ -5,7 +5,7 @@ import { selectUserVerifiedEmail } from 'redux/selectors/user'; import CommentsReplies from './view'; const select = (state, props) => ({ - comments: makeSelectRepliesForParentId(props.topLevelId)(state), + comments: makeSelectRepliesForParentId(props.parentId)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state), commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, myChannels: selectMyChannelClaims(state), diff --git a/ui/component/commentsReplies/view.jsx b/ui/component/commentsReplies/view.jsx index ec4a88f4c..69ab12016 100644 --- a/ui/component/commentsReplies/view.jsx +++ b/ui/component/commentsReplies/view.jsx @@ -3,7 +3,6 @@ import * as ICONS from 'constants/icons'; import React from 'react'; import Comment from 'component/comment'; import Button from 'component/button'; -import CommentCreate from 'component/commentCreate'; type Props = { comments: Array, @@ -11,25 +10,13 @@ type Props = { claimIsMine: boolean, myChannels: ?Array, linkedComment?: Comment, - topLevelId: string, commentingEnabled: boolean, - topLevelIsReplying: boolean, - setTopLevelIsReplying: boolean => void, + threadDepth: number, }; function CommentsReplies(props: Props) { - const { - uri, - comments, - claimIsMine, - myChannels, - linkedComment, - topLevelId, - commentingEnabled, - topLevelIsReplying, - setTopLevelIsReplying, - } = props; - const [isExpanded, setExpanded] = React.useState(false); + const { uri, comments, claimIsMine, myChannels, linkedComment, commentingEnabled, threadDepth } = props; + const [isExpanded, setExpanded] = React.useState(true); const [start, setStart] = React.useState(0); const [end, setEnd] = React.useState(9); const sortedComments = comments ? [...comments].reverse() : []; @@ -63,7 +50,6 @@ function CommentsReplies(props: Props) { setStart(numberOfComments || 0); } setEnd(numberOfComments + 1); - setTopLevelIsReplying(false); } React.useEffect(() => { @@ -84,17 +70,13 @@ function CommentsReplies(props: Props) { const displayedComments = sortedComments.slice(start, end); return ( - (Boolean(numberOfComments) || topLevelIsReplying) && ( + Boolean(numberOfComments) && (
      - {Boolean(numberOfComments) && ( + {Boolean(numberOfComments) && !isExpanded && (
      )} - - {topLevelIsReplying && ( - handleCommentDone()} - onCancelReplying={() => setTopLevelIsReplying(false)} - /> - )}
      ) ); diff --git a/ui/scss/component/_comments.scss b/ui/scss/component/_comments.scss index 3c0a01a96..41a8587d5 100644 --- a/ui/scss/component/_comments.scss +++ b/ui/scss/component/_comments.scss @@ -1,5 +1,5 @@ -$thumbnailWidth: 2rem; -$thumbnailWidthSmall: 1.5rem; +$thumbnailWidth: 1.5rem; +$thumbnailWidthSmall: 0rem; .comments { list-style-type: none; @@ -9,7 +9,7 @@ $thumbnailWidthSmall: 1.5rem; .comments--replies { list-style-type: none; - margin-left: var(--spacing-m); + margin-left: var(--spacing-s); flex: 1; } @@ -29,8 +29,7 @@ $thumbnailWidthSmall: 1.5rem; } .comment__create--reply { - margin-top: var(--spacing-l); - margin-left: calc(#{$thumbnailWidth} + var(--spacing-m)); + margin-top: var(--spacing-m); position: relative; } @@ -81,11 +80,17 @@ $thumbnailWidthSmall: 1.5rem; } } +.comment--top-level { + &:not(:first-child) { + margin-top: var(--spacing-l); + } +} + .comment__threadline { @extend .button--alt; height: auto; align-self: stretch; - padding: 2px; + padding: 1px; border-radius: 3px; background-color: var(--color-comment-threadline); @@ -94,6 +99,10 @@ $thumbnailWidthSmall: 1.5rem; background-color: var(--color-comment-threadline-hover); border-color: var(--color-comment-threadline-hover); } + + @media (min-width: $breakpoint-small) { + padding: 2px; + } } .comment-new__label-wrapper { @@ -134,7 +143,6 @@ $thumbnailWidthSmall: 1.5rem; .comment__meta { display: flex; justify-content: space-between; - text-overflow: ellipsis; } .comment__meta-information { diff --git a/ui/scss/themes/dark.scss b/ui/scss/themes/dark.scss index edb30d60f..2799abb8c 100644 --- a/ui/scss/themes/dark.scss +++ b/ui/scss/themes/dark.scss @@ -57,6 +57,8 @@ --color-purchased-text: var(--color-gray-5); --color-comment-highlighted: #484734; --color-thumbnail-background: var(--color-gray-5); + --color-comment-threadline: #434b54; + --color-comment-threadline-hover: var(--color-gray-4); // Text --color-text: #d8d8d8; diff --git a/ui/scss/themes/light.scss b/ui/scss/themes/light.scss index a6fc157e3..667628496 100644 --- a/ui/scss/themes/light.scss +++ b/ui/scss/themes/light.scss @@ -26,9 +26,8 @@ --color-purchased-alt: #ffebc2; --color-purchased-text: var(--color-gray-5); --color-comment-highlighted: #fff2d9; - --color-comment-threadline: var(--color-gray-2); + --color-comment-threadline: var(--color-gray-1); --color-comment-threadline-hover: var(--color-gray-4); - --color-comment-threadline-border: var(--color-gray-2); --color-thumbnail-background: var(--color-gray-1); // Icons