From e9a2f4489927afb8caed69069b840b06e5158428 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Thu, 29 Jul 2021 22:53:36 +0800 Subject: [PATCH] Commentron: incorporate 'setting.Get' into 'doFetchCreatorSettings' ## General - `setting.List`: returns full creator settings. Requires signature (i.e. you own the channel) - `setting.Get`: returns a public subset of the creator settings. No signature required, and it is mainly used by the GUI to determine the constraints of a channel (e.g. comments enabled? min tip requirements? etc.). Does not include private settings like "blocked words list". `doFetchCreatorSettings` will handle both of these. Clients that uses the stashed results (`settingsByChannelId`) just needs to be aware the result might not contain everything, depending on whether you own the channel or not. ## Misc Related Changes - Finally fix the reducer for COMMENT_FETCH_SETTINGS_COMPLETED to not purge the data on each call. - Change `doFetchCreatorSettings` to operate on a single channel instead of multiple. We ended up not using the multple mode anyway, so it was wasteful code trying to batch the promises. - `commentsDisabledChannelIds` is no longer needed. Previously, this was created just to differentiate between Creator (full) and Channel (subset) settings. It's cleaner to just use one object, so eliminated this. - Remove unused 'commentingEnabled'. ## Aside - There are now 2 ways to know if a channel has disabled comments: (1) from `comment.list` and `setting.Get|List`. Both of them updates `settingsByChannelId`, so it'll still be a single place for the GUI to check against. --- flow-typed/Comment.js | 17 +++- ui/comments.js | 1 + ui/component/commentCreate/index.js | 20 +++- ui/component/commentCreate/view.jsx | 17 +++- ui/component/commentsList/index.js | 14 ++- ui/component/commentsList/view.jsx | 11 ++- ui/page/settingsCreator/index.js | 2 +- ui/page/settingsCreator/view.jsx | 59 ++++++++---- ui/redux/actions/comments.js | 97 ++++++------------- ui/redux/reducers/comments.js | 144 +++++++++++++--------------- ui/redux/selectors/comments.js | 18 +--- ui/util/claim.js | 11 +++ 12 files changed, 212 insertions(+), 199 deletions(-) create mode 100644 ui/util/claim.js diff --git a/flow-typed/Comment.js b/flow-typed/Comment.js index 17d8eac53..551699e3e 100644 --- a/flow-typed/Comment.js +++ b/flow-typed/Comment.js @@ -58,7 +58,6 @@ declare type CommentsState = { blockingByUri: {}, unBlockingByUri: {}, togglingForDelegatorMap: {[string]: Array}, // {"blockedUri": ["delegatorUri1", ""delegatorUri2", ...]} - commentsDisabledChannelIds: Array, settingsByChannelId: { [string]: PerChannelSettings }, // ChannelID -> settings fetchingSettings: boolean, fetchingBlockedWords: boolean, @@ -222,10 +221,20 @@ declare type ModerationAmIParams = { }; declare type SettingsParams = { - channel_name: string, + channel_name?: string, channel_id: string, - signature: string, - signing_ts: string, + signature?: string, + signing_ts?: string, +}; + +declare type SettingsResponse = { + words?: string, + comments_enabled: boolean, + min_tip_amount_comment: number, + min_tip_amount_super_chat: number, + slow_mode_min_gap: number, + curse_jar_amount: number, + filters_enabled?: boolean, }; declare type UpdateSettingsParams = { diff --git a/ui/comments.js b/ui/comments.js index 9069e49d3..58593895f 100644 --- a/ui/comments.js +++ b/ui/comments.js @@ -34,6 +34,7 @@ const Comments = { setting_unblock_word: (params: BlockWordParams) => fetchCommentsApi('setting.UnBlockWord', params), setting_list_blocked_words: (params: SettingsParams) => fetchCommentsApi('setting.ListBlockedWords', params), setting_update: (params: UpdateSettingsParams) => fetchCommentsApi('setting.Update', params), + setting_get: (params: SettingsParams) => fetchCommentsApi('setting.Get', params), super_list: (params: SuperListParams) => fetchCommentsApi('comment.SuperChatList', params), }; diff --git a/ui/component/commentCreate/index.js b/ui/component/commentCreate/index.js index a02de664f..66bb84f01 100644 --- a/ui/component/commentCreate/index.js +++ b/ui/component/commentCreate/index.js @@ -7,30 +7,42 @@ import { doSendTip, } from 'lbry-redux'; import { doOpenModal, doSetActiveChannel } from 'redux/actions/app'; -import { doCommentCreate } from 'redux/actions/comments'; +import { doCommentCreate, doFetchCreatorSettings } from 'redux/actions/comments'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectActiveChannelClaim } from 'redux/selectors/app'; -import { makeSelectCommentsDisabledForUri } from 'redux/selectors/comments'; +import { selectSettingsByChannelId } from 'redux/selectors/comments'; import { CommentCreate } from './view'; import { doToast } from 'redux/actions/notifications'; 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), activeChannelClaim: selectActiveChannelClaim(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state), + settingsByChannelId: selectSettingsByChannelId(state), }); const perform = (dispatch, ownProps) => ({ createComment: (comment, claimId, parentId, txid, payment_intent_id, environment) => - dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, ownProps.livestream, txid, payment_intent_id, environment)), + dispatch( + doCommentCreate( + comment, + claimId, + parentId, + ownProps.uri, + ownProps.livestream, + txid, + payment_intent_id, + environment + ) + ), openModal: (modal, props) => dispatch(doOpenModal(modal, props)), setActiveChannel: (claimId) => dispatch(doSetActiveChannel(claimId)), sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)), doToast: (options) => dispatch(doToast(options)), + doFetchCreatorSettings: (channelClaimId) => dispatch(doFetchCreatorSettings(channelClaimId)), }); export default connect(select, perform)(CommentCreate); diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 238187cb4..0d7646fc0 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -17,6 +17,7 @@ import ChannelThumbnail from 'component/channelThumbnail'; import UriIndicator from 'component/uriIndicator'; import Empty from 'component/common/empty'; import { Lbryio } from 'lbryinc'; +import { getChannelIdFromClaim } from 'util/claim'; let stripeEnvironment = 'test'; // if the key contains pk_live it's a live key @@ -32,7 +33,6 @@ type Props = { uri: string, claim: StreamClaim, createComment: (string, string, string, ?string, ?string, ?string) => Promise, - commentsDisabledBySettings: boolean, channels: ?Array, onDoneReplying?: () => void, onCancelReplying?: () => void, @@ -50,12 +50,13 @@ type Props = { sendTip: ({}, (any) => void, (any) => void) => void, doToast: ({ message: string }) => void, disabled: boolean, + doFetchCreatorSettings: (channelId: string) => void, + settingsByChannelId: { [channelId: string]: PerChannelSettings }, }; export function CommentCreate(props: Props) { const { createComment, - commentsDisabledBySettings, claim, channels, onDoneReplying, @@ -71,6 +72,8 @@ export function CommentCreate(props: Props) { claimIsMine, sendTip, doToast, + doFetchCreatorSettings, + settingsByChannelId, } = props; const buttonRef: ElementRef = React.useRef(); const { @@ -92,6 +95,8 @@ export function CommentCreate(props: Props) { const [tipError, setTipError] = React.useState(); const disabled = isSubmitting || !activeChannelClaim || !commentValue.length; const [shouldDisableReviewButton, setShouldDisableReviewButton] = React.useState(); + const channelId = getChannelIdFromClaim(claim); + const channelSettings = channelId ? settingsByChannelId[channelId] : undefined; function handleCommentChange(event) { let commentValue; @@ -275,7 +280,13 @@ export function CommentCreate(props: Props) { setAdvancedEditor(!advancedEditor); } - if (commentsDisabledBySettings) { + React.useEffect(() => { + if (!channelSettings && channelId) { + doFetchCreatorSettings(channelId); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + if (channelSettings && !channelSettings.comments_enabled) { return ; } diff --git a/ui/component/commentsList/index.js b/ui/component/commentsList/index.js index dac16d16d..5d54af205 100644 --- a/ui/component/commentsList/index.js +++ b/ui/component/commentsList/index.js @@ -1,5 +1,10 @@ import { connect } from 'react-redux'; -import { makeSelectClaimIsMine, selectFetchingMyChannels, selectMyChannelClaims } from 'lbry-redux'; +import { + makeSelectClaimForUri, + makeSelectClaimIsMine, + selectFetchingMyChannels, + selectMyChannelClaims, +} from 'lbry-redux'; import { makeSelectTopLevelCommentsForUri, makeSelectTopLevelTotalPagesForUri, @@ -7,12 +12,11 @@ import { selectIsFetchingReacts, makeSelectTotalCommentsCountForUri, selectOthersReactsById, - makeSelectCommentsDisabledForUri, selectMyReactionsByCommentId, makeSelectCommentIdsForUri, + selectSettingsByChannelId, } from 'redux/selectors/comments'; import { doCommentReset, doCommentList, doCommentById, doCommentReactList } from 'redux/actions/comments'; -import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectActiveChannelClaim } from 'redux/selectors/app'; import CommentsList from './view'; @@ -24,12 +28,12 @@ const select = (state, props) => { topLevelComments: makeSelectTopLevelCommentsForUri(props.uri)(state), topLevelTotalPages: makeSelectTopLevelTotalPagesForUri(props.uri)(state), totalComments: makeSelectTotalCommentsCountForUri(props.uri)(state), + claim: makeSelectClaimForUri(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state), isFetchingComments: selectIsFetchingComments(state), isFetchingReacts: selectIsFetchingReacts(state), - commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true, - commentsDisabledBySettings: makeSelectCommentsDisabledForUri(props.uri)(state), fetchingChannels: selectFetchingMyChannels(state), + settingsByChannelId: selectSettingsByChannelId(state), myReactsByCommentId: selectMyReactionsByCommentId(state), othersReactsById: selectOthersReactsById(state), activeChannelId: activeChannelClaim && activeChannelClaim.claim_id, diff --git a/ui/component/commentsList/view.jsx b/ui/component/commentsList/view.jsx index 01075cf1c..3c232441a 100644 --- a/ui/component/commentsList/view.jsx +++ b/ui/component/commentsList/view.jsx @@ -14,6 +14,7 @@ import { ENABLE_COMMENT_REACTIONS } from 'config'; import Empty from 'component/common/empty'; import debounce from 'util/debounce'; import { useIsMobile } from 'effects/use-screensize'; +import { getChannelIdFromClaim } from 'util/claim'; const DEBOUNCE_SCROLL_HANDLER_MS = 200; @@ -29,12 +30,12 @@ type Props = { allCommentIds: any, topLevelComments: Array, topLevelTotalPages: number, - commentsDisabledBySettings: boolean, fetchTopLevelComments: (string, number, number, number) => void, fetchComment: (string) => void, fetchReacts: (Array) => Promise, resetComments: (string) => void, uri: string, + claim: ?Claim, claimIsMine: boolean, myChannels: ?Array, isFetchingComments: boolean, @@ -45,6 +46,7 @@ type Props = { myReactsByCommentId: ?{ [string]: Array }, // "CommentId:MyChannelId" -> reaction array (note the ID concatenation) othersReactsById: ?{ [string]: { [REACTION_TYPES.LIKE | REACTION_TYPES.DISLIKE]: number } }, activeChannelId: ?string, + settingsByChannelId: { [channelId: string]: PerChannelSettings }, }; function CommentList(props: Props) { @@ -57,7 +59,7 @@ function CommentList(props: Props) { uri, topLevelComments, topLevelTotalPages, - commentsDisabledBySettings, + claim, claimIsMine, myChannels, isFetchingComments, @@ -68,6 +70,7 @@ function CommentList(props: Props) { myReactsByCommentId, othersReactsById, activeChannelId, + settingsByChannelId, } = props; const commentRef = React.useRef(); @@ -78,6 +81,8 @@ function CommentList(props: Props) { const isMobile = useIsMobile(); const [expandedComments, setExpandedComments] = React.useState(!isMobile); const totalFetchedComments = allCommentIds ? allCommentIds.length : 0; + const channelId = getChannelIdFromClaim(claim); + const channelSettings = channelId ? settingsByChannelId[channelId] : undefined; // Display comments immediately if not fetching reactions // If not, wait to show comments until reactions are fetched @@ -279,7 +284,7 @@ function CommentList(props: Props) { <> - {!commentsDisabledBySettings && !isFetchingComments && hasNoComments && ( + {channelSettings && channelSettings.comments_enabled && !isFetchingComments && hasNoComments && ( )} diff --git a/ui/page/settingsCreator/index.js b/ui/page/settingsCreator/index.js index 62d2137da..8420dc985 100644 --- a/ui/page/settingsCreator/index.js +++ b/ui/page/settingsCreator/index.js @@ -29,7 +29,7 @@ const select = (state) => ({ const perform = (dispatch) => ({ commentBlockWords: (channelClaim, words) => dispatch(doCommentBlockWords(channelClaim, words)), commentUnblockWords: (channelClaim, words) => dispatch(doCommentUnblockWords(channelClaim, words)), - fetchCreatorSettings: (channelClaimIds) => dispatch(doFetchCreatorSettings(channelClaimIds)), + fetchCreatorSettings: (channelClaimId) => dispatch(doFetchCreatorSettings(channelClaimId)), updateCreatorSettings: (channelClaim, settings) => dispatch(doUpdateCreatorSettings(channelClaim, settings)), commentModAddDelegate: (modChanId, modChanName, creatorChannelClaim) => dispatch(doCommentModAddDelegate(modChanId, modChanName, creatorChannelClaim)), diff --git a/ui/page/settingsCreator/view.jsx b/ui/page/settingsCreator/view.jsx index d7b1af2c0..dfa16bcef 100644 --- a/ui/page/settingsCreator/view.jsx +++ b/ui/page/settingsCreator/view.jsx @@ -30,7 +30,7 @@ type Props = { commentModAddDelegate: (string, string, ChannelClaim) => void, commentModRemoveDelegate: (string, string, ChannelClaim) => void, commentModListDelegates: (ChannelClaim) => void, - fetchCreatorSettings: (Array) => void, + fetchCreatorSettings: (channelId: string) => void, updateCreatorSettings: (ChannelClaim, PerChannelSettings) => void, doToast: ({ message: string }) => void, }; @@ -61,21 +61,16 @@ export default function SettingsCreatorPage(props: Props) { const [slowModeMinGap, setSlowModeMinGap] = React.useState(0); const [lastUpdated, setLastUpdated] = React.useState(1); - function settingsToStates(settings: PerChannelSettings) { - if (settings.comments_enabled !== undefined) { - setCommentsEnabled(settings.comments_enabled); - } - if (settings.min_tip_amount_comment !== undefined) { - setMinTipAmountComment(settings.min_tip_amount_comment); - } - if (settings.min_tip_amount_super_chat !== undefined) { - setMinTipAmountSuperChat(settings.min_tip_amount_super_chat); - } - if (settings.slow_mode_min_gap !== undefined) { - setSlowModeMinGap(settings.slow_mode_min_gap); - } - if (settings.words) { - const tagArray = Array.from(new Set(settings.words)); + /** + * Updates corresponding GUI states with the given PerChannelSettings values. + * + * @param settings + * @param fullSync If true, update all states and consider 'undefined' settings as "cleared/false"; + * if false, only update defined settings. + */ + function settingsToStates(settings: PerChannelSettings, fullSync: boolean) { + const doSetMutedWordTags = (words: Array) => { + const tagArray = Array.from(new Set(words)); setMutedWordTags( tagArray .filter((t) => t !== '') @@ -83,11 +78,35 @@ export default function SettingsCreatorPage(props: Props) { return { name: x }; }) ); + }; + + if (fullSync) { + setCommentsEnabled(settings.comments_enabled || false); + setMinTipAmountComment(settings.min_tip_amount_comment || 0); + setMinTipAmountSuperChat(settings.min_tip_amount_super_chat || 0); + setSlowModeMinGap(settings.slow_mode_min_gap || 0); + doSetMutedWordTags(settings.words || []); + } else { + if (settings.comments_enabled !== undefined) { + setCommentsEnabled(settings.comments_enabled); + } + if (settings.min_tip_amount_comment !== undefined) { + setMinTipAmountComment(settings.min_tip_amount_comment); + } + if (settings.min_tip_amount_super_chat !== undefined) { + setMinTipAmountSuperChat(settings.min_tip_amount_super_chat); + } + if (settings.slow_mode_min_gap !== undefined) { + setSlowModeMinGap(settings.slow_mode_min_gap); + } + if (settings.words) { + doSetMutedWordTags(settings.words); + } } } function setSettings(newSettings: PerChannelSettings) { - settingsToStates(newSettings); + settingsToStates(newSettings, false); updateCreatorSettings(activeChannelClaim, newSettings); setLastUpdated(Date.now()); } @@ -213,15 +232,15 @@ export default function SettingsCreatorPage(props: Props) { if (activeChannelClaim && settingsByChannelId && settingsByChannelId[activeChannelClaim.claim_id]) { const channelSettings = settingsByChannelId[activeChannelClaim.claim_id]; - settingsToStates(channelSettings); + settingsToStates(channelSettings, true); } }, [activeChannelClaim, settingsByChannelId, lastUpdated]); - // Re-sync list, mainly to correct any invalid settings. + // Re-sync list on first idle time; mainly to correct any invalid settings. React.useEffect(() => { if (lastUpdated && activeChannelClaim) { const timer = setTimeout(() => { - fetchCreatorSettings([activeChannelClaim.claim_id]); + fetchCreatorSettings(activeChannelClaim.claim_id); }, DEBOUNCE_REFRESH_MS); return () => clearTimeout(timer); } diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js index 160ff7e9f..a97fe6a7f 100644 --- a/ui/redux/actions/comments.js +++ b/ui/redux/actions/comments.js @@ -104,7 +104,7 @@ export function doCommentList( totalFilteredItems: total_filtered_items, totalPages: total_pages, claimId: claimId, - authorClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined, + commenterClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined, uri: uri, }, }); @@ -1357,7 +1357,7 @@ export function doFetchCommentModAmIList(channelClaim: ChannelClaim) { }; } -export const doFetchCreatorSettings = (channelClaimIds: Array = []) => { +export const doFetchCreatorSettings = (channelId: string) => { return async (dispatch: Dispatch, getState: GetState) => { const state = getState(); const myChannels = selectMyChannelClaims(state); @@ -1366,84 +1366,49 @@ export const doFetchCreatorSettings = (channelClaimIds: Array = []) => { type: ACTIONS.COMMENT_FETCH_SETTINGS_STARTED, }); - let channelSignatures = []; + let signedName; + if (myChannels) { - for (const channelClaim of myChannels) { - if (channelClaimIds.length !== 0 && !channelClaimIds.includes(channelClaim.claim_id)) { - continue; - } - - try { - const channelSignature = await Lbry.channel_sign({ - channel_id: channelClaim.claim_id, - hexdata: toHex(channelClaim.name), - }); - - channelSignatures.push({ ...channelSignature, claim_id: channelClaim.claim_id, name: channelClaim.name }); - } catch (e) {} + const index = myChannels.findIndex((myChannel) => myChannel.claim_id === channelId); + if (index > -1) { + signedName = await channelSignName(channelId, myChannels[index].name); } } - return Promise.all( - channelSignatures.map((signatureData) => - Comments.setting_list({ - channel_name: signatureData.name, - channel_id: signatureData.claim_id, - signature: signatureData.signature, - signing_ts: signatureData.signing_ts, - }) - ) - ) - .then((settings) => { - const settingsByChannelId = {}; - - for (let i = 0; i < channelSignatures.length; ++i) { - const channelId = channelSignatures[i].claim_id; - settingsByChannelId[channelId] = settings[i]; - - if (settings[i].words) { - settingsByChannelId[channelId].words = settings[i].words.split(','); - } - - delete settingsByChannelId[channelId].channel_name; - delete settingsByChannelId[channelId].channel_id; - delete settingsByChannelId[channelId].signature; - delete settingsByChannelId[channelId].signing_ts; - } + const cmd = signedName ? Comments.setting_list : Comments.setting_get; + return cmd({ + channel_id: channelId, + channel_name: (signedName && signedName.name) || undefined, + signature: (signedName && signedName.signature) || undefined, + signing_ts: (signedName && signedName.signing_ts) || undefined, + }) + .then((response: SettingsResponse) => { dispatch({ type: ACTIONS.COMMENT_FETCH_SETTINGS_COMPLETED, - data: settingsByChannelId, + data: { + channelId: channelId, + settings: response, + partialUpdate: !signedName, + }, }); }) .catch((err) => { - // TODO: Use error codes when available. - // TODO: The "validation is disallowed" thing ideally should just be a - // success case that returns a null setting, instead of an error. - // As we are using 'Promise.all', if one channel fails, everyone - // fails. This forces us to remove the batch functionality of this - // function. However, since this "validation is disallowed" thing - // is potentially a temporary one to handle spammers, I retained - // the batch functionality for now. if (err.message === 'validation is disallowed for non controlling channels') { - const settingsByChannelId = {}; - for (let i = 0; i < channelSignatures.length; ++i) { - const channelId = channelSignatures[i].claim_id; - // 'undefined' means "fetching or have not fetched"; - // 'null' means "feature not available for this channel"; - settingsByChannelId[channelId] = null; - } - dispatch({ type: ACTIONS.COMMENT_FETCH_SETTINGS_COMPLETED, - data: settingsByChannelId, + data: { + channelId: channelId, + settings: null, + partialUpdate: !signedName, + }, + }); + } else { + devToast(dispatch, `Creator: ${err}`); + dispatch({ + type: ACTIONS.COMMENT_FETCH_SETTINGS_FAILED, }); - return; } - - dispatch({ - type: ACTIONS.COMMENT_FETCH_SETTINGS_FAILED, - }); }); }; }; @@ -1454,7 +1419,7 @@ export const doFetchCreatorSettings = (channelClaimIds: Array = []) => { * * @param channelClaim * @param settings - * @returns {function(Dispatch, GetState): Promise|Promise|*} + * @returns {function(Dispatch, GetState): any} */ export const doUpdateCreatorSettings = (channelClaim: ChannelClaim, settings: PerChannelSettings) => { return async (dispatch: Dispatch, getState: GetState) => { diff --git a/ui/redux/reducers/comments.js b/ui/redux/reducers/comments.js index 6e3ed5f07..5371bc5df 100644 --- a/ui/redux/reducers/comments.js +++ b/ui/redux/reducers/comments.js @@ -4,6 +4,8 @@ import { handleActions } from 'util/redux-utils'; import { BLOCK_LEVEL } from 'constants/comment'; import { isURIEqual } from 'lbry-redux'; +const BTC_SATOSHIS = 100000000; + const defaultState: CommentsState = { commentById: {}, // commentId -> Comment byId: {}, // ClaimID -> list of fetched comment IDs. @@ -41,7 +43,6 @@ const defaultState: CommentsState = { blockingByUri: {}, unBlockingByUri: {}, togglingForDelegatorMap: {}, - commentsDisabledChannelIds: [], settingsByChannelId: {}, // ChannelId -> PerChannelSettings fetchingSettings: false, fetchingBlockedWords: false, @@ -251,32 +252,8 @@ export default handleActions( claimId, uri, disabled, - authorClaimId, + commenterClaimId, } = action.data; - const commentsDisabledChannelIds = [...state.commentsDisabledChannelIds]; - - if (disabled) { - if (!commentsDisabledChannelIds.includes(authorClaimId)) { - commentsDisabledChannelIds.push(authorClaimId); - } - - const isLoadingByParentId = Object.assign({}, state.isLoadingByParentId); - if (parentId) { - isLoadingByParentId[parentId] = false; - } - - return { - ...state, - commentsDisabledChannelIds, - isLoading: false, - isLoadingByParentId, - }; - } 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); @@ -289,50 +266,61 @@ export default handleActions( const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById); const totalRepliesByParentId = Object.assign({}, state.totalRepliesByParentId); const isLoadingByParentId = Object.assign({}, state.isLoadingByParentId); + const settingsByChannelId = Object.assign({}, state.settingsByChannelId); - if (!parentId) { - totalCommentsById[claimId] = totalItems; - topLevelTotalCommentsById[claimId] = totalFilteredItems; - topLevelTotalPagesById[claimId] = totalPages; - } else { - totalRepliesByParentId[parentId] = totalFilteredItems; + settingsByChannelId[commenterClaimId] = { + ...(settingsByChannelId[commenterClaimId] || {}), + comments_enabled: !disabled, + }; + + if (parentId) { isLoadingByParentId[parentId] = false; } - const commonUpdateAction = (comment, commentById, commentIds, index) => { - // map the comment_ids to the new comments - commentById[comment.comment_id] = comment; - commentIds[index] = comment.comment_id; - }; + if (!disabled) { + if (parentId) { + totalRepliesByParentId[parentId] = totalFilteredItems; + } else { + totalCommentsById[claimId] = totalItems; + topLevelTotalCommentsById[claimId] = totalFilteredItems; + topLevelTotalPagesById[claimId] = totalPages; + } - if (comments) { - // we use an Array to preserve order of listing - // in reality this doesn't matter and we can just - // sort comments by their timestamp - const commentIds = Array(comments.length); + const commonUpdateAction = (comment, commentById, commentIds, index) => { + // map the comment_ids to the new comments + commentById[comment.comment_id] = comment; + commentIds[index] = comment.comment_id; + }; - // --- Top-level comments --- - if (!parentId) { - for (let i = 0; i < comments.length; ++i) { - const comment = comments[i]; - commonUpdateAction(comment, commentById, commentIds, i); - pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id); - if (comment.is_pinned) { - pushToArrayInObject(pinnedCommentsById, claimId, comment.comment_id); + if (comments) { + // we use an Array to preserve order of listing + // in reality this doesn't matter and we can just + // sort comments by their timestamp + const commentIds = Array(comments.length); + + // --- Top-level comments --- + if (!parentId) { + for (let i = 0; i < comments.length; ++i) { + const comment = comments[i]; + commonUpdateAction(comment, commentById, commentIds, i); + pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id); + if (comment.is_pinned) { + pushToArrayInObject(pinnedCommentsById, claimId, comment.comment_id); + } } } - } - // --- Replies --- - else { - for (let i = 0; i < comments.length; ++i) { - const comment = comments[i]; - commonUpdateAction(comment, commentById, commentIds, i); - pushToArrayInObject(repliesByParentId, parentId, comment.comment_id); + // --- Replies --- + else { + for (let i = 0; i < comments.length; ++i) { + const comment = comments[i]; + commonUpdateAction(comment, commentById, commentIds, i); + pushToArrayInObject(repliesByParentId, parentId, comment.comment_id); + } } - } - byId[claimId] ? byId[claimId].push(...commentIds) : (byId[claimId] = commentIds); - commentsByUri[uri] = claimId; + byId[claimId] ? byId[claimId].push(...commentIds) : (byId[claimId] = commentIds); + commentsByUri[uri] = claimId; + } } return { @@ -347,9 +335,9 @@ export default handleActions( byId, commentById, commentsByUri, - commentsDisabledChannelIds, isLoading: false, isLoadingByParentId, + settingsByChannelId, }; }, @@ -1018,23 +1006,27 @@ export default handleActions( fetchingSettings: false, }), [ACTIONS.COMMENT_FETCH_SETTINGS_COMPLETED]: (state: CommentsState, action: any) => { - // TODO: This is incorrect, as it could make 'settingsByChannelId' store - // only 1 channel with other channel's data purged. It works for now - // because the GUI only shows 1 channel's setting at a time, and *always* - // re-fetches to get latest data before displaying. Either rename this to - // 'activeChannelCreatorSettings', or append the new data properly. - const settingsByChannelId = Object.assign({}, action.data); - const BTC_SATOSHIS = 100000000; + const { channelId, settings, partialUpdate } = action.data; + const settingsByChannelId = Object.assign({}, state.settingsByChannelId); - const channelIds = Object.keys(settingsByChannelId); - channelIds.forEach((ci) => { - if (settingsByChannelId[ci].min_tip_amount_comment) { - settingsByChannelId[ci].min_tip_amount_comment /= BTC_SATOSHIS; + if (partialUpdate) { + settingsByChannelId[channelId] = { + // The existing may contain additional Creator Settings (e.g. 'words') + ...(settingsByChannelId[channelId] || {}), + // Spread new settings. + ...settings, + }; + } else { + settingsByChannelId[channelId] = settings; + if (settings.words) { + settingsByChannelId[channelId].words = settings.words.split(','); } - if (settingsByChannelId[ci].min_tip_amount_super_chat) { - settingsByChannelId[ci].min_tip_amount_super_chat /= BTC_SATOSHIS; - } - }); + } + + // Both partial and full update will always include these, + // so safe to always re-compute without accidentally double-converting. + settingsByChannelId[channelId].min_tip_amount_comment /= BTC_SATOSHIS; + settingsByChannelId[channelId].min_tip_amount_super_chat /= BTC_SATOSHIS; return { ...state, diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index 1a2ec9e78..a724a27f3 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, makeSelectClaimForUri } from 'lbry-redux'; +import { selectClaimsById, isClaimNsfw, selectMyActiveClaims } from 'lbry-redux'; const selectState = (state) => state.comments || {}; @@ -12,10 +12,6 @@ export const selectIsFetchingComments = createSelector(selectState, (state) => s export const selectIsFetchingCommentsByParentId = createSelector(selectState, (state) => state.isLoadingByParentId); 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 selectPinnedCommentsById = createSelector(selectState, (state) => state.pinnedCommentsById); @@ -426,15 +422,3 @@ 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); - }); diff --git a/ui/util/claim.js b/ui/util/claim.js new file mode 100644 index 000000000..caefb9e21 --- /dev/null +++ b/ui/util/claim.js @@ -0,0 +1,11 @@ +// @flow + +export function getChannelIdFromClaim(claim: ?Claim) { + if (claim) { + if (claim.value_type === 'channel') { + return claim.claim_id; + } else if (claim.signing_channel) { + return claim.signing_channel.claim_id; + } + } +}