diff --git a/flow-typed/Comment.js b/flow-typed/Comment.js index 2de7ab930..d8388db72 100644 --- a/flow-typed/Comment.js +++ b/flow-typed/Comment.js @@ -41,6 +41,7 @@ declare type CommentsState = { fetchingModerationBlockList: boolean, blockingByUri: {}, unBlockingByUri: {}, + commentsDisabledChannelIds: Array, settingsByChannelId: { [string]: PerChannelSettings }, // ChannelID -> settings fetchingSettings: boolean, fetchingBlockedWords: boolean, diff --git a/ui/component/commentCreate/index.js b/ui/component/commentCreate/index.js index a6dc33941..33fb66833 100644 --- a/ui/component/commentCreate/index.js +++ b/ui/component/commentCreate/index.js @@ -10,11 +10,13 @@ import { doOpenModal, doSetActiveChannel } from 'redux/actions/app'; import { doCommentCreate } from 'redux/actions/comments'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectActiveChannelClaim } from 'redux/selectors/app'; +import { makeSelectCommentsDisabledForUri } from 'redux/selectors/comments'; import { doToast } from 'redux/actions/notifications'; import { CommentCreate } from './view'; const select = (state, props) => ({ commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, + commentsDisabledBySettings: makeSelectCommentsDisabledForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state), channels: selectMyChannelClaims(state), isFetchingChannels: selectFetchingMyChannels(state), diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 8d8c04f44..aa5443cbc 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -15,6 +15,7 @@ import WalletTipAmountSelector from 'component/walletTipAmountSelector'; import CreditAmount from 'component/common/credit-amount'; import ChannelThumbnail from 'component/channelThumbnail'; import UriIndicator from 'component/uriIndicator'; +import Empty from 'component/common/empty'; const COMMENT_SLOW_MODE_SECONDS = 5; @@ -22,6 +23,7 @@ type Props = { uri: string, claim: StreamClaim, createComment: (string, string, string, ?string) => Promise, + commentsDisabledBySettings: boolean, channels: ?Array, onDoneReplying?: () => void, onCancelReplying?: () => void, @@ -41,6 +43,7 @@ type Props = { export function CommentCreate(props: Props) { const { createComment, + commentsDisabledBySettings, claim, channels, onDoneReplying, @@ -181,6 +184,10 @@ export function CommentCreate(props: Props) { setAdvancedEditor(!advancedEditor); } + if (commentsDisabledBySettings) { + return ; + } + if (!hasChannels) { return (
({ claimIsMine: makeSelectClaimIsMine(props.uri)(state), isFetchingComments: selectIsFetchingComments(state), commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, + commentsDisabledBySettings: makeSelectCommentsDisabledForUri(props.uri)(state), fetchingChannels: selectFetchingMyChannels(state), reactionsById: selectOthersReactsById(state), activeChannelId: selectActiveChannelId(state), }); -const perform = dispatch => ({ - fetchComments: uri => dispatch(doCommentList(uri)), - fetchReacts: uri => dispatch(doCommentReactList(uri)), +const perform = (dispatch) => ({ + fetchComments: (uri) => dispatch(doCommentList(uri)), + fetchReacts: (uri) => dispatch(doCommentReactList(uri)), }); export default connect(select, perform)(CommentsList); diff --git a/ui/component/commentsList/view.jsx b/ui/component/commentsList/view.jsx index af003a5ce..9f679c779 100644 --- a/ui/component/commentsList/view.jsx +++ b/ui/component/commentsList/view.jsx @@ -16,6 +16,7 @@ import Empty from 'component/common/empty'; type Props = { comments: Array, + commentsDisabledBySettings: boolean, fetchComments: (string) => void, fetchReacts: (string) => Promise, uri: string, @@ -35,6 +36,7 @@ function CommentList(props: Props) { fetchReacts, uri, comments, + commentsDisabledBySettings, claimIsMine, myChannels, isFetchingComments, @@ -147,7 +149,9 @@ function CommentList(props: Props) { } // Default to newest first for apps that don't have comment reactions - const sortedComments = reactionsById ? sortComments({ comments, reactionsById, sort, isMyComment, justCommented }) : []; + const sortedComments = reactionsById + ? sortComments({ comments, reactionsById, sort, isMyComment, justCommented }) + : []; const displayedComments = readyToDisplayComments ? prepareComments(sortedComments, linkedComment).slice(start, end) : []; @@ -212,7 +216,7 @@ function CommentList(props: Props) { <> - {!isFetchingComments && hasNoComments && ( + {!commentsDisabledBySettings && !isFetchingComments && hasNoComments && ( )} diff --git a/ui/page/settingsCreator/view.jsx b/ui/page/settingsCreator/view.jsx index 0c763453c..f1dfceaed 100644 --- a/ui/page/settingsCreator/view.jsx +++ b/ui/page/settingsCreator/view.jsx @@ -149,18 +149,18 @@ export default function SettingsCreatorPage(props: Props) { )} {!isBusy && !isDisabled && ( <> - {FEATURE_IS_READY && ( - - setSettings({ comments_enabled: !commentsEnabled })} - /> + + setSettings({ comments_enabled: !commentsEnabled })} + /> + {FEATURE_IS_READY && ( setSettings({ slow_mode_min_gap: e.target.value })} /> - - } - /> - )} + )} + + } + /> commentsEnabled". + const authorChannelClaim = claim.value_type === 'channel' ? claim : claim.signing_channel; + return Comments.comment_list({ page, claim_id: claimId, page_size: pageSize, + channel_id: authorChannelClaim ? authorChannelClaim.claim_id : undefined, + channel_name: authorChannelClaim ? authorChannelClaim.name : undefined, }) .then((result: CommentListResponse) => { const { items: comments } = result; @@ -46,16 +51,27 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number = data: { comments, claimId: claimId, + authorClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined, uri: uri, }, }); return result; }) .catch((error) => { - dispatch({ - type: ACTIONS.COMMENT_LIST_FAILED, - data: error, - }); + if (error.message === 'comments are disabled by the creator') { + dispatch({ + type: ACTIONS.COMMENT_LIST_COMPLETED, + data: { + authorClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined, + disabled: true, + }, + }); + } else { + dispatch({ + type: ACTIONS.COMMENT_LIST_FAILED, + data: error, + }); + } }); }; } diff --git a/ui/redux/reducers/comments.js b/ui/redux/reducers/comments.js index e0c81cad3..9f9ff9488 100644 --- a/ui/redux/reducers/comments.js +++ b/ui/redux/reducers/comments.js @@ -24,6 +24,7 @@ const defaultState: CommentsState = { fetchingModerationBlockList: false, blockingByUri: {}, unBlockingByUri: {}, + commentsDisabledChannelIds: [], settingsByChannelId: {}, // ChannelId -> PerChannelSettings fetchingSettings: false, fetchingBlockedWords: false, @@ -167,7 +168,25 @@ export default handleActions( [ACTIONS.COMMENT_LIST_STARTED]: (state) => ({ ...state, isLoading: true }), [ACTIONS.COMMENT_LIST_COMPLETED]: (state: CommentsState, action: any) => { - const { comments, claimId, uri } = action.data; + const { comments, claimId, uri, disabled, authorClaimId } = action.data; + const commentsDisabledChannelIds = [...state.commentsDisabledChannelIds]; + + if (disabled) { + if (!commentsDisabledChannelIds.includes(authorClaimId)) { + commentsDisabledChannelIds.push(authorClaimId); + } + + return { + ...state, + commentsDisabledChannelIds, + isLoading: false, + }; + } else { + const index = commentsDisabledChannelIds.indexOf(authorClaimId); + if (index > -1) { + commentsDisabledChannelIds.splice(index, 1); + } + } const commentById = Object.assign({}, state.commentById); const byId = Object.assign({}, state.byId); @@ -213,6 +232,7 @@ export default handleActions( byId, commentById, commentsByUri, + commentsDisabledChannelIds, isLoading: false, }; }, diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index ee4590262..506563bbe 100644 --- a/ui/redux/selectors/comments.js +++ b/ui/redux/selectors/comments.js @@ -3,7 +3,7 @@ import { createSelector } from 'reselect'; import { selectMutedChannels } from 'redux/selectors/blocked'; import { selectShowMatureContent } from 'redux/selectors/settings'; import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc'; -import { selectClaimsById, isClaimNsfw, selectMyActiveClaims } from 'lbry-redux'; +import { selectClaimsById, isClaimNsfw, selectMyActiveClaims, makeSelectClaimForUri } from 'lbry-redux'; const selectState = (state) => state.comments || {}; @@ -11,6 +11,10 @@ export const selectCommentsById = createSelector(selectState, (state) => state.c export const selectIsFetchingComments = createSelector(selectState, (state) => state.isLoading); export const selectIsPostingComment = createSelector(selectState, (state) => state.isCommenting); export const selectIsFetchingReacts = createSelector(selectState, (state) => state.isFetchingReacts); +export const selectCommentsDisabledChannelIds = createSelector( + selectState, + (state) => state.commentsDisabledChannelIds +); export const selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId); export const selectModerationBlockList = createSelector(selectState, (state) => state.moderationBlockList ? state.moderationBlockList.reverse() : [] @@ -330,3 +334,15 @@ export const makeSelectSuperChatTotalAmountForUri = (uri: string) => return superChatData.totalAmount; }); + +export const makeSelectCommentsDisabledForUri = (uri: string) => + createSelector(selectCommentsDisabledChannelIds, makeSelectClaimForUri(uri), (commentsDisabledChannelIds, claim) => { + const channelClaim = !claim + ? null + : claim.value_type === 'channel' + ? claim + : claim.signing_channel && claim.is_channel_signature_valid + ? claim.signing_channel + : null; + return channelClaim && channelClaim.claim_id && commentsDisabledChannelIds.includes(channelClaim.claim_id); + });