diff --git a/static/app-strings.json b/static/app-strings.json index c09e9fade..65b4690bd 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -31,6 +31,7 @@ "Loop": "Loop", "Report content": "Report content", "Report Content": "Report Content", + "Report comment": "Report comment", "Languages": "Languages", "Media Type": "Media Type", "License": "License", @@ -411,6 +412,8 @@ "Loading": "Loading", "This file is in your library.": "This file is in your library.", "Invalid claim ID": "Invalid claim ID", + "Invalid comment ID": "Invalid comment ID", + "Missing 'claimId' parameter.": "Missing 'claimId' parameter.", "View Tag": "View Tag", "Hide": "Hide", "Close": "Close", diff --git a/ui/component/reportContent/index.js b/ui/component/reportContent/index.js index c1134fa81..1c7238cff 100644 --- a/ui/component/reportContent/index.js +++ b/ui/component/reportContent/index.js @@ -1,6 +1,8 @@ import { connect } from 'react-redux'; +import { doCommentById } from 'redux/actions/comments'; import { doReportContent } from 'redux/actions/reportContent'; import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app'; +import { selectCommentForCommentId } from 'redux/selectors/comments'; import { selectIsReportingContent, selectReportContentError } from 'redux/selectors/reportContent'; import { doClaimSearch } from 'redux/actions/claims'; import { selectClaimForClaimId } from 'redux/selectors/claims'; @@ -11,19 +13,23 @@ const select = (state, props) => { const { search } = props.location; const urlParams = new URLSearchParams(search); const claimId = urlParams.get('claimId'); + const commentId = urlParams.get('commentId'); return { + claimId, + commentId, isReporting: selectIsReportingContent(state), error: selectReportContentError(state), activeChannelClaim: selectActiveChannelClaim(state), incognito: selectIncognito(state), - claimId: claimId, claim: selectClaimForClaimId(state, claimId), + comment: selectCommentForCommentId(state, commentId), }; }; const perform = { doClaimSearch, + doCommentById, doReportContent, }; diff --git a/ui/component/reportContent/view.jsx b/ui/component/reportContent/view.jsx index d936c47d7..b5cc39a78 100644 --- a/ui/component/reportContent/view.jsx +++ b/ui/component/reportContent/view.jsx @@ -4,11 +4,13 @@ import Button from 'component/button'; import { Form, FormField } from 'component/common/form'; import Card from 'component/common/card'; import ClaimPreview from 'component/claimPreview'; +import Comment from 'component/comment'; import ChannelSelector from 'component/channelSelector'; import Spinner from 'component/spinner'; import ErrorText from 'component/common/error-text'; import Icon from 'component/common/icon'; import { COUNTRIES } from 'util/country'; +import { URL } from 'config'; import { EMAIL_REGEX } from 'constants/email'; import { FF_MAX_CHARS_REPORT_CONTENT_DETAILS, @@ -59,23 +61,39 @@ const DEFAULT_INPUT_DATA = { type Props = { // --- urlParams --- claimId: string, + commentId?: string, // --- redux --- claim: StreamClaim, + comment?: Comment, isReporting: boolean, error: string, activeChannelClaim: ?ChannelClaim, incognito: boolean, doClaimSearch: (any) => Promise, + doCommentById: (string, boolean) => Promise, doReportContent: (string, string) => void, }; export default function ReportContent(props: Props) { - const { isReporting, error, activeChannelClaim, incognito, claimId, claim, doClaimSearch, doReportContent } = props; + const { + isReporting, + error, + activeChannelClaim, + incognito, + claimId, + commentId, + claim, + comment, + doClaimSearch, + doCommentById, + doReportContent, + } = props; const [input, setInput] = React.useState({ ...DEFAULT_INPUT_DATA }); const [page, setPage] = React.useState(PAGE_TYPE); const [timestampInvalid, setTimestampInvalid] = React.useState(false); const [isResolvingClaim, setIsResolvingClaim] = React.useState(false); + const [isResolvingComment, setIsResolvingComment] = React.useState(false); const { goBack } = useHistory(); // Resolve claim if URL is entered directly or if page is reloaded. @@ -93,6 +111,16 @@ export default function ReportContent(props: Props) { } }, [claim, claimId, doClaimSearch]); + // Fetch comment if `commentId` is provided + React.useEffect(() => { + if (commentId) { + setIsResolvingComment(true); + doCommentById(commentId, false).finally(() => { + setIsResolvingComment(false); + }); + } + }, [commentId, doCommentById]); + // On mount, pause player and get the timestamp, if applicable. React.useEffect(() => { if (window.player) { @@ -109,6 +137,20 @@ export default function ReportContent(props: Props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + function getCommentUrl(contentClaim, commentId) { + if (commentId) { + if (contentClaim && contentClaim.canonical_url) { + const canonical = contentClaim.canonical_url.replace(/#/g, ':'); + const commentUrl = `${canonical.replace('lbry://', `${URL}/`)}?lc=${commentId}`; + return commentUrl + '\n\n'; + } else { + return commentId + '\n\n'; + } + } else { + return ''; + } + } + function onSubmit() { if (!claim) { if (isDev) throw new Error('ReportContent::onSubmit -- null claim'); @@ -168,8 +210,8 @@ export default function ReportContent(props: Props) { default: pushParam(params, 'type', input.type); pushParam(params, 'category', input.category); - pushParam(params, 'additional_details', input.additionalDetails); - if (includeTimestamp(claim)) { + pushParam(params, 'additional_details', getCommentUrl(claim, commentId) + input.additionalDetails); + if (includeTimestamp(claim, commentId)) { pushParam(params, 'timestamp', input.timestamp); } break; @@ -216,7 +258,7 @@ export default function ReportContent(props: Props) { return false; } default: - return (!includeTimestamp(claim) || isTimestampValid(input.timestamp)) && input.additionalDetails; + return (!includeTimestamp(claim, commentId) || isTimestampValid(input.timestamp)) && input.additionalDetails; } } @@ -253,9 +295,11 @@ export default function ReportContent(props: Props) { } } - function includeTimestamp(claim: StreamClaim) { + function includeTimestamp(claim: StreamClaim, commentId: ?string) { return ( - claim.value_type === 'stream' && (claim.value.stream_type === 'video' || claim.value.stream_type === 'audio') + !commentId && + claim.value_type === 'stream' && + (claim.value.stream_type === 'video' || claim.value.stream_type === 'audio') ); } @@ -276,6 +320,7 @@ export default function ReportContent(props: Props) { key={x} label={__(String(x))} checked={x === input.type} + disabled={x === REPORT_API.INFRINGES_MY_RIGHTS && commentId} onChange={() => updateInput('type', x)} /> ); @@ -461,7 +506,7 @@ export default function ReportContent(props: Props) { default: body = ( <> - {includeTimestamp(claim) && ( + {includeTimestamp(claim, commentId) && (
+ ) : null; + } + + function getCommentPreviews(comment: ?Comment) { + return comment ? ( +
+ +
+ ) : null; + } + + // ************************************************************************** + // ************************************************************************** + + // --- Report comment --- + if (commentId) { + if (!claimId) { + return ; + } else { + return ( +
+ : __('Invalid comment ID')} + /> + + ); + } + } + + // --- Report content --- + if (claimId) { + return ( +
+ : __('Invalid claim ID')} + /> + ); } - return ( -
- : __('Invalid claim ID')} - /> - - ); + // --- Invalid --- + return ; }