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",
|
"Loop": "Loop",
|
||||||
"Report content": "Report content",
|
"Report content": "Report content",
|
||||||
"Report Content": "Report Content",
|
"Report Content": "Report Content",
|
||||||
|
"Report comment": "Report comment",
|
||||||
"Languages": "Languages",
|
"Languages": "Languages",
|
||||||
"Media Type": "Media Type",
|
"Media Type": "Media Type",
|
||||||
"License": "License",
|
"License": "License",
|
||||||
|
@ -411,6 +412,8 @@
|
||||||
"Loading": "Loading",
|
"Loading": "Loading",
|
||||||
"This file is in your library.": "This file is in your library.",
|
"This file is in your library.": "This file is in your library.",
|
||||||
"Invalid claim ID": "Invalid claim ID",
|
"Invalid claim ID": "Invalid claim ID",
|
||||||
|
"Invalid comment ID": "Invalid comment ID",
|
||||||
|
"Missing 'claimId' parameter.": "Missing 'claimId' parameter.",
|
||||||
"View Tag": "View Tag",
|
"View Tag": "View Tag",
|
||||||
"Hide": "Hide",
|
"Hide": "Hide",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { doCommentById } from 'redux/actions/comments';
|
||||||
import { doReportContent } from 'redux/actions/reportContent';
|
import { doReportContent } from 'redux/actions/reportContent';
|
||||||
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||||
|
import { selectCommentForCommentId } from 'redux/selectors/comments';
|
||||||
import { selectIsReportingContent, selectReportContentError } from 'redux/selectors/reportContent';
|
import { selectIsReportingContent, selectReportContentError } from 'redux/selectors/reportContent';
|
||||||
import { doClaimSearch } from 'redux/actions/claims';
|
import { doClaimSearch } from 'redux/actions/claims';
|
||||||
import { selectClaimForClaimId } from 'redux/selectors/claims';
|
import { selectClaimForClaimId } from 'redux/selectors/claims';
|
||||||
|
@ -11,19 +13,23 @@ const select = (state, props) => {
|
||||||
const { search } = props.location;
|
const { search } = props.location;
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const claimId = urlParams.get('claimId');
|
const claimId = urlParams.get('claimId');
|
||||||
|
const commentId = urlParams.get('commentId');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
claimId,
|
||||||
|
commentId,
|
||||||
isReporting: selectIsReportingContent(state),
|
isReporting: selectIsReportingContent(state),
|
||||||
error: selectReportContentError(state),
|
error: selectReportContentError(state),
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
incognito: selectIncognito(state),
|
incognito: selectIncognito(state),
|
||||||
claimId: claimId,
|
|
||||||
claim: selectClaimForClaimId(state, claimId),
|
claim: selectClaimForClaimId(state, claimId),
|
||||||
|
comment: selectCommentForCommentId(state, commentId),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const perform = {
|
const perform = {
|
||||||
doClaimSearch,
|
doClaimSearch,
|
||||||
|
doCommentById,
|
||||||
doReportContent,
|
doReportContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,13 @@ import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
|
import Comment from 'component/comment';
|
||||||
import ChannelSelector from 'component/channelSelector';
|
import ChannelSelector from 'component/channelSelector';
|
||||||
import Spinner from 'component/spinner';
|
import Spinner from 'component/spinner';
|
||||||
import ErrorText from 'component/common/error-text';
|
import ErrorText from 'component/common/error-text';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import { COUNTRIES } from 'util/country';
|
import { COUNTRIES } from 'util/country';
|
||||||
|
import { URL } from 'config';
|
||||||
import { EMAIL_REGEX } from 'constants/email';
|
import { EMAIL_REGEX } from 'constants/email';
|
||||||
import {
|
import {
|
||||||
FF_MAX_CHARS_REPORT_CONTENT_DETAILS,
|
FF_MAX_CHARS_REPORT_CONTENT_DETAILS,
|
||||||
|
@ -59,23 +61,39 @@ const DEFAULT_INPUT_DATA = {
|
||||||
type Props = {
|
type Props = {
|
||||||
// --- urlParams ---
|
// --- urlParams ---
|
||||||
claimId: string,
|
claimId: string,
|
||||||
|
commentId?: string,
|
||||||
// --- redux ---
|
// --- redux ---
|
||||||
claim: StreamClaim,
|
claim: StreamClaim,
|
||||||
|
comment?: Comment,
|
||||||
isReporting: boolean,
|
isReporting: boolean,
|
||||||
error: string,
|
error: string,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
incognito: boolean,
|
incognito: boolean,
|
||||||
doClaimSearch: (any) => Promise<any>,
|
doClaimSearch: (any) => Promise<any>,
|
||||||
|
doCommentById: (string, boolean) => Promise<any>,
|
||||||
doReportContent: (string, string) => void,
|
doReportContent: (string, string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ReportContent(props: Props) {
|
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 [input, setInput] = React.useState({ ...DEFAULT_INPUT_DATA });
|
||||||
const [page, setPage] = React.useState(PAGE_TYPE);
|
const [page, setPage] = React.useState(PAGE_TYPE);
|
||||||
const [timestampInvalid, setTimestampInvalid] = React.useState(false);
|
const [timestampInvalid, setTimestampInvalid] = React.useState(false);
|
||||||
const [isResolvingClaim, setIsResolvingClaim] = React.useState(false);
|
const [isResolvingClaim, setIsResolvingClaim] = React.useState(false);
|
||||||
|
const [isResolvingComment, setIsResolvingComment] = React.useState(false);
|
||||||
const { goBack } = useHistory();
|
const { goBack } = useHistory();
|
||||||
|
|
||||||
// Resolve claim if URL is entered directly or if page is reloaded.
|
// 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]);
|
}, [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.
|
// On mount, pause player and get the timestamp, if applicable.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (window.player) {
|
if (window.player) {
|
||||||
|
@ -109,6 +137,20 @@ export default function ReportContent(props: Props) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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() {
|
function onSubmit() {
|
||||||
if (!claim) {
|
if (!claim) {
|
||||||
if (isDev) throw new Error('ReportContent::onSubmit -- null claim');
|
if (isDev) throw new Error('ReportContent::onSubmit -- null claim');
|
||||||
|
@ -168,8 +210,8 @@ export default function ReportContent(props: Props) {
|
||||||
default:
|
default:
|
||||||
pushParam(params, 'type', input.type);
|
pushParam(params, 'type', input.type);
|
||||||
pushParam(params, 'category', input.category);
|
pushParam(params, 'category', input.category);
|
||||||
pushParam(params, 'additional_details', input.additionalDetails);
|
pushParam(params, 'additional_details', getCommentUrl(claim, commentId) + input.additionalDetails);
|
||||||
if (includeTimestamp(claim)) {
|
if (includeTimestamp(claim, commentId)) {
|
||||||
pushParam(params, 'timestamp', input.timestamp);
|
pushParam(params, 'timestamp', input.timestamp);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -216,7 +258,7 @@ export default function ReportContent(props: Props) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
default:
|
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 (
|
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}
|
key={x}
|
||||||
label={__(String(x))}
|
label={__(String(x))}
|
||||||
checked={x === input.type}
|
checked={x === input.type}
|
||||||
|
disabled={x === REPORT_API.INFRINGES_MY_RIGHTS && commentId}
|
||||||
onChange={() => updateInput('type', x)}
|
onChange={() => updateInput('type', x)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -461,7 +506,7 @@ export default function ReportContent(props: Props) {
|
||||||
default:
|
default:
|
||||||
body = (
|
body = (
|
||||||
<>
|
<>
|
||||||
{includeTimestamp(claim) && (
|
{includeTimestamp(claim, commentId) && (
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<FormField
|
<FormField
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -706,20 +751,54 @@ export default function ReportContent(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClaimPreview(claim: StreamClaim) {
|
function getClaimPreview(claim: StreamClaim) {
|
||||||
return (
|
return claim ? (
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<ClaimPreview uri={claim.permanent_url} hideMenu hideActions nonClickable type="small" />
|
<ClaimPreview uri={claim.permanent_url} hideMenu hideActions nonClickable type="small" />
|
||||||
</div>
|
</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 (
|
return (
|
||||||
<Form onSubmit={onSubmit}>
|
<Form onSubmit={onSubmit}>
|
||||||
<Card
|
<Card
|
||||||
title={claim && claim.value_type === 'channel' ? __('Report channel') : __('Report content')}
|
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')}
|
actions={claim ? getActionElem() : isResolvingClaim ? <Spinner /> : __('Invalid claim ID')}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Invalid ---
|
||||||
|
return <Card title={__('Report content')} subtitle={__('Invalid parameters.')} />;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue