From 876ba507f09301161c4369a8c69de15b8dde9a65 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 1 Oct 2021 13:52:47 +0800 Subject: [PATCH 1/7] Add option to pass in url-search params. Impetus: allow linked comment ID and setting the discussion tab when clicking on the `ClaimPreview`. --- ui/component/claimPreview/view.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index 343424d5f..34e7dc188 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -70,6 +70,7 @@ type Props = { streamingUrl: ?string, getFile: (string) => void, customShouldHide?: (Claim) => boolean, + searchParams?: { [string]: string }, showUnresolvedClaim?: boolean, showNullPlaceholder?: boolean, includeSupportAction?: boolean, @@ -125,6 +126,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { // modifiers active, customShouldHide, + searchParams, showNullPlaceholder, // value from show mature content user setting // true if the user doesn't wanna see nsfw content @@ -221,6 +223,11 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { if (listId) { navigateSearch.set(COLLECTIONS_CONSTS.COLLECTION_ID, listId); } + if (searchParams) { + Object.keys(searchParams).forEach((key) => { + navigateSearch.set(key, searchParams[key]); + }); + } const handleNavLinkClick = (e) => { if (onClick) { -- 2.45.3 From 99b742ac3ee7adb5c3a8c7726774346220b1ba2d Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 27 Sep 2021 11:44:44 +0800 Subject: [PATCH 2/7] comment.list: fix typos and renamed variables - Switch from 'author' to 'creator' to disambiguate between comment author and content author. For comment author, we'll use 'commenter' from now on. - Corrected 'commenterClaimId' to 'creatorClaimId' (just a typo, no functional change). --- ui/redux/actions/comments.js | 11 +++++------ ui/redux/reducers/comments.js | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js index 44947d109..8196ef288 100644 --- a/ui/redux/actions/comments.js +++ b/ui/redux/actions/comments.js @@ -127,7 +127,6 @@ export function doCommentList( type: ACTIONS.COMMENT_LIST_FAILED, data: 'unable to find claim for uri', }); - return; } @@ -139,7 +138,7 @@ export function doCommentList( }); // Adding 'channel_id' and 'channel_name' enables "CreatorSettings > commentsEnabled". - const authorChannelClaim = claim.value_type === 'channel' ? claim : claim.signing_channel; + const creatorChannelClaim = claim.value_type === 'channel' ? claim : claim.signing_channel; return Comments.comment_list({ page, @@ -147,8 +146,8 @@ export function doCommentList( page_size: pageSize, parent_id: parentId || undefined, top_level: !parentId, - channel_id: authorChannelClaim ? authorChannelClaim.claim_id : undefined, - channel_name: authorChannelClaim ? authorChannelClaim.name : undefined, + channel_id: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined, + channel_name: creatorChannelClaim ? creatorChannelClaim.name : undefined, sort_by: sortBy, }) .then((result: CommentListResponse) => { @@ -162,7 +161,7 @@ export function doCommentList( totalFilteredItems: total_filtered_items, totalPages: total_pages, claimId: claimId, - commenterClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined, + creatorClaimId: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined, uri: uri, }, }); @@ -175,7 +174,7 @@ export function doCommentList( dispatch({ type: ACTIONS.COMMENT_LIST_COMPLETED, data: { - authorClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined, + creatorClaimId: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined, disabled: true, }, }); diff --git a/ui/redux/reducers/comments.js b/ui/redux/reducers/comments.js index d4fd5b926..29a65a885 100644 --- a/ui/redux/reducers/comments.js +++ b/ui/redux/reducers/comments.js @@ -246,7 +246,7 @@ export default handleActions( claimId, uri, disabled, - commenterClaimId, + creatorClaimId, } = action.data; const commentById = Object.assign({}, state.commentById); @@ -262,8 +262,8 @@ export default handleActions( const isLoadingByParentId = Object.assign({}, state.isLoadingByParentId); const settingsByChannelId = Object.assign({}, state.settingsByChannelId); - settingsByChannelId[commenterClaimId] = { - ...(settingsByChannelId[commenterClaimId] || {}), + settingsByChannelId[creatorClaimId] = { + ...(settingsByChannelId[creatorClaimId] || {}), comments_enabled: !disabled, }; -- 2.45.3 From 5e269261229a63a2ffff7d185892b9561a9c09be Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 27 Sep 2021 15:05:59 +0800 Subject: [PATCH 3/7] doCommentReset: change param from uri to claimId This reduces one lookup as clients will always have the claimID ready, but might not have the full URI. It was using URI previously just to match the other APIs. --- ui/component/commentsList/index.js | 2 +- ui/component/commentsList/view.jsx | 7 +++++-- ui/redux/actions/comments.js | 13 +++---------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ui/component/commentsList/index.js b/ui/component/commentsList/index.js index 8d15ab707..c83dae73b 100644 --- a/ui/component/commentsList/index.js +++ b/ui/component/commentsList/index.js @@ -48,7 +48,7 @@ const perform = (dispatch) => ({ fetchTopLevelComments: (uri, page, pageSize, sortBy) => dispatch(doCommentList(uri, '', page, pageSize, sortBy)), fetchComment: (commentId) => dispatch(doCommentById(commentId)), fetchReacts: (commentIds) => dispatch(doCommentReactList(commentIds)), - resetComments: (uri) => dispatch(doCommentReset(uri)), + resetComments: (claimId) => dispatch(doCommentReset(claimId)), }); export default connect(select, perform)(CommentsList); diff --git a/ui/component/commentsList/view.jsx b/ui/component/commentsList/view.jsx index 6a51fd42c..35525d19a 100644 --- a/ui/component/commentsList/view.jsx +++ b/ui/component/commentsList/view.jsx @@ -151,10 +151,13 @@ function CommentList(props: Props) { // Reset comments useEffect(() => { if (page === 0) { - resetComments(uri); + if (claim) { + resetComments(claim.claim_id); + } setPage(1); } - }, [page, uri, resetComments]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [page, uri, resetComments]); // 'claim' is derived from 'uri' // Fetch top-level comments useEffect(() => { diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js index 8196ef288..cdc9ce281 100644 --- a/ui/redux/actions/comments.js +++ b/ui/redux/actions/comments.js @@ -238,17 +238,10 @@ export function doCommentById(commentId: string, toastIfNotFound: boolean = true }; } -export function doCommentReset(uri: string) { - return (dispatch: Dispatch, getState: GetState) => { - const state = getState(); - const claim = selectClaimsByUri(state)[uri]; - const claimId = claim ? claim.claim_id : null; - +export function doCommentReset(claimId: string) { + return (dispatch: Dispatch) => { if (!claimId) { - dispatch({ - type: ACTIONS.COMMENT_LIST_FAILED, - data: 'unable to find claim for uri', - }); + console.error(`Failed to reset comments`); //eslint-disable-line return; } -- 2.45.3 From e068df89f945b5818c35343dcbe9616054629ded Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 27 Sep 2021 16:24:57 +0800 Subject: [PATCH 4/7] Add doCommentListOwn -- command to fetch own comments Since the redux slice is set up based on content or channel ID (for Channel Discussion page), re-use the channel ID for the case of "own comments". We always clear each ID when fetching page-0, so no worries of conflict when actually browsing the Channel Discussion page. --- flow-typed/Comment.js | 2 +- ui/redux/actions/comments.js | 107 ++++++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/flow-typed/Comment.js b/flow-typed/Comment.js index 8b2ad570c..50521e725 100644 --- a/flow-typed/Comment.js +++ b/flow-typed/Comment.js @@ -111,7 +111,7 @@ declare type ReactionListResponse = { declare type CommentListParams = { page: number, // pagination: which page of results page_size: number, // pagination: nr of comments to show in a page (max 200) - claim_id: string, // claim id of claim being commented on + claim_id?: string, // claim id of claim being commented on channel_name?: string, // signing channel name of claim (enables 'commentsEnabled' check) channel_id?: string, // signing channel claim id of claim (enables 'commentsEnabled' check) author_claim_id?: string, // filters comments to just this author diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js index cdc9ce281..3da0f9ccd 100644 --- a/ui/redux/actions/comments.js +++ b/ui/redux/actions/comments.js @@ -3,7 +3,15 @@ import * as ACTIONS from 'constants/action_types'; import * as REACTION_TYPES from 'constants/reactions'; import * as PAGES from 'constants/pages'; import { SORT_BY, BLOCK_LEVEL } from 'constants/comment'; -import { Lbry, parseURI, buildURI, selectClaimsByUri, selectMyChannelClaims, isURIEqual } from 'lbry-redux'; +import { + Lbry, + parseURI, + buildURI, + selectClaimsByUri, + selectMyChannelClaims, + isURIEqual, + doClaimSearch, +} from 'lbry-redux'; import { doToast, doSeeNotifications } from 'redux/actions/notifications'; import { makeSelectMyReactionsForComment, @@ -200,6 +208,103 @@ export function doCommentList( }; } +export function doCommentListOwn( + channelId: string, + page: number = 1, + pageSize: number = 99999, + sortBy: number = SORT_BY.NEWEST +) { + return async (dispatch: Dispatch, getState: GetState) => { + const state = getState(); + const myChannelClaims = selectMyChannelClaims(state); + if (!myChannelClaims) { + console.error('Failed to fetch channel list.'); // eslint-disable-line + return; + } + + const channelClaim = myChannelClaims.find((x) => x.claim_id === channelId); + if (!channelClaim) { + console.error('You do not own this channel.'); // eslint-disable-line + return; + } + + const channelSignature = await channelSignName(channelClaim.claim_id, channelClaim.name); + if (!channelSignature) { + console.error('Failed to sign channel name.'); // eslint-disable-line + return; + } + + dispatch({ + type: ACTIONS.COMMENT_LIST_STARTED, + data: {}, + }); + + return Comments.comment_list({ + page, + page_size: pageSize, + sort_by: sortBy, + author_claim_id: channelId, + requestor_channel_name: channelClaim.name, + requestor_channel_id: channelClaim.claim_id, + signature: channelSignature.signature, + signing_ts: channelSignature.signing_ts, + }) + .then((result: CommentListResponse) => { + const { items: comments, total_items, total_filtered_items, total_pages } = result; + + if (!comments) { + dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: 'No more comments.' }); + return; + } + + dispatch( + doClaimSearch({ + page: 1, + page_size: 20, + no_totals: true, + claim_ids: comments.map((c) => c.claim_id), + }) + ) + .then((result) => { + dispatch({ + type: ACTIONS.COMMENT_LIST_COMPLETED, + data: { + comments, + totalItems: total_items, + totalFilteredItems: total_filtered_items, + totalPages: total_pages, + uri: channelClaim.canonical_url, // hijack "Discussion Page" + claimId: channelClaim.claim_id, // hijack "Discussion Page" + }, + }); + }) + .catch((err) => { + dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: err }); + }); + }) + .catch((error) => { + switch (error.message) { + case FETCH_API_FAILED_TO_FETCH: + dispatch( + doToast({ + isError: true, + message: Comments.isCustomServer + ? __('Failed to fetch comments. Verify custom server settings.') + : __('Failed to fetch comments.'), + }) + ); + dispatch(doToast({ isError: true, message: `${error.message}` })); + dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error }); + break; + + default: + dispatch(doToast({ isError: true, message: `${error.message}` })); + dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error }); + } + }); + }; +} + export function doCommentById(commentId: string, toastIfNotFound: boolean = true) { return (dispatch: Dispatch, getState: GetState) => { dispatch({ -- 2.45.3 From 69dbdce33706f71381517b0f1a00e1d4cb6ba157 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 27 Sep 2021 16:25:51 +0800 Subject: [PATCH 5/7] Comment: add option to hide the actions section --- ui/component/comment/view.jsx | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ui/component/comment/view.jsx b/ui/component/comment/view.jsx index b52003bcf..b4816303a 100644 --- a/ui/component/comment/view.jsx +++ b/ui/component/comment/view.jsx @@ -52,6 +52,7 @@ type Props = { doToast: ({ message: string }) => void, isTopLevel?: boolean, threadDepth: number, + hideActions?: boolean, isPinned: boolean, othersReacts: ?{ like: number, @@ -95,6 +96,7 @@ function Comment(props: Props) { doToast, isTopLevel, threadDepth, + hideActions, isPinned, othersReacts, playingUri, @@ -348,18 +350,20 @@ function Comment(props: Props) { )} -
- {threadDepth !== 0 && ( -
+ {!hideActions && ( +
+ {threadDepth !== 0 && ( +
+ )} {numDirectReplies > 0 && !showReplies && (
-- 2.45.3 From 53ae7d0105343b92ef07fa415efd08b49371b047 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Mon, 27 Sep 2021 13:42:12 +0800 Subject: [PATCH 6/7] Implement own-comments page --- static/app-strings.json | 4 + ui/component/router/view.jsx | 2 + ui/component/settingAccount/index.js | 3 +- ui/component/settingAccount/view.jsx | 14 +- ui/constants/pageTitles.js | 1 + ui/constants/pages.js | 1 + ui/page/ownComments/index.js | 33 +++++ ui/page/ownComments/view.jsx | 210 +++++++++++++++++++++++++++ ui/scss/component/_comments.scss | 57 ++++++++ 9 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 ui/page/ownComments/index.js create mode 100644 ui/page/ownComments/view.jsx diff --git a/static/app-strings.json b/static/app-strings.json index 36a82cd1a..5baa9217f 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -209,6 +209,9 @@ "Failed to copy.": "Failed to copy.", "The publisher has chosen to charge %lbc% to view this content. Your balance is currently too low to view it. Check out %reward_link% for free %lbc% or send more %lbc% to your wallet. You can also %buy_link% more %lbc%.": "The publisher has chosen to charge %lbc% to view this content. Your balance is currently too low to view it. Check out %reward_link% for free %lbc% or send more %lbc% to your wallet. You can also %buy_link% more %lbc%.", "Connecting...": "Connecting...", + "Your comments": "Your comments", + "View your past comments.": "View your past comments.", + "Content or channel was deleted.": "Content or channel was deleted.", "Comments": "Comments", "Comment": "Comment", "Comment --[button to submit something]--": "Comment", @@ -1460,6 +1463,7 @@ "Staked LBRY Credits": "Staked LBRY Credits", "1 comment": "1 comment", "%total_comments% comments": "%total_comments% comments", + "No comments": "No comments", "Upvote": "Upvote", "Downvote": "Downvote", "You loved this": "You loved this", diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx index 03db2b538..ad5234265 100644 --- a/ui/component/router/view.jsx +++ b/ui/component/router/view.jsx @@ -59,6 +59,7 @@ const ListBlockedPage = lazyImport(() => import('page/listBlocked' /* webpackChu const ListsPage = lazyImport(() => import('page/lists' /* webpackChunkName: "secondary" */)); const LiveStreamSetupPage = lazyImport(() => import('page/livestreamSetup' /* webpackChunkName: "secondary" */)); const LivestreamCurrentPage = lazyImport(() => import('page/livestreamCurrent' /* webpackChunkName: "secondary" */)); +const OwnComments = lazyImport(() => import('page/ownComments' /* webpackChunkName: "ownComments" */)); const PasswordResetPage = lazyImport(() => import('page/passwordReset' /* webpackChunkName: "secondary" */)); const PasswordSetPage = lazyImport(() => import('page/passwordSet' /* webpackChunkName: "secondary" */)); const PublishPage = lazyImport(() => import('page/publish' /* webpackChunkName: "secondary" */)); @@ -329,6 +330,7 @@ function AppRouter(props: Props) { + diff --git a/ui/component/settingAccount/index.js b/ui/component/settingAccount/index.js index 83d77af76..0644ceaf6 100644 --- a/ui/component/settingAccount/index.js +++ b/ui/component/settingAccount/index.js @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import { doWalletStatus, selectWalletIsEncrypted } from 'lbry-redux'; +import { doWalletStatus, selectMyChannelClaims, selectWalletIsEncrypted } from 'lbry-redux'; import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectLanguage } from 'redux/selectors/settings'; @@ -9,6 +9,7 @@ const select = (state) => ({ isAuthenticated: selectUserVerifiedEmail(state), walletEncrypted: selectWalletIsEncrypted(state), user: selectUser(state), + myChannels: selectMyChannelClaims(state), language: selectLanguage(state), }); diff --git a/ui/component/settingAccount/view.jsx b/ui/component/settingAccount/view.jsx index f39262379..812e3818c 100644 --- a/ui/component/settingAccount/view.jsx +++ b/ui/component/settingAccount/view.jsx @@ -15,12 +15,13 @@ type Props = { isAuthenticated: boolean, walletEncrypted: boolean, user: User, + myChannels: ?Array, // --- perform --- doWalletStatus: () => void, }; export default function SettingAccount(props: Props) { - const { isAuthenticated, walletEncrypted, user, doWalletStatus } = props; + const { isAuthenticated, walletEncrypted, user, myChannels, doWalletStatus } = props; const [storedPassword, setStoredPassword] = React.useState(false); // Determine if password is stored. @@ -92,6 +93,17 @@ export default function SettingAccount(props: Props) { )} {/* @endif */} + + {myChannels && ( + +