diff --git a/ui/component/comment/index.js b/ui/component/comment/index.js index ffe06dd37..c7c5cc797 100644 --- a/ui/component/comment/index.js +++ b/ui/component/comment/index.js @@ -5,46 +5,47 @@ import { makeSelectThumbnailForUri, selectMyChannelClaims, } 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 { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectLinkedCommentAncestors, selectOthersReactsForComment, makeSelectTotalReplyPagesForParentId, } from 'redux/selectors/comments'; +import { doCommentUpdate, doCommentList } from 'redux/actions/comments'; +import { doSetPlayingUri } from 'redux/actions/content'; +import { doToast } from 'redux/actions/notifications'; +import { makeSelectChannelIsMuted } from 'redux/selectors/blocked'; import { selectActiveChannelClaim } from 'redux/selectors/app'; import { selectPlayingUri } from 'redux/selectors/content'; -import Comment from './view'; +import { selectUserVerifiedEmail } from 'redux/selectors/user'; +import CommentView from './view'; const select = (state, props) => { + const { channel_url: authorUri, comment_id: commentId } = props.comment; + const activeChannelClaim = selectActiveChannelClaim(state); const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id; - const reactionKey = activeChannelId ? `${props.commentId}:${activeChannelId}` : props.commentId; + const reactionKey = activeChannelId ? `${commentId}:${activeChannelId}` : commentId; return { + channelIsBlocked: authorUri && makeSelectChannelIsMuted(authorUri)(state), claim: makeSelectClaimForUri(props.uri)(state), - thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state), - channelIsBlocked: props.authorUri && makeSelectChannelIsMuted(props.authorUri)(state), - commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, - othersReacts: selectOthersReactsForComment(state, reactionKey), - activeChannelClaim, - myChannels: selectMyChannelClaims(state), - playingUri: selectPlayingUri(state), - stakedLevel: makeSelectStakedLevelForChannelUri(props.authorUri)(state), linkedCommentAncestors: selectLinkedCommentAncestors(state), - totalReplyPages: makeSelectTotalReplyPagesForParentId(props.commentId)(state), + myChannels: selectMyChannelClaims(state), + othersReacts: selectOthersReactsForComment(state, reactionKey), + playingUri: selectPlayingUri(state), + stakedLevel: makeSelectStakedLevelForChannelUri(authorUri)(state), + thumbnail: authorUri && makeSelectThumbnailForUri(authorUri)(state), + totalReplyPages: makeSelectTotalReplyPagesForParentId(commentId)(state), + userCanComment: selectUserVerifiedEmail(state), }; }; -const perform = (dispatch) => ({ +const perform = (dispatch, ownProps) => ({ 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)), + updateComment: (editedComment) => dispatch(doCommentUpdate(ownProps.comment.comment_id, editedComment)), + fetchReplies: (page, pageSize, sortBy) => + dispatch(doCommentList(ownProps.uri, ownProps.comment.comment_id, page, pageSize, sortBy)), doToast: (options) => dispatch(doToast(options)), }); -export default connect(select, perform)(Comment); +export default connect(select, perform)(CommentView); diff --git a/ui/component/comment/view.jsx b/ui/component/comment/view.jsx index e0ebeb2fa..9ad5f5b8c 100644 --- a/ui/component/comment/view.jsx +++ b/ui/component/comment/view.jsx @@ -1,120 +1,100 @@ // @flow -import * as ICONS from 'constants/icons'; -import * as PAGES from 'constants/pages'; -import * as KEYCODES from 'constants/keycodes'; -import { COMMENT_HIGHLIGHTED } from 'constants/classnames'; -import { SORT_BY, COMMENT_PAGE_SIZE_REPLIES } from 'constants/comment'; -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 { parseURI } from 'util/lbryURI'; -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 ChannelThumbnail from 'component/channelThumbnail'; +import 'scss/component/_comments.scss'; +import { ENABLE_COMMENT_REACTIONS } from 'config'; import { Menu, MenuButton } 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 { parseSticker } from 'util/comments'; +import { parseURI } from 'util/lbryURI'; +import { SORT_BY, COMMENT_PAGE_SIZE_REPLIES } from 'constants/comment'; import { useHistory } from 'react-router'; +import * as ICONS from 'constants/icons'; +import Button from 'component/button'; +import ChannelThumbnail from 'component/channelThumbnail'; +import classnames from 'classnames'; import CommentCreate from 'component/commentCreate'; import CommentMenuList from 'component/commentMenuList'; -import UriIndicator from 'component/uriIndicator'; +import CommentReactions from 'component/commentReactions'; +import CommentsReplies from 'component/commentsReplies'; import CreditAmount from 'component/common/credit-amount'; +import DateTime from 'component/dateTime'; +import Expandable from 'component/expandable'; +import Icon from 'component/common/icon'; +import MarkdownPreview from 'component/common/markdown-preview'; import OptimizedImage from 'component/optimizedImage'; -import { parseSticker } from 'util/comments'; +import React, { useEffect, useState } from 'react'; +import Tooltip from 'component/common/tooltip'; +import UriIndicator from 'component/uriIndicator'; const AUTO_EXPAND_ALL_REPLIES = false; - -type Props = { - 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, - fetchReplies: (string, string, number, number, number) => void, - totalReplyPages: number, - commentModBlock: (string) => void, - linkedCommentId?: string, - linkedCommentAncestors: { [string]: Array }, - myChannels: ?Array, - commentingEnabled: boolean, - doToast: ({ message: string }) => void, - isTopLevel?: boolean, - threadDepth: number, - hideActions?: boolean, - isPinned: boolean, - othersReacts: ?{ - like: number, - dislike: number, - }, - commentIdentityChannel: any, - activeChannelClaim: ?ChannelClaim, - playingUri: ?PlayingUri, - stakedLevel: number, - supportAmount: number, - numDirectReplies: number, - isModerator: boolean, - isGlobalMod: boolean, - isFiat: boolean, - supportDisabled: boolean, - setQuickReply: (any) => void, - quickReply: any, -}; - const LENGTH_TO_COLLAPSE = 300; -function Comment(props: Props) { +type Props = { + channelIsBlocked: boolean, // if the channel is blacklisted in the app + claim: StreamClaim, + claimIsMine: boolean, // if you control the claim which this comment was posted on + comment: Comment, + commentIdentityChannel: any, + hideActions?: boolean, + isTopLevel?: boolean, + linkedCommentAncestors: { [string]: Array }, + linkedCommentId?: string, + myChannels: ?Array, + othersReacts: ?{ like: number, dislike: number }, + playingUri: ?PlayingUri, + quickReply: any, + stakedLevel: number, + supportDisabled: boolean, + threadDepth: number, + totalReplyPages: number, + uri: string, + userCanComment: boolean, + clearPlayingUri: () => void, + commentModBlock: (string) => void, + fetchReplies: (number, number, string) => void, + setQuickReply: (any) => void, + updateComment: (string) => void, +}; + +function CommentView(props: Props) { const { - clearPlayingUri, - claim, - uri, - author, - authorUri, - timePosted, - message, channelIsBlocked, - commentIsMine, - commentId, - updateComment, - fetchReplies, - totalReplyPages, - linkedCommentId, - linkedCommentAncestors, - commentingEnabled, - myChannels, - doToast, - isTopLevel, - threadDepth, + claim, + comment, hideActions, - isPinned, + isTopLevel, + linkedCommentAncestors, + linkedCommentId, + myChannels, othersReacts, playingUri, - stakedLevel, - supportAmount, - numDirectReplies, - isModerator, - isGlobalMod, - isFiat, - supportDisabled, - setQuickReply, quickReply, + stakedLevel, + supportDisabled, + threadDepth, + totalReplyPages, + uri, + userCanComment, + clearPlayingUri, + fetchReplies, + setQuickReply, + updateComment, } = props; const { - push, + channel_id: authorId, + channel_url: authorUri, + comment_id: commentId, + comment: message, + is_fiat: isFiat, + is_global_mod: isGlobalMod, + is_moderator: isModerator, + is_pinned: isPinned, + replies: numDirectReplies, + support_amount: supportAmount, + } = comment; + const timePosted = comment.timestamp * 1000; + const commentIsMine = authorId && myChannels && myChannels.some(({ claim_id }) => claim_id === authorId); + + const { replace, location: { pathname, search }, } = useHistory(); @@ -129,79 +109,22 @@ function Comment(props: Props) { 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(showRepliesOnMount); const [page, setPage] = useState(showRepliesOnMount ? 1 : 0); - const [advancedEditor] = usePersistedState('comment-editor-mode', false); const [displayDeadComment, setDisplayDeadComment] = React.useState(false); - const hasChannels = myChannels && myChannels.length > 0; + 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 commentByContentOwner = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri; const stickerFromMessage = parseSticker(message); - let channelOwnerOfContent; + let contentOwnerChannel; try { - const { channelName } = parseURI(uri); - if (channelName) { - channelOwnerOfContent = channelName; - } + ({ channelName: contentOwnerChannel } = parseURI(uri)); } catch (e) {} - useEffect(() => { - 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 === KEYCODES.ESCAPE) { - setEditing(false); - } - }; - - window.addEventListener('keydown', handleEscape); - - // removes the listener so it doesn't cause problems elsewhere in the app - return () => { - window.removeEventListener('keydown', handleEscape); - }; - } - }, [author, authorUri, editedMessage, isEditing, setEditing]); - - useEffect(() => { - if (page > 0) { - fetchReplies(uri, commentId, page, COMMENT_PAGE_SIZE_REPLIES, SORT_BY.OLDEST); - } - }, [page, uri, commentId, fetchReplies]); - - function handleEditMessageChanged(event) { - setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value); - } - - function handleEditComment() { - if (playingUri && playingUri.source === 'comment') { - clearPlayingUri(); - } - setEditing(true); - } - - function handleSubmit() { - updateComment(commentId, editedMessage); - if (setQuickReply) setQuickReply({ ...quickReply, comment_id: commentId, comment: 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); - } - } - function handleTimeClick() { const urlParams = new URLSearchParams(search); urlParams.delete('lc'); @@ -221,10 +144,27 @@ function Comment(props: Props) { } }, []); + useEffect(() => { + if (page > 0) fetchReplies(page, COMMENT_PAGE_SIZE_REPLIES, SORT_BY.OLDEST); + }, [commentId, fetchReplies, page, uri]); + + const commentBadge = (label: string, className: string, icon: string) => ( + + + + + + ); + + const MarkdownWrapper = + editedMessage.length >= LENGTH_TO_COLLAPSE + ? ({ children }) => {children} + : ({ children }) => children; + return (
  • 0, })} @@ -233,48 +173,25 @@ function Comment(props: Props) {
    -
    - {authorUri ? ( - - ) : ( - - )} +
    +
    -
    +
    -
    - {isGlobalMod && ( - - - - - - )} +
    + {isModerator && commentBadge(__('Moderator'), 'commentBadge__mod', ICONS.BADGE_MOD)} + {isGlobalMod && commentBadge(__('Admin'), 'commentBadge__globalMod', ICONS.BADGE_MOD)} - {isModerator && ( - - - - - - )} - - {!author ? ( - {__('Anonymous')} - ) : ( - - )} +
    -
    +
    {isEditing ? ( -
    - -
    -
    - + { + if (editedMessage) { + updateComment(editedMessage); + if (setQuickReply) setQuickReply({ ...quickReply, comment_id: commentId, comment: editedMessage }); + setCommentValue(editedMessage); + } + setEditing(false); + }} + supportDisabled + /> ) : ( <> -
    +
    slimedToDeath && setDisplayDeadComment(true)} + > {slimedToDeath && !displayDeadComment ? ( -
    setDisplayDeadComment(true)} className="comment__dead"> - {__('This comment was slimed to death.')} -
    + <> + {__('This comment was slimed to death.')} + + ) : stickerFromMessage ? ( -
    - -
    - ) : editedMessage.length >= LENGTH_TO_COLLAPSE ? ( - + + ) : ( + - - ) : ( - + )}
    @@ -368,10 +279,10 @@ function Comment(props: Props) {
    {threadDepth !== 0 && (
    )} - {numDirectReplies > 0 && !showReplies && ( -
    -
    - )} - - {numDirectReplies > 0 && showReplies && ( -
    -
    - )} + button="link" + onClick={() => { + setShowReplies(true); + if (page === 0) setPage(1); + }} + icon={ICONS.DOWN} + /> +
    + ) : ( +
    +
    + ))} {isReplying && ( { - setReplying(false); - }} + onCancelReplying={() => setReplying(false)} supportDisabled={supportDisabled} /> )} @@ -446,4 +352,4 @@ function Comment(props: Props) { ); } -export default Comment; +export default CommentView; diff --git a/ui/component/commentCreate/index.js b/ui/component/commentCreate/index.js index 84ac3e3ec..6505e0869 100644 --- a/ui/component/commentCreate/index.js +++ b/ui/component/commentCreate/index.js @@ -21,7 +21,7 @@ const select = (state, props) => ({ claimIsMine: makeSelectClaimIsMine(props.uri)(state), isFetchingChannels: selectFetchingMyChannels(state), settingsByChannelId: selectSettingsByChannelId(state), - supportDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_SUPPORT_TAG)(state), + supportDisabled: props.supportDisabled || makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_SUPPORT_TAG)(state), }); const perform = (dispatch, ownProps) => ({ diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index e177cbe3c..ee63c4e97 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -45,7 +45,9 @@ type Props = { channels: ?Array, claim: StreamClaim, claimIsMine: boolean, + editedMessage?: string, embed?: boolean, + isEdit?: boolean, isFetchingChannels: boolean, isNested: boolean, isReply: boolean, @@ -60,6 +62,7 @@ type Props = { doToast: ({ message: string }) => void, fetchComment: (commentId: string) => Promise, onCancelReplying?: () => void, + onDoneEditing?: (editedMessage?: string) => void, onDoneReplying?: () => void, sendCashTip: (TipParams, UserParams, string, ?string, (any) => void) => string, sendTip: ({}, (any) => void, (any) => void) => void, @@ -74,7 +77,9 @@ export function CommentCreate(props: Props) { channels, claim, claimIsMine, + editedMessage, embed, + isEdit, isFetchingChannels, isNested, isReply, @@ -91,6 +96,7 @@ export function CommentCreate(props: Props) { onCancelReplying, onDoneReplying, sendCashTip, + onDoneEditing, sendTip, setQuickReply, } = props; @@ -114,7 +120,7 @@ export function CommentCreate(props: Props) { const [selectedSticker, setSelectedSticker] = React.useState(); const [tipAmount, setTipAmount] = React.useState(1); const [convertedAmount, setConvertedAmount] = React.useState(); - const [commentValue, setCommentValue] = React.useState(''); + const [commentValue, setCommentValue] = React.useState(editedMessage || ''); const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false); const [stickerSelector, setStickerSelector] = React.useState(); const [activeTab, setActiveTab] = React.useState(''); @@ -474,16 +480,18 @@ export function CommentCreate(props: Props) { - {!livestream && ( -
    {isReply ? __('Replying as ') : __('Comment as ')}
    - )} - - + !isEdit && ( + + {!livestream && ( +
    {isReply ? __('Replying as ') : __('Comment as ')}
    + )} + +
    + ) } quickActionLabel={ !SIMPLE_SITE && (isReply ? undefined : advancedEditor ? __('Simple Editor') : __('Advanced Editor')) @@ -577,16 +585,17 @@ export function CommentCreate(props: Props) { disabled={disabled || stickerSelector} type="submit" label={ - isReply - ? isSubmitting - ? __('Replying...') - : __('Reply') - : isSubmitting - ? __('Commenting...') - : __('Comment --[button to submit something]--') + (isEdit && __('Done')) || + (isSubmitting + ? (isReply && __('Replying...')) || __('Commenting...') + : (isReply && __('Reply')) || __('Comment --[button to submit something]--')) } requiresAuth - onClick={() => activeChannelClaim && commentValue.length && handleCreateComment()} + onClick={() => + activeChannelClaim && + commentValue.length && + (isEdit && editedMessage && onDoneEditing ? onDoneEditing() : handleCreateComment()) + } /> ) )} @@ -629,7 +638,8 @@ export function CommentCreate(props: Props) { isReviewingSupportComment || stickerSelector || isReviewingStickerComment || - (isReply && !minTip)) && ( + (isReply && !minTip) || + isEdit) && (
    )} + {isExpanded && fetchedReplies && hasMore && (
    )} + {(isFetchingByParentId[parentId] || isResolvingReplies || !canDisplayComments) && ( -
    -
    - -
    +
    +
    )}
    diff --git a/ui/component/common/credit-amount.jsx b/ui/component/common/credit-amount.jsx index 99136699e..84c5dd7c8 100644 --- a/ui/component/common/credit-amount.jsx +++ b/ui/component/common/credit-amount.jsx @@ -92,8 +92,8 @@ class CreditAmount extends React.PureComponent { {amountText} diff --git a/ui/component/common/wait-until-on-page.jsx b/ui/component/common/wait-until-on-page.jsx index 032dd8b90..ce645ad4a 100644 --- a/ui/component/common/wait-until-on-page.jsx +++ b/ui/component/common/wait-until-on-page.jsx @@ -1,17 +1,10 @@ // @flow -import React from 'react'; +import { scaleToDevicePixelRatio } from 'util/scale'; import debounce from 'util/debounce'; +import React from 'react'; const DEBOUNCE_SCROLL_HANDLER_MS = 50; -function scaleToDevicePixelRatio(value) { - const devicePixelRatio = window.devicePixelRatio || 1.0; - if (devicePixelRatio < 1.0) { - return Math.ceil(value / devicePixelRatio); - } - return Math.ceil(value * devicePixelRatio); -} - type Props = { children: any, skipWait?: boolean, diff --git a/ui/component/livestreamComment/index.js b/ui/component/livestreamComment/index.js index b18a96a36..f8f35a043 100644 --- a/ui/component/livestreamComment/index.js +++ b/ui/component/livestreamComment/index.js @@ -1,10 +1,11 @@ import { connect } from 'react-redux'; -import { makeSelectStakedLevelForChannelUri, selectClaimForUri } from 'redux/selectors/claims'; +import { makeSelectStakedLevelForChannelUri, selectClaimForUri, selectMyChannelClaims } from 'redux/selectors/claims'; import LivestreamComment from './view'; const select = (state, props) => ({ claim: selectClaimForUri(state, props.uri), stakedLevel: makeSelectStakedLevelForChannelUri(props.authorUri)(state), + myChannels: selectMyChannelClaims(state), }); export default connect(select)(LivestreamComment); diff --git a/ui/component/livestreamComment/view.jsx b/ui/component/livestreamComment/view.jsx index f817773db..706bcd059 100644 --- a/ui/component/livestreamComment/view.jsx +++ b/ui/component/livestreamComment/view.jsx @@ -1,102 +1,87 @@ // @flow -import * as ICONS from 'constants/icons'; -import React from 'react'; -import { parseURI } from 'util/lbryURI'; -import MarkdownPreview from 'component/common/markdown-preview'; -import Tooltip from 'component/common/tooltip'; -import ChannelThumbnail from 'component/channelThumbnail'; +import 'scss/component/_livestream-comment.scss'; import { Menu, MenuButton } from '@reach/menu-button'; -import Icon from 'component/common/icon'; +import { parseSticker } from 'util/comments'; +import { parseURI } from 'util/lbryURI'; +import * as ICONS from 'constants/icons'; +import Button from 'component/button'; +import ChannelThumbnail from 'component/channelThumbnail'; import classnames from 'classnames'; import CommentMenuList from 'component/commentMenuList'; -import Button from 'component/button'; import CreditAmount from 'component/common/credit-amount'; +import Icon from 'component/common/icon'; +import MarkdownPreview from 'component/common/markdown-preview'; import OptimizedImage from 'component/optimizedImage'; -import { parseSticker } from 'util/comments'; +import React from 'react'; +import Tooltip from 'component/common/tooltip'; type Props = { - uri: string, claim: StreamClaim, - authorUri: string, - commentId: string, - message: string, - commentIsMine: boolean, + comment: Comment, + myChannels: ?Array, stakedLevel: number, - supportAmount: number, - isModerator: boolean, - isGlobalMod: boolean, - isFiat: boolean, - isPinned: boolean, + uri: string, }; function LivestreamComment(props: Props) { - const { - claim, - uri, - authorUri, - message, - commentIsMine, - commentId, - stakedLevel, - supportAmount, - isModerator, - isGlobalMod, - isFiat, - isPinned, - } = props; + const { claim, comment, myChannels, stakedLevel, uri } = props; - const commentByOwnerOfContent = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri; - const { claimName } = parseURI(authorUri); + const { + channel_url: authorUri, + channel_id: authorId, + comment_id: commentId, + comment: message, + is_fiat: isFiat, + is_global_mod: isGlobalMod, + is_moderator: isModerator, + is_pinned: isPinned, + support_amount: supportAmount, + } = comment; + const commentIsMine = authorId && myChannels && myChannels.some(({ claim_id }) => claim_id === authorId); + + const commentByContentOwner = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri; const stickerFromMessage = parseSticker(message); + let claimName; + try { + authorUri && ({ claimName } = parseURI(authorUri)); + } catch (e) {} + + const commentBadge = (label: string, icon: string, className?: string) => ( + + + + + + ); return (
  • 0, - 'livestream-comment--sticker': Boolean(stickerFromMessage), + className={classnames('livestreamComment', { + 'livestreamComment--superchat': supportAmount > 0, + 'livestreamComment--sticker': Boolean(stickerFromMessage), })} > {supportAmount > 0 && ( -
    -
    - +
    +
    +
    )} -
    +
    {(supportAmount > 0 || Boolean(stickerFromMessage)) && }
    - {isGlobalMod && ( - - - - - - )} - - {isModerator && ( - - - - - - )} - - {commentByOwnerOfContent && ( - - - - - - )} + {isGlobalMod && commentBadge(__('Admin'), ICONS.BADGE_MOD, 'comment__badge--globalMod')} + {isModerator && commentBadge(__('Moderator'), ICONS.BADGE_MOD, 'comment__badge--mod')} + {commentByContentOwner && commentBadge(__('Streamer'), ICONS.BADGE_STREAMER)}
    -
    +
    diff --git a/ui/component/livestreamComments/index.js b/ui/component/livestreamComments/index.js index e7b3d263d..aeb167f4b 100644 --- a/ui/component/livestreamComments/index.js +++ b/ui/component/livestreamComments/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { selectClaimForUri, selectMyChannelClaims } from 'redux/selectors/claims'; +import { selectClaimForUri } from 'redux/selectors/claims'; import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket'; import { doCommentList, doSuperChatList } from 'redux/actions/comments'; import { @@ -20,7 +20,6 @@ const select = (state, props) => ({ fetchingComments: selectIsFetchingComments(state), superChats: makeSelectSuperChatsForUri(props.uri)(state), superChatsTotalAmount: makeSelectSuperChatTotalAmountForUri(props.uri)(state), - myChannels: selectMyChannelClaims(state), }); export default connect(select, { diff --git a/ui/component/livestreamComments/view.jsx b/ui/component/livestreamComments/view.jsx index 33e22cdfc..20e0b42f8 100644 --- a/ui/component/livestreamComments/view.jsx +++ b/ui/component/livestreamComments/view.jsx @@ -1,32 +1,32 @@ // @flow -import React from 'react'; -import classnames from 'classnames'; -import Spinner from 'component/spinner'; -import CommentCreate from 'component/commentCreate'; -import LivestreamComment from 'component/livestreamComment'; -import Button from 'component/button'; -import UriIndicator from 'component/uriIndicator'; -import CreditAmount from 'component/common/credit-amount'; -import ChannelThumbnail from 'component/channelThumbnail'; -import Tooltip from 'component/common/tooltip'; -import * as ICONS from 'constants/icons'; -import OptimizedImage from 'component/optimizedImage'; +import 'scss/component/_livestream-comments.scss'; import { parseSticker } from 'util/comments'; +import * as ICONS from 'constants/icons'; +import Button from 'component/button'; +import ChannelThumbnail from 'component/channelThumbnail'; +import classnames from 'classnames'; +import CommentCreate from 'component/commentCreate'; +import CreditAmount from 'component/common/credit-amount'; +import LivestreamComment from 'component/livestreamComment'; +import OptimizedImage from 'component/optimizedImage'; +import React from 'react'; +import Spinner from 'component/spinner'; +import Tooltip from 'component/common/tooltip'; +import UriIndicator from 'component/uriIndicator'; type Props = { - uri: string, - claim: ?StreamClaim, activeViewers: number, + claim: ?StreamClaim, + comments: Array, embed?: boolean, + fetchingComments: boolean, + pinnedComments: Array, + superChats: Array, + uri: string, + doCommentList: (string, string, number, number) => void, doCommentSocketConnect: (string, string) => void, doCommentSocketDisconnect: (string) => void, - doCommentList: (string, string, number, number) => void, - comments: Array, - pinnedComments: Array, - fetchingComments: boolean, doSuperChatList: (string) => void, - superChats: Array, - myChannels: ?Array, }; const VIEW_MODE_CHAT = 'view_chat'; @@ -36,17 +36,16 @@ const COMMENT_SCROLL_TIMEOUT = 25; export default function LivestreamComments(props: Props) { const { claim, - uri, + comments: commentsByChronologicalOrder, embed, + fetchingComments, + pinnedComments, + superChats: superChatsByTipAmount, + uri, + doCommentList, doCommentSocketConnect, doCommentSocketDisconnect, - comments: commentsByChronologicalOrder, - pinnedComments, - doCommentList, - fetchingComments, doSuperChatList, - myChannels, - superChats: superChatsByTipAmount, } = props; let superChatsFiatAmount, superChatsLBCAmount, superChatsTotalAmount, hasSuperChats; @@ -55,22 +54,28 @@ export default function LivestreamComments(props: Props) { const [viewMode, setViewMode] = React.useState(VIEW_MODE_CHAT); const [scrollPos, setScrollPos] = React.useState(0); const [showPinned, setShowPinned] = React.useState(true); + const claimId = claim && claim.claim_id; const commentsLength = commentsByChronologicalOrder && commentsByChronologicalOrder.length; - // which kind of superchat to display, either const commentsToDisplay = viewMode === VIEW_MODE_CHAT ? commentsByChronologicalOrder : superChatsByTipAmount; const stickerSuperChats = superChatsByTipAmount && superChatsByTipAmount.filter(({ comment }) => Boolean(parseSticker(comment))); const discussionElement = document.querySelector('.livestream__comments'); - const pinnedComment = pinnedComments.length > 0 ? pinnedComments[0] : null; + let superChatsReversed; + // array of superchats organized by fiat or not first, then support amount + if (superChatsByTipAmount) { + const clonedSuperchats = JSON.parse(JSON.stringify(superChatsByTipAmount)); + + // for top to bottom display, oldest superchat on top most recent on bottom + superChatsReversed = clonedSuperchats.sort((a, b) => b.timestamp - a.timestamp); + } + const restoreScrollPos = React.useCallback(() => { - if (discussionElement) { - discussionElement.scrollTop = 0; - } + if (discussionElement) discussionElement.scrollTop = 0; }, [discussionElement]); React.useEffect(() => { @@ -139,75 +144,41 @@ export default function LivestreamComments(props: Props) { hasSuperChats = (superChatsTotalAmount || 0) > 0; } - let superChatsReversed; - // array of superchats organized by fiat or not first, then support amount - if (superChatsByTipAmount) { - const clonedSuperchats = JSON.parse(JSON.stringify(superChatsByTipAmount)); - - // for top to bottom display, oldest superchat on top most recent on bottom - superChatsReversed = clonedSuperchats.sort((a, b) => { - return b.timestamp - a.timestamp; - }); - } - - // 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; - } - - if (!claim) { - return null; - } - function getStickerUrl(comment: string) { const stickerFromComment = parseSticker(comment); return stickerFromComment && stickerFromComment.url; } - return ( + const getChatContentToggle = (toggleMode: string, label: any) => ( +
    )}
    @@ -217,10 +188,10 @@ export default function LivestreamComments(props: Props) {
    )} -
    +
    {viewMode === VIEW_MODE_CHAT && superChatsByTipAmount && hasSuperChats && ( -
    -
    +
    +
    {superChatsByTipAmount.map((superChat: Comment) => { const isSticker = stickerSuperChats && stickerSuperChats.includes(superChat); @@ -230,28 +201,28 @@ export default function LivestreamComments(props: Props) { return ( -
    -
    +
    +
    -
    +
    {stickerSuperChats.includes(superChat) && getStickerUrl(superChat.comment) && ( -
    +
    )} @@ -265,20 +236,8 @@ export default function LivestreamComments(props: Props) { )} {pinnedComment && showPinned && viewMode === VIEW_MODE_CHAT && ( -
    - +
    +
    ) : ( @@ -333,14 +270,14 @@ export default function LivestreamComments(props: Props) { {scrollPos < 0 && viewMode === VIEW_MODE_CHAT && (