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:
parent
95fa01a952
commit
d6ac2c7954
9 changed files with 95 additions and 27 deletions
1
flow-typed/Comment.js
vendored
1
flow-typed/Comment.js
vendored
|
@ -41,6 +41,7 @@ declare type CommentsState = {
|
||||||
fetchingModerationBlockList: boolean,
|
fetchingModerationBlockList: boolean,
|
||||||
blockingByUri: {},
|
blockingByUri: {},
|
||||||
unBlockingByUri: {},
|
unBlockingByUri: {},
|
||||||
|
commentsDisabledChannelIds: Array<string>,
|
||||||
settingsByChannelId: { [string]: PerChannelSettings }, // ChannelID -> settings
|
settingsByChannelId: { [string]: PerChannelSettings }, // ChannelID -> settings
|
||||||
fetchingSettings: boolean,
|
fetchingSettings: boolean,
|
||||||
fetchingBlockedWords: boolean,
|
fetchingBlockedWords: boolean,
|
||||||
|
|
|
@ -10,11 +10,13 @@ import { doOpenModal, doSetActiveChannel } from 'redux/actions/app';
|
||||||
import { doCommentCreate } from 'redux/actions/comments';
|
import { doCommentCreate } from 'redux/actions/comments';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
import { makeSelectCommentsDisabledForUri } from 'redux/selectors/comments';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import { CommentCreate } from './view';
|
import { CommentCreate } from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
|
commentsDisabledBySettings: makeSelectCommentsDisabledForUri(props.uri)(state),
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
channels: selectMyChannelClaims(state),
|
channels: selectMyChannelClaims(state),
|
||||||
isFetchingChannels: selectFetchingMyChannels(state),
|
isFetchingChannels: selectFetchingMyChannels(state),
|
||||||
|
|
|
@ -15,6 +15,7 @@ import WalletTipAmountSelector from 'component/walletTipAmountSelector';
|
||||||
import CreditAmount from 'component/common/credit-amount';
|
import CreditAmount from 'component/common/credit-amount';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from 'component/uriIndicator';
|
||||||
|
import Empty from 'component/common/empty';
|
||||||
|
|
||||||
const COMMENT_SLOW_MODE_SECONDS = 5;
|
const COMMENT_SLOW_MODE_SECONDS = 5;
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
claim: StreamClaim,
|
claim: StreamClaim,
|
||||||
createComment: (string, string, string, ?string) => Promise<any>,
|
createComment: (string, string, string, ?string) => Promise<any>,
|
||||||
|
commentsDisabledBySettings: boolean,
|
||||||
channels: ?Array<ChannelClaim>,
|
channels: ?Array<ChannelClaim>,
|
||||||
onDoneReplying?: () => void,
|
onDoneReplying?: () => void,
|
||||||
onCancelReplying?: () => void,
|
onCancelReplying?: () => void,
|
||||||
|
@ -41,6 +43,7 @@ type Props = {
|
||||||
export function CommentCreate(props: Props) {
|
export function CommentCreate(props: Props) {
|
||||||
const {
|
const {
|
||||||
createComment,
|
createComment,
|
||||||
|
commentsDisabledBySettings,
|
||||||
claim,
|
claim,
|
||||||
channels,
|
channels,
|
||||||
onDoneReplying,
|
onDoneReplying,
|
||||||
|
@ -181,6 +184,10 @@ export function CommentCreate(props: Props) {
|
||||||
setAdvancedEditor(!advancedEditor);
|
setAdvancedEditor(!advancedEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (commentsDisabledBySettings) {
|
||||||
|
return <Empty padded text={__('This channel has disabled comments on their page.')} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasChannels) {
|
if (!hasChannels) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
selectIsFetchingComments,
|
selectIsFetchingComments,
|
||||||
makeSelectTotalCommentsCountForUri,
|
makeSelectTotalCommentsCountForUri,
|
||||||
selectOthersReactsById,
|
selectOthersReactsById,
|
||||||
|
makeSelectCommentsDisabledForUri,
|
||||||
} from 'redux/selectors/comments';
|
} from 'redux/selectors/comments';
|
||||||
import { doCommentList, doCommentReactList } from 'redux/actions/comments';
|
import { doCommentList, doCommentReactList } from 'redux/actions/comments';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
@ -18,14 +19,15 @@ const select = (state, props) => ({
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
isFetchingComments: selectIsFetchingComments(state),
|
isFetchingComments: selectIsFetchingComments(state),
|
||||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
|
commentsDisabledBySettings: makeSelectCommentsDisabledForUri(props.uri)(state),
|
||||||
fetchingChannels: selectFetchingMyChannels(state),
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
reactionsById: selectOthersReactsById(state),
|
reactionsById: selectOthersReactsById(state),
|
||||||
activeChannelId: selectActiveChannelId(state),
|
activeChannelId: selectActiveChannelId(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = (dispatch) => ({
|
||||||
fetchComments: uri => dispatch(doCommentList(uri)),
|
fetchComments: (uri) => dispatch(doCommentList(uri)),
|
||||||
fetchReacts: uri => dispatch(doCommentReactList(uri)),
|
fetchReacts: (uri) => dispatch(doCommentReactList(uri)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(CommentsList);
|
export default connect(select, perform)(CommentsList);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import Empty from 'component/common/empty';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
comments: Array<Comment>,
|
comments: Array<Comment>,
|
||||||
|
commentsDisabledBySettings: boolean,
|
||||||
fetchComments: (string) => void,
|
fetchComments: (string) => void,
|
||||||
fetchReacts: (string) => Promise<any>,
|
fetchReacts: (string) => Promise<any>,
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -35,6 +36,7 @@ function CommentList(props: Props) {
|
||||||
fetchReacts,
|
fetchReacts,
|
||||||
uri,
|
uri,
|
||||||
comments,
|
comments,
|
||||||
|
commentsDisabledBySettings,
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
myChannels,
|
myChannels,
|
||||||
isFetchingComments,
|
isFetchingComments,
|
||||||
|
@ -147,7 +149,9 @@ function CommentList(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to newest first for apps that don't have comment reactions
|
// 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
|
const displayedComments = readyToDisplayComments
|
||||||
? prepareComments(sortedComments, linkedComment).slice(start, end)
|
? prepareComments(sortedComments, linkedComment).slice(start, end)
|
||||||
: [];
|
: [];
|
||||||
|
@ -212,7 +216,7 @@ function CommentList(props: Props) {
|
||||||
<>
|
<>
|
||||||
<CommentCreate uri={uri} justCommented={justCommented} />
|
<CommentCreate uri={uri} justCommented={justCommented} />
|
||||||
|
|
||||||
{!isFetchingComments && hasNoComments && (
|
{!commentsDisabledBySettings && !isFetchingComments && hasNoComments && (
|
||||||
<Empty padded text={__('That was pretty deep. What do you think?')} />
|
<Empty padded text={__('That was pretty deep. What do you think?')} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -149,18 +149,18 @@ export default function SettingsCreatorPage(props: Props) {
|
||||||
)}
|
)}
|
||||||
{!isBusy && !isDisabled && (
|
{!isBusy && !isDisabled && (
|
||||||
<>
|
<>
|
||||||
{FEATURE_IS_READY && (
|
<Card
|
||||||
<Card
|
title={__('General')}
|
||||||
title={__('General')}
|
actions={
|
||||||
actions={
|
<>
|
||||||
<>
|
<FormField
|
||||||
<FormField
|
type="checkbox"
|
||||||
type="checkbox"
|
name="comments_enabled"
|
||||||
name="comments_enabled"
|
label={__('Enable comments for channel.')}
|
||||||
label={__('Enable comments for channel.')}
|
checked={commentsEnabled}
|
||||||
checked={commentsEnabled}
|
onChange={() => setSettings({ comments_enabled: !commentsEnabled })}
|
||||||
onChange={() => setSettings({ comments_enabled: !commentsEnabled })}
|
/>
|
||||||
/>
|
{FEATURE_IS_READY && (
|
||||||
<FormField
|
<FormField
|
||||||
name="slow_mode_min_gap"
|
name="slow_mode_min_gap"
|
||||||
label={__('Minimum time gap in seconds for Slow Mode in livestream chat.')}
|
label={__('Minimum time gap in seconds for Slow Mode in livestream chat.')}
|
||||||
|
@ -171,10 +171,10 @@ export default function SettingsCreatorPage(props: Props) {
|
||||||
value={slowModeMinGap}
|
value={slowModeMinGap}
|
||||||
onChange={(e) => setSettings({ slow_mode_min_gap: e.target.value })}
|
onChange={(e) => setSettings({ slow_mode_min_gap: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</>
|
)}
|
||||||
}
|
</>
|
||||||
/>
|
}
|
||||||
)}
|
/>
|
||||||
<Card
|
<Card
|
||||||
title={__('Filter')}
|
title={__('Filter')}
|
||||||
actions={
|
actions={
|
||||||
|
|
|
@ -34,10 +34,15 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number =
|
||||||
type: ACTIONS.COMMENT_LIST_STARTED,
|
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({
|
return Comments.comment_list({
|
||||||
page,
|
page,
|
||||||
claim_id: claimId,
|
claim_id: claimId,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
|
channel_id: authorChannelClaim ? authorChannelClaim.claim_id : undefined,
|
||||||
|
channel_name: authorChannelClaim ? authorChannelClaim.name : undefined,
|
||||||
})
|
})
|
||||||
.then((result: CommentListResponse) => {
|
.then((result: CommentListResponse) => {
|
||||||
const { items: comments } = result;
|
const { items: comments } = result;
|
||||||
|
@ -46,16 +51,27 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number =
|
||||||
data: {
|
data: {
|
||||||
comments,
|
comments,
|
||||||
claimId: claimId,
|
claimId: claimId,
|
||||||
|
authorClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined,
|
||||||
uri: uri,
|
uri: uri,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
dispatch({
|
if (error.message === 'comments are disabled by the creator') {
|
||||||
type: ACTIONS.COMMENT_LIST_FAILED,
|
dispatch({
|
||||||
data: error,
|
type: ACTIONS.COMMENT_LIST_COMPLETED,
|
||||||
});
|
data: {
|
||||||
|
authorClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.COMMENT_LIST_FAILED,
|
||||||
|
data: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ const defaultState: CommentsState = {
|
||||||
fetchingModerationBlockList: false,
|
fetchingModerationBlockList: false,
|
||||||
blockingByUri: {},
|
blockingByUri: {},
|
||||||
unBlockingByUri: {},
|
unBlockingByUri: {},
|
||||||
|
commentsDisabledChannelIds: [],
|
||||||
settingsByChannelId: {}, // ChannelId -> PerChannelSettings
|
settingsByChannelId: {}, // ChannelId -> PerChannelSettings
|
||||||
fetchingSettings: false,
|
fetchingSettings: false,
|
||||||
fetchingBlockedWords: false,
|
fetchingBlockedWords: false,
|
||||||
|
@ -167,7 +168,25 @@ export default handleActions(
|
||||||
[ACTIONS.COMMENT_LIST_STARTED]: (state) => ({ ...state, isLoading: true }),
|
[ACTIONS.COMMENT_LIST_STARTED]: (state) => ({ ...state, isLoading: true }),
|
||||||
|
|
||||||
[ACTIONS.COMMENT_LIST_COMPLETED]: (state: CommentsState, action: any) => {
|
[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 commentById = Object.assign({}, state.commentById);
|
||||||
const byId = Object.assign({}, state.byId);
|
const byId = Object.assign({}, state.byId);
|
||||||
|
@ -213,6 +232,7 @@ export default handleActions(
|
||||||
byId,
|
byId,
|
||||||
commentById,
|
commentById,
|
||||||
commentsByUri,
|
commentsByUri,
|
||||||
|
commentsDisabledChannelIds,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { createSelector } from 'reselect';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
|
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 || {};
|
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 selectIsFetchingComments = createSelector(selectState, (state) => state.isLoading);
|
||||||
export const selectIsPostingComment = createSelector(selectState, (state) => state.isCommenting);
|
export const selectIsPostingComment = createSelector(selectState, (state) => state.isCommenting);
|
||||||
export const selectIsFetchingReacts = createSelector(selectState, (state) => state.isFetchingReacts);
|
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 selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId);
|
||||||
export const selectModerationBlockList = createSelector(selectState, (state) =>
|
export const selectModerationBlockList = createSelector(selectState, (state) =>
|
||||||
state.moderationBlockList ? state.moderationBlockList.reverse() : []
|
state.moderationBlockList ? state.moderationBlockList.reverse() : []
|
||||||
|
@ -330,3 +334,15 @@ export const makeSelectSuperChatTotalAmountForUri = (uri: string) =>
|
||||||
|
|
||||||
return superChatData.totalAmount;
|
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);
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue