209 lines
6.7 KiB
React
209 lines
6.7 KiB
React
|
// @flow
|
||
|
import React from 'react';
|
||
|
import Button from 'component/button';
|
||
|
import ChannelSelector from 'component/channelSelector';
|
||
|
import ClaimPreview from 'component/claimPreview';
|
||
|
import Comment from 'component/comment';
|
||
|
import Card from 'component/common/card';
|
||
|
import Empty from 'component/common/empty';
|
||
|
import Page from 'component/page';
|
||
|
import Spinner from 'component/spinner';
|
||
|
import { COMMENT_PAGE_SIZE_TOP_LEVEL } from 'constants/comment';
|
||
|
import * as ICONS from 'constants/icons';
|
||
|
import useFetched from 'effects/use-fetched';
|
||
|
import debounce from 'util/debounce';
|
||
|
|
||
|
function scaleToDevicePixelRatio(value) {
|
||
|
const devicePixelRatio = window.devicePixelRatio || 1.0;
|
||
|
if (devicePixelRatio < 1.0) {
|
||
|
return Math.ceil(value / devicePixelRatio);
|
||
|
}
|
||
|
return Math.ceil(value * devicePixelRatio);
|
||
|
}
|
||
|
|
||
|
type Props = {
|
||
|
activeChannelClaim: ?ChannelClaim,
|
||
|
allComments: Array<Comment>,
|
||
|
totalComments: number,
|
||
|
topLevelTotalPages: number,
|
||
|
isFetchingComments: boolean,
|
||
|
claimsById: any,
|
||
|
doCommentReset: (claimId: string) => void,
|
||
|
doCommentListOwn: (channelId: string, page: number, pageSize: number) => void,
|
||
|
};
|
||
|
|
||
|
export default function OwnComments(props: Props) {
|
||
|
const {
|
||
|
activeChannelClaim,
|
||
|
allComments,
|
||
|
totalComments,
|
||
|
isFetchingComments,
|
||
|
claimsById,
|
||
|
doCommentReset,
|
||
|
doCommentListOwn,
|
||
|
} = props;
|
||
|
const spinnerRef = React.useRef();
|
||
|
const [page, setPage] = React.useState(0);
|
||
|
const [activeChannelId, setActiveChannelId] = React.useState('');
|
||
|
|
||
|
// Since we are sharing the key for Discussion and MyComments, don't show
|
||
|
// the list until we've gone through the initial reset.
|
||
|
const wasResetAndReady = useFetched(isFetchingComments);
|
||
|
|
||
|
const totalPages = Math.ceil(totalComments / COMMENT_PAGE_SIZE_TOP_LEVEL);
|
||
|
const moreBelow = page < totalPages;
|
||
|
|
||
|
function getCommentsElem(comments) {
|
||
|
return comments.map((comment) => {
|
||
|
const contentClaim = claimsById[comment.claim_id];
|
||
|
const isChannel = contentClaim && contentClaim.value_type === 'channel';
|
||
|
|
||
|
return (
|
||
|
<div key={comment.comment_id} className="comments-own card__main-actions">
|
||
|
<div className="section__actions">
|
||
|
<div className="comments-own--claim">
|
||
|
{contentClaim && (
|
||
|
<ClaimPreview
|
||
|
uri={contentClaim.canonical_url}
|
||
|
searchParams={{
|
||
|
...(isChannel ? { view: 'discussion' } : {}),
|
||
|
}}
|
||
|
hideActions
|
||
|
hideMenu
|
||
|
properties={() => null}
|
||
|
/>
|
||
|
)}
|
||
|
{!contentClaim && <Empty text={__('Content or channel was deleted.')} />}
|
||
|
</div>
|
||
|
<Comment
|
||
|
isTopLevel
|
||
|
hideActions
|
||
|
authorUri={comment.channel_url}
|
||
|
author={comment.channel_name}
|
||
|
commentId={comment.comment_id}
|
||
|
message={comment.comment}
|
||
|
timePosted={comment.timestamp * 1000}
|
||
|
commentIsMine
|
||
|
supportAmount={comment.support_amount}
|
||
|
numDirectReplies={0} // Don't show replies here
|
||
|
isModerator={comment.is_moderator}
|
||
|
isGlobalMod={comment.is_global_mod}
|
||
|
isFiat={comment.is_fiat}
|
||
|
/>
|
||
|
</div>
|
||
|
</div>
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Active channel changed
|
||
|
React.useEffect(() => {
|
||
|
if (activeChannelClaim && activeChannelClaim.claim_id !== activeChannelId) {
|
||
|
setActiveChannelId(activeChannelClaim.claim_id);
|
||
|
setPage(0);
|
||
|
}
|
||
|
}, [activeChannelClaim, activeChannelId]);
|
||
|
|
||
|
// Reset comments
|
||
|
React.useEffect(() => {
|
||
|
if (page === 0 && activeChannelId) {
|
||
|
doCommentReset(activeChannelId);
|
||
|
setPage(1);
|
||
|
}
|
||
|
}, [page, activeChannelId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||
|
|
||
|
// Fetch own comments
|
||
|
React.useEffect(() => {
|
||
|
if (page !== 0 && activeChannelId) {
|
||
|
doCommentListOwn(activeChannelId, page, COMMENT_PAGE_SIZE_TOP_LEVEL);
|
||
|
}
|
||
|
}, [page]); // eslint-disable-line react-hooks/exhaustive-deps
|
||
|
|
||
|
// Infinite scroll
|
||
|
React.useEffect(() => {
|
||
|
function shouldFetchNextPage(page, topLevelTotalPages, window, document, yPrefetchPx = 1000) {
|
||
|
if (!spinnerRef || !spinnerRef.current) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const rect = spinnerRef.current.getBoundingClientRect(); // $FlowFixMe
|
||
|
const windowH = window.innerHeight || document.documentElement.clientHeight; // $FlowFixMe
|
||
|
const windowW = window.innerWidth || document.documentElement.clientWidth; // $FlowFixMe
|
||
|
|
||
|
const isApproachingViewport = yPrefetchPx !== 0 && rect.top < windowH + scaleToDevicePixelRatio(yPrefetchPx);
|
||
|
|
||
|
const isInViewport =
|
||
|
rect.width > 0 &&
|
||
|
rect.height > 0 &&
|
||
|
rect.bottom >= 0 &&
|
||
|
rect.right >= 0 &&
|
||
|
// $FlowFixMe
|
||
|
rect.top <= windowH &&
|
||
|
// $FlowFixMe
|
||
|
rect.left <= windowW;
|
||
|
|
||
|
return (isInViewport || isApproachingViewport) && page < topLevelTotalPages;
|
||
|
}
|
||
|
|
||
|
const handleCommentScroll = debounce(() => {
|
||
|
if (shouldFetchNextPage(page, totalPages, window, document)) {
|
||
|
setPage(page + 1);
|
||
|
}
|
||
|
}, 200);
|
||
|
|
||
|
if (!isFetchingComments && moreBelow && spinnerRef && spinnerRef.current) {
|
||
|
if (shouldFetchNextPage(page, totalPages, window, document, 0)) {
|
||
|
setPage(page + 1);
|
||
|
} else {
|
||
|
window.addEventListener('scroll', handleCommentScroll);
|
||
|
return () => window.removeEventListener('scroll', handleCommentScroll);
|
||
|
}
|
||
|
}
|
||
|
}, [page, spinnerRef, isFetchingComments, moreBelow, totalPages]);
|
||
|
|
||
|
// **************************************************************************
|
||
|
// **************************************************************************
|
||
|
|
||
|
if (!activeChannelClaim) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<Page noFooter noSideNavigation settingsPage backout={{ title: __('Your comments'), backLabel: __('Back') }}>
|
||
|
<ChannelSelector hideAnon />
|
||
|
<Card
|
||
|
isBodyList
|
||
|
title={
|
||
|
totalComments > 0
|
||
|
? totalComments === 1
|
||
|
? __('1 comment')
|
||
|
: __('%total_comments% comments', { total_comments: totalComments })
|
||
|
: isFetchingComments
|
||
|
? ''
|
||
|
: __('No comments')
|
||
|
}
|
||
|
titleActions={
|
||
|
<Button
|
||
|
button="alt"
|
||
|
icon={ICONS.REFRESH}
|
||
|
title={__('Refresh')}
|
||
|
onClick={() => {
|
||
|
setPage(0);
|
||
|
}}
|
||
|
/>
|
||
|
}
|
||
|
body={
|
||
|
<>
|
||
|
{wasResetAndReady && <ul className="comments">{allComments && getCommentsElem(allComments)}</ul>}
|
||
|
{(isFetchingComments || moreBelow) && (
|
||
|
<div className="main--empty" ref={spinnerRef}>
|
||
|
<Spinner type="small" />
|
||
|
</div>
|
||
|
)}
|
||
|
</>
|
||
|
}
|
||
|
/>
|
||
|
</Page>
|
||
|
);
|
||
|
}
|