Comments: enable 'enable_comments' flag

## Issue
Closes 6159 "Support Comments Enabled/Disabled for comment.List API"

## New behavior
- `disable-comments` tag will block the comments component entirely.
- `settings.commentsEnabled`:
  - When false, will pause comment fetching, posting and replying.
  - Any already-fetched comments will stay on screen (unless user reloads/F5).
This commit is contained in:
infinite-persistence 2021-06-03 13:57:50 +08:00 committed by jessopb
parent 95fa01a952
commit d6ac2c7954
9 changed files with 95 additions and 27 deletions

View file

@ -41,6 +41,7 @@ declare type CommentsState = {
fetchingModerationBlockList: boolean,
blockingByUri: {},
unBlockingByUri: {},
commentsDisabledChannelIds: Array<string>,
settingsByChannelId: { [string]: PerChannelSettings }, // ChannelID -> settings
fetchingSettings: boolean,
fetchingBlockedWords: boolean,

View file

@ -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),

View file

@ -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<any>,
commentsDisabledBySettings: boolean,
channels: ?Array<ChannelClaim>,
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 <Empty padded text={__('This channel has disabled comments on their page.')} />;
}
if (!hasChannels) {
return (
<div

View file

@ -5,6 +5,7 @@ import {
selectIsFetchingComments,
makeSelectTotalCommentsCountForUri,
selectOthersReactsById,
makeSelectCommentsDisabledForUri,
} from 'redux/selectors/comments';
import { doCommentList, doCommentReactList } from 'redux/actions/comments';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
@ -18,14 +19,15 @@ const select = (state, props) => ({
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);

View file

@ -16,6 +16,7 @@ import Empty from 'component/common/empty';
type Props = {
comments: Array<Comment>,
commentsDisabledBySettings: boolean,
fetchComments: (string) => void,
fetchReacts: (string) => Promise<any>,
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) {
<>
<CommentCreate uri={uri} justCommented={justCommented} />
{!isFetchingComments && hasNoComments && (
{!commentsDisabledBySettings && !isFetchingComments && hasNoComments && (
<Empty padded text={__('That was pretty deep. What do you think?')} />
)}

View file

@ -149,18 +149,18 @@ export default function SettingsCreatorPage(props: Props) {
)}
{!isBusy && !isDisabled && (
<>
{FEATURE_IS_READY && (
<Card
title={__('General')}
actions={
<>
<FormField
type="checkbox"
name="comments_enabled"
label={__('Enable comments for channel.')}
checked={commentsEnabled}
onChange={() => setSettings({ comments_enabled: !commentsEnabled })}
/>
<Card
title={__('General')}
actions={
<>
<FormField
type="checkbox"
name="comments_enabled"
label={__('Enable comments for channel.')}
checked={commentsEnabled}
onChange={() => setSettings({ comments_enabled: !commentsEnabled })}
/>
{FEATURE_IS_READY && (
<FormField
name="slow_mode_min_gap"
label={__('Minimum time gap in seconds for Slow Mode in livestream chat.')}
@ -171,10 +171,10 @@ export default function SettingsCreatorPage(props: Props) {
value={slowModeMinGap}
onChange={(e) => setSettings({ slow_mode_min_gap: e.target.value })}
/>
</>
}
/>
)}
)}
</>
}
/>
<Card
title={__('Filter')}
actions={

View file

@ -34,10 +34,15 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number =
type: ACTIONS.COMMENT_LIST_STARTED,
});
// Adding 'channel_id' and 'channel_name' enables "CreatorSettings > 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,
});
}
});
};
}

View file

@ -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,
};
},

View file

@ -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);
});