From 862ea676804708c46bf4ad59806c6ca8e5157c71 Mon Sep 17 00:00:00 2001 From: Jessop Breth Date: Tue, 11 Jun 2019 21:14:37 -0400 Subject: [PATCH] changes for comments --- dist/bundle.es.js | 184 +++++++++++++++++++++++++++++++- dist/flow-typed/Comment.js | 18 ++++ dist/flow-typed/Lbry.js | 6 ++ flow-typed/Comment.js | 18 ++++ flow-typed/Lbry.js | 6 ++ src/constants/action_types.js | 8 ++ src/index.js | 5 + src/lbry.js | 3 + src/redux/actions/comments.js | 82 ++++++++++++++ src/redux/reducers/comments.js | 66 ++++++++++++ src/redux/selectors/comments.js | 36 +++++++ 11 files changed, 429 insertions(+), 3 deletions(-) create mode 100644 dist/flow-typed/Comment.js create mode 100644 flow-typed/Comment.js create mode 100644 src/redux/actions/comments.js create mode 100644 src/redux/reducers/comments.js create mode 100644 src/redux/selectors/comments.js diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 71bf2e5..e4cc141 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -102,6 +102,14 @@ const CLAIM_SEARCH_STARTED = 'CLAIM_SEARCH_STARTED'; const CLAIM_SEARCH_COMPLETED = 'CLAIM_SEARCH_COMPLETED'; const CLAIM_SEARCH_FAILED = 'CLAIM_SEARCH_FAILED'; +// Comments +const COMMENT_LIST_STARTED = 'COMMENT_LIST_STARTED'; +const COMMENT_LIST_COMPLETED = 'COMMENT_LIST_COMPLETED'; +const COMMENT_LIST_UPDATED = 'COMMENT_LIST_UPDATED'; +const COMMENT_CREATE_STARTED = 'COMMENT_CREATE_STARTED'; +const COMMENT_CREATE_COMPLETED = 'COMMENT_CREATE_COMPLETED'; +const COMMENT_CREATE_FAILED = 'COMMENT_CREATE_FAILED'; + // Files const FILE_LIST_STARTED = 'FILE_LIST_STARTED'; const FILE_LIST_SUCCEEDED = 'FILE_LIST_SUCCEEDED'; @@ -318,6 +326,12 @@ var action_types = /*#__PURE__*/Object.freeze({ CLAIM_SEARCH_STARTED: CLAIM_SEARCH_STARTED, CLAIM_SEARCH_COMPLETED: CLAIM_SEARCH_COMPLETED, CLAIM_SEARCH_FAILED: CLAIM_SEARCH_FAILED, + COMMENT_LIST_STARTED: COMMENT_LIST_STARTED, + COMMENT_LIST_COMPLETED: COMMENT_LIST_COMPLETED, + COMMENT_LIST_UPDATED: COMMENT_LIST_UPDATED, + COMMENT_CREATE_STARTED: COMMENT_CREATE_STARTED, + COMMENT_CREATE_COMPLETED: COMMENT_CREATE_COMPLETED, + COMMENT_CREATE_FAILED: COMMENT_CREATE_FAILED, FILE_LIST_STARTED: FILE_LIST_STARTED, FILE_LIST_SUCCEEDED: FILE_LIST_SUCCEEDED, FETCH_FILE_INFO_STARTED: FETCH_FILE_INFO_STARTED, @@ -676,6 +690,9 @@ const Lbry = { sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params), sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params), + // Comments + comment_list: (params = {}) => daemonCallWithResult('comment_list', params), + comment_create: (params = {}) => daemonCallWithResult('comment_create', params), // Connect to the sdk connect: () => { if (Lbry.connectPromise === null) { @@ -2761,6 +2778,74 @@ const doDeleteTag = name => ({ } }); +// + +function doCommentList(uri) { + return (dispatch, getState) => { + const state = getState(); + const claim = selectClaimsByUri(state)[uri]; + const claimId = claim ? claim.claim_id : null; + + dispatch({ + type: COMMENT_LIST_STARTED + }); + lbryProxy.comment_list({ + claim_id: claimId + }).then(results => { + dispatch({ + type: COMMENT_LIST_COMPLETED, + data: { + comments: results, + claimId: claimId, + uri: uri + } + }); + }).catch(error => { + console.log(error); + }); + }; +} + +function doCommentCreate(comment = '', claim_id = '', channel, parent_id) { + return (dispatch, getState) => { + const state = getState(); + dispatch({ + type: COMMENT_CREATE_STARTED + }); + const myChannels = selectMyChannelClaims(state); + const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel); + const channel_id = namedChannelClaim ? namedChannelClaim.claim_id : null; + return lbryProxy.comment_create({ + comment, + claim_id, + channel_id + }).then(result => { + dispatch({ + type: COMMENT_CREATE_COMPLETED, + data: { + response: result + } + }); + dispatch({ + type: COMMENT_LIST_UPDATED, + data: { + comment: result, + claimId: claim_id + } + }); + }).catch(error => { + dispatch({ + type: COMMENT_CREATE_FAILED, + data: error + }); + dispatch(doToast({ + message: 'Oops, someone broke comments.', + isError: true + })); + }); + }; +} + var _extends$4 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const reducers = {}; @@ -3724,6 +3809,70 @@ function contentReducer(state = defaultState$6, action) { return state; } +// + +// TODO change to handleActions() +// const commentsReducer = handleActions( { +const reducers$4 = {}; + +const defaultState$7 = { + byId: {}, + commentsByUri: {}, + isLoading: false +}; + +reducers$4[COMMENT_CREATE_STARTED] = (state, action) => Object.assign({}, state, { + isLoading: true +}); + +reducers$4[COMMENT_CREATE_FAILED] = (state, action) => { + // TODO: handle error.. what else? + return state; +}; + +reducers$4[COMMENT_CREATE_COMPLETED] = (state, action) => { + const { comments } = action.data; + return state; +}; + +reducers$4[COMMENT_LIST_UPDATED] = (state, action) => { + const { comment, claimId } = action.data; + const byId = Object.assign({}, state.byId); + const comments = byId[claimId]; + const newComments = comments.slice(); + newComments.unshift(comment); + byId[claimId] = newComments; + return Object.assign({}, state, { + byId + }); +}; + +reducers$4[COMMENT_LIST_STARTED] = state => Object.assign({}, state, { + isLoading: true +}); + +reducers$4[COMMENT_LIST_COMPLETED] = (state, action) => { + const { comments, claimId, uri } = action.data; + const byId = Object.assign({}, state.byId); + const commentsByUri = Object.assign({}, state.commentsByUri); + + if (comments['items']) { + byId[claimId] = comments['items']; + commentsByUri[uri] = claimId; + } + return Object.assign({}, state, { + byId, + commentsByUri, + isLoading: false + }); +}; + +function commentReducer(state = defaultState$7, action) { + const handler = reducers$4[action.type]; + if (handler) return handler(state, action); + return state; +} + var _extends$b = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const tagsReducerBuilder = defaultState => handleActions({ @@ -3810,11 +3959,36 @@ const selectError = reselect.createSelector(selectState$6, state => { // -const selectState$7 = state => state.tags || {}; +const selectState$7 = state => state.comments || {}; -const selectKnownTagsByName = reselect.createSelector(selectState$7, state => state.knownTags); +const selectCommentsById = reselect.createSelector(selectState$7, state => state.byId || {}); -const selectFollowedTagsList = reselect.createSelector(selectState$7, state => state.followedTags); +const selectCommentsByUri = reselect.createSelector(selectState$7, state => { + const byUri = state.commentsByUri || {}; + const comments = {}; + Object.keys(byUri).forEach(uri => { + const claimId = byUri[uri]; + if (claimId === null) { + comments[uri] = null; + } else { + comments[uri] = claimId; + } + }); + return comments; +}); + +const makeSelectCommentsForUri = uri => reselect.createSelector(selectCommentsById, selectCommentsByUri, (byId, byUri) => { + const claimId = byUri[uri]; + return byId && byId[claimId]; +}); + +// + +const selectState$8 = state => state.tags || {}; + +const selectKnownTagsByName = reselect.createSelector(selectState$8, state => state.knownTags); + +const selectFollowedTagsList = reselect.createSelector(selectState$8, state => state.followedTags); const selectFollowedTags = reselect.createSelector(selectFollowedTagsList, followedTags => followedTags.map(tag => ({ name: tag })).sort((a, b) => a.name.localeCompare(b.name))); @@ -3844,6 +4018,7 @@ exports.TRANSACTIONS = transaction_types; exports.batchActions = batchActions; exports.buildURI = buildURI; exports.claimsReducer = claimsReducer; +exports.commentReducer = commentReducer; exports.contentReducer = contentReducer; exports.convertToShareLink = convertToShareLink; exports.creditsToString = creditsToString; @@ -3853,6 +4028,8 @@ exports.doBalanceSubscribe = doBalanceSubscribe; exports.doBlurSearchInput = doBlurSearchInput; exports.doCheckAddressIsMine = doCheckAddressIsMine; exports.doClaimSearch = doClaimSearch; +exports.doCommentCreate = doCommentCreate; +exports.doCommentList = doCommentList; exports.doCreateChannel = doCreateChannel; exports.doDeletePurchasedUri = doDeletePurchasedUri; exports.doDeleteTag = doDeleteTag; @@ -3906,6 +4083,7 @@ exports.makeSelectClaimIsNsfw = makeSelectClaimIsNsfw; exports.makeSelectClaimIsPending = makeSelectClaimIsPending; exports.makeSelectClaimsInChannelForCurrentPageState = makeSelectClaimsInChannelForCurrentPageState; exports.makeSelectClaimsInChannelForPage = makeSelectClaimsInChannelForPage; +exports.makeSelectCommentsForUri = makeSelectCommentsForUri; exports.makeSelectContentPositionForUri = makeSelectContentPositionForUri; exports.makeSelectContentTypeForUri = makeSelectContentTypeForUri; exports.makeSelectCoverForUri = makeSelectCoverForUri; diff --git a/dist/flow-typed/Comment.js b/dist/flow-typed/Comment.js new file mode 100644 index 0000000..808dda8 --- /dev/null +++ b/dist/flow-typed/Comment.js @@ -0,0 +1,18 @@ +declare type Comment = { + author: string, + claim_index?: number, + comment_id?: number, + downvotes?: number, + message: string, + omitted?: number, + reply_count?: number, + time_posted?: number, + upvotes?: number, + parent_id?: number, +}; + +declare type CommentsState = { + byId: {}, + isLoading: boolean, + commentsByUri: { [string]: string }, +} diff --git a/dist/flow-typed/Lbry.js b/dist/flow-typed/Lbry.js index d67a17d..9931f68 100644 --- a/dist/flow-typed/Lbry.js +++ b/dist/flow-typed/Lbry.js @@ -105,6 +105,9 @@ declare type ChannelCreateResponse = GenericTxResponse & { outputs: Array, }; +declare type CommentCreateResponse = Comment; +declare type CommentListResponse = Array; + declare type ChannelListResponse = Array; declare type FileListResponse = Array; @@ -184,6 +187,9 @@ declare type LbryTypes = { blob_delete: (params: {}) => Promise, blob_list: (params: {}) => Promise, + // Commenting + comment_list: (params: {}) => Promise, + comment_create: (params: {}) => Promise, // Wallet utilities account_balance: (params: {}) => Promise, account_decrypt: (prams: {}) => Promise, diff --git a/flow-typed/Comment.js b/flow-typed/Comment.js new file mode 100644 index 0000000..808dda8 --- /dev/null +++ b/flow-typed/Comment.js @@ -0,0 +1,18 @@ +declare type Comment = { + author: string, + claim_index?: number, + comment_id?: number, + downvotes?: number, + message: string, + omitted?: number, + reply_count?: number, + time_posted?: number, + upvotes?: number, + parent_id?: number, +}; + +declare type CommentsState = { + byId: {}, + isLoading: boolean, + commentsByUri: { [string]: string }, +} diff --git a/flow-typed/Lbry.js b/flow-typed/Lbry.js index d67a17d..9931f68 100644 --- a/flow-typed/Lbry.js +++ b/flow-typed/Lbry.js @@ -105,6 +105,9 @@ declare type ChannelCreateResponse = GenericTxResponse & { outputs: Array, }; +declare type CommentCreateResponse = Comment; +declare type CommentListResponse = Array; + declare type ChannelListResponse = Array; declare type FileListResponse = Array; @@ -184,6 +187,9 @@ declare type LbryTypes = { blob_delete: (params: {}) => Promise, blob_list: (params: {}) => Promise, + // Commenting + comment_list: (params: {}) => Promise, + comment_create: (params: {}) => Promise, // Wallet utilities account_balance: (params: {}) => Promise, account_decrypt: (prams: {}) => Promise, diff --git a/src/constants/action_types.js b/src/constants/action_types.js index 80acf3f..c1427fb 100644 --- a/src/constants/action_types.js +++ b/src/constants/action_types.js @@ -92,6 +92,14 @@ export const CLAIM_SEARCH_STARTED = 'CLAIM_SEARCH_STARTED'; export const CLAIM_SEARCH_COMPLETED = 'CLAIM_SEARCH_COMPLETED'; export const CLAIM_SEARCH_FAILED = 'CLAIM_SEARCH_FAILED'; +// Comments +export const COMMENT_LIST_STARTED = 'COMMENT_LIST_STARTED'; +export const COMMENT_LIST_COMPLETED = 'COMMENT_LIST_COMPLETED'; +export const COMMENT_LIST_UPDATED = 'COMMENT_LIST_UPDATED'; +export const COMMENT_CREATE_STARTED = 'COMMENT_CREATE_STARTED'; +export const COMMENT_CREATE_COMPLETED = 'COMMENT_CREATE_COMPLETED'; +export const COMMENT_CREATE_FAILED = 'COMMENT_CREATE_FAILED'; + // Files export const FILE_LIST_STARTED = 'FILE_LIST_STARTED'; export const FILE_LIST_SUCCEEDED = 'FILE_LIST_SUCCEEDED'; diff --git a/src/index.js b/src/index.js index 4ae019e..0e88ed3 100644 --- a/src/index.js +++ b/src/index.js @@ -90,6 +90,8 @@ export { export { doToggleTagFollow, doAddTag, doDeleteTag } from 'redux/actions/tags'; +export { doCommentList, doCommentCreate } from 'redux/actions/comments'; + // utils export { batchActions } from 'util/batchActions'; export { parseQueryParams, toQueryString } from 'util/query_params'; @@ -104,6 +106,7 @@ export { notificationsReducer } from 'redux/reducers/notifications'; export { searchReducer } from 'redux/reducers/search'; export { walletReducer } from 'redux/reducers/wallet'; export { contentReducer } from 'redux/reducers/content'; +export { commentReducer } from 'redux/reducers/comments'; export { tagsReducerBuilder } from 'redux/reducers/tags'; // selectors @@ -170,6 +173,8 @@ export { selectLastClaimSearchUris, } from 'redux/selectors/claims'; +export { makeSelectCommentsForUri } from 'redux/selectors/comments'; + export { makeSelectFileInfoForUri, makeSelectDownloadingForUri, diff --git a/src/lbry.js b/src/lbry.js index 3ac88ad..617b24b 100644 --- a/src/lbry.js +++ b/src/lbry.js @@ -100,6 +100,9 @@ const Lbry: LbryTypes = { sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params), sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params), + // Comments + comment_list: (params = {}) => daemonCallWithResult('comment_list', params), + comment_create: (params = {}) => daemonCallWithResult('comment_create', params), // Connect to the sdk connect: () => { if (Lbry.connectPromise === null) { diff --git a/src/redux/actions/comments.js b/src/redux/actions/comments.js new file mode 100644 index 0000000..6d48802 --- /dev/null +++ b/src/redux/actions/comments.js @@ -0,0 +1,82 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; +import Lbry from 'lbry'; +import { selectClaimsByUri, selectMyChannelClaims } from 'redux/selectors/claims'; +import { doToast } from 'redux/actions/notifications'; + +export function doCommentList(uri: string) { + return (dispatch: Dispatch, getState: GetState) => { + const state = getState(); + const claim = selectClaimsByUri(state)[uri]; + const claimId = claim ? claim.claim_id : null; + + dispatch({ + type: ACTIONS.COMMENT_LIST_STARTED, + }); + Lbry.comment_list({ + claim_id: claimId, + }) + .then(results => { + dispatch({ + type: ACTIONS.COMMENT_LIST_COMPLETED, + data: { + comments: results, + claimId: claimId, + uri: uri, + }, + }); + }) + .catch(error => { + console.log(error); + }); + }; +} + +export function doCommentCreate( + comment: string = '', + claim_id: string = '', + channel: ?string, + parent_id?: number +) { + return (dispatch: Dispatch, getState: GetState) => { + const state = getState(); + dispatch({ + type: ACTIONS.COMMENT_CREATE_STARTED, + }); + const myChannels = selectMyChannelClaims(state); + const namedChannelClaim = myChannels.find(myChannel => myChannel.name === channel); + const channel_id = namedChannelClaim ? namedChannelClaim.claim_id : null; + return Lbry.comment_create({ + comment, + claim_id, + channel_id, + }) + .then((result: Comment) => { + dispatch({ + type: ACTIONS.COMMENT_CREATE_COMPLETED, + data: { + response: result, + }, + }); + dispatch({ + type: ACTIONS.COMMENT_LIST_UPDATED, + data: { + comment: result, + claimId: claim_id, + }, + }); + }) + .catch(error => { + dispatch({ + type: ACTIONS.COMMENT_CREATE_FAILED, + data: error, + }); + dispatch( + doToast({ + message: 'Oops, someone broke comments.', + isError: true, + }) + ); + }); + }; +} diff --git a/src/redux/reducers/comments.js b/src/redux/reducers/comments.js new file mode 100644 index 0000000..06895a3 --- /dev/null +++ b/src/redux/reducers/comments.js @@ -0,0 +1,66 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; + +// TODO change to handleActions() +// const commentsReducer = handleActions( { +const reducers = {}; + +const defaultState: CommentsState = { + byId: {}, + commentsByUri: {}, + isLoading: false, +}; + +reducers[ACTIONS.COMMENT_CREATE_STARTED] = (state: CommentsState, action: any): CommentsState => + Object.assign({}, state, { + isLoading: true, + }); + +reducers[ACTIONS.COMMENT_CREATE_FAILED] = (state: CommentsState, action: any): CommentsState => { + // TODO: handle error.. what else? + return state; +}; + +reducers[ACTIONS.COMMENT_CREATE_COMPLETED] = (state: CommentsState, action: any): CommentsState => { + const { comments }: any = action.data; + return state; +}; + +reducers[ACTIONS.COMMENT_LIST_UPDATED] = (state: CommentsState, action: any): CommentsState => { + const { comment, claimId }: any = action.data; + const byId = Object.assign({}, state.byId); + const comments = byId[claimId]; + const newComments = comments.slice(); + newComments.unshift(comment); + byId[claimId] = newComments; + return Object.assign({}, state, { + byId, + }); +}; + +reducers[ACTIONS.COMMENT_LIST_STARTED] = state => + Object.assign({}, state, { + isLoading: true, + }); + +reducers[ACTIONS.COMMENT_LIST_COMPLETED] = (state: CommentsState, action: any) => { + const { comments, claimId, uri } = action.data; + const byId = Object.assign({}, state.byId); + const commentsByUri = Object.assign({}, state.commentsByUri); + + if (comments['items']) { + byId[claimId] = comments['items']; + commentsByUri[uri] = claimId; + } + return Object.assign({}, state, { + byId, + commentsByUri, + isLoading: false, + }); +}; + +export function commentReducer(state: CommentsState = defaultState, action: any) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/src/redux/selectors/comments.js b/src/redux/selectors/comments.js new file mode 100644 index 0000000..c391a5c --- /dev/null +++ b/src/redux/selectors/comments.js @@ -0,0 +1,36 @@ +// @flow +import { createSelector } from 'reselect'; + +const selectState = state => state.comments || {}; + +export const selectCommentsById = createSelector( + selectState, + state => state.byId || {} +); + +export const selectCommentsByUri = createSelector( + selectState, + state => { + const byUri = state.commentsByUri || {}; + const comments = {}; + Object.keys(byUri).forEach(uri => { + const claimId = byUri[uri]; + if (claimId === null) { + comments[uri] = null; + } else { + comments[uri] = claimId; + } + }); + return comments; + } +); + +export const makeSelectCommentsForUri = (uri: string) => + createSelector( + selectCommentsById, + selectCommentsByUri, + (byId, byUri) => { + const claimId = byUri[uri]; + return byId && byId[claimId]; + } + );