ReportContent: add ?commentId
support (#1826)
## Ticket 1822 ## Changes - Add `?commentId=` support in the URL. - `claimId` remains a required parameter so that we can derive the comment URL. - The backend will know it's a comment report when both parameters are present. - The comment URL is added to the top of `additional_details`. - The backend rejects if `additional_details` is provided for DMCA. Grayed out DMCA for the case of reporting comments.
This commit is contained in:
parent
7b89ad8c14
commit
68a4697c7d
3 changed files with 106 additions and 18 deletions
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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<any>,
|
||||
doCommentById: (string, boolean) => Promise<any>,
|
||||
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) && (
|
||||
<div className="section">
|
||||
<FormField
|
||||
type="text"
|
||||
|
@ -706,20 +751,54 @@ export default function ReportContent(props: Props) {
|
|||
}
|
||||
|
||||
function getClaimPreview(claim: StreamClaim) {
|
||||
return (
|
||||
return claim ? (
|
||||
<div className="section">
|
||||
<ClaimPreview uri={claim.permanent_url} hideMenu hideActions nonClickable type="small" />
|
||||
</div>
|
||||
);
|
||||
) : null;
|
||||
}
|
||||
|
||||
function getCommentPreviews(comment: ?Comment) {
|
||||
return comment ? (
|
||||
<div className="section non-clickable">
|
||||
<Comment comment={comment} isTopLevel hideActions hideContextMenu />
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// **************************************************************************
|
||||
|
||||
// --- Report comment ---
|
||||
if (commentId) {
|
||||
if (!claimId) {
|
||||
return <Card title={__('Report comment')} subtitle={__("Missing 'claimId' parameter.")} />;
|
||||
} else {
|
||||
return (
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Card
|
||||
title={__('Report comment')}
|
||||
subtitle={getCommentPreviews(comment)}
|
||||
actions={comment ? getActionElem() : isResolvingComment ? <Spinner /> : __('Invalid comment ID')}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Report content ---
|
||||
if (claimId) {
|
||||
return (
|
||||
<Form onSubmit={onSubmit}>
|
||||
<Card
|
||||
title={claim && claim.value_type === 'channel' ? __('Report channel') : __('Report content')}
|
||||
subtitle={claim ? getClaimPreview(claim) : null}
|
||||
subtitle={getClaimPreview(claim)}
|
||||
actions={claim ? getActionElem() : isResolvingClaim ? <Spinner /> : __('Invalid claim ID')}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
// --- Invalid ---
|
||||
return <Card title={__('Report content')} subtitle={__('Invalid parameters.')} />;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue