1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 04:46:40 +07:00

moved comments load to hook

This commit is contained in:
Fedor Katurov 2021-09-20 11:56:20 +07:00
parent 5e1e575ee3
commit 639c952c2c
11 changed files with 184 additions and 59 deletions

View file

@ -8,16 +8,27 @@ import { Footer } from '~/components/main/Footer';
import { Card } from '~/components/containers/Card'; import { Card } from '~/components/containers/Card';
import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
import { selectAuthUser } from '~/redux/auth/selectors'; import { selectAuthUser } from '~/redux/auth/selectors';
import { IComment, INode } from '~/redux/types'; import { IComment, IFile, INode } from '~/redux/types';
interface IProps { interface IProps {
isLoadingComments: boolean; isLoadingComments: boolean;
commentCount: number; commentCount: number;
node: INode; node: INode;
comments: IComment[]; comments: IComment[];
onDelete: (id: IComment['id'], locked: boolean) => void;
onLoadMoreComments: () => void;
onShowPhotoswipe: (images: IFile[], index: number) => void;
} }
const BorisComments: FC<IProps> = ({ isLoadingComments, node, commentCount, comments }) => { const BorisComments: FC<IProps> = ({
node,
commentCount,
comments,
isLoadingComments,
onLoadMoreComments,
onDelete,
onShowPhotoswipe,
}) => {
const user = useShallowSelect(selectAuthUser); const user = useShallowSelect(selectAuthUser);
return ( return (
@ -28,7 +39,15 @@ const BorisComments: FC<IProps> = ({ isLoadingComments, node, commentCount, comm
{isLoadingComments ? ( {isLoadingComments ? (
<NodeNoComments is_loading count={7} /> <NodeNoComments is_loading count={7} />
) : ( ) : (
<NodeComments comments={comments} count={commentCount} user={user} order="ASC" /> <NodeComments
comments={comments}
count={commentCount}
user={user}
order="ASC"
onLoadMoreComments={onLoadMoreComments}
onDelete={onDelete}
onShowPhotoswipe={onShowPhotoswipe}
/>
)} )}
</Group> </Group>

View file

@ -1,6 +1,6 @@
import React, { FC, HTMLAttributes, memo } from 'react'; import React, { FC, HTMLAttributes, memo } from 'react';
import { CommentWrapper } from '~/components/containers/CommentWrapper'; import { CommentWrapper } from '~/components/containers/CommentWrapper';
import { IComment, ICommentGroup } from '~/redux/types'; import { IComment, ICommentGroup, IFile } from '~/redux/types';
import { CommentContent } from '~/components/comment/CommentContent'; import { CommentContent } from '~/components/comment/CommentContent';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { CommendDeleted } from '../../node/CommendDeleted'; import { CommendDeleted } from '../../node/CommendDeleted';
@ -13,7 +13,7 @@ type IProps = HTMLAttributes<HTMLDivElement> & {
is_same?: boolean; is_same?: boolean;
can_edit?: boolean; can_edit?: boolean;
onDelete: (id: IComment['id'], isLocked: boolean) => void; onDelete: (id: IComment['id'], isLocked: boolean) => void;
modalShowPhotoswipe: typeof MODAL_ACTIONS.modalShowPhotoswipe; modalShowPhotoswipe: (images: IFile[], index: number) => void;
}; };
const Comment: FC<IProps> = memo( const Comment: FC<IProps> = memo(

View file

@ -21,7 +21,7 @@ interface IProps {
comment: IComment; comment: IComment;
can_edit: boolean; can_edit: boolean;
onDelete: (id: IComment['id'], isLocked: boolean) => void; onDelete: (id: IComment['id'], isLocked: boolean) => void;
modalShowPhotoswipe: typeof MODAL_ACTIONS.modalShowPhotoswipe; modalShowPhotoswipe: (images: IFile[], index: number) => void;
} }
const CommentContent: FC<IProps> = memo(({ comment, can_edit, onDelete, modalShowPhotoswipe }) => { const CommentContent: FC<IProps> = memo(({ comment, can_edit, onDelete, modalShowPhotoswipe }) => {

View file

@ -6,7 +6,7 @@ import { NodeCommentsBlock } from '~/components/node/NodeCommentsBlock';
import { NodeCommentForm } from '~/components/node/NodeCommentForm'; import { NodeCommentForm } from '~/components/node/NodeCommentForm';
import { NodeRelatedBlock } from '~/components/node/NodeRelatedBlock'; import { NodeRelatedBlock } from '~/components/node/NodeRelatedBlock';
import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks'; import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
import { IComment, INode } from '~/redux/types'; import { IComment, IFile, INode } from '~/redux/types';
import { useUser } from '~/utils/hooks/user/userUser'; import { useUser } from '~/utils/hooks/user/userUser';
import { NodeTagsBlock } from '~/components/node/NodeTagsBlock'; import { NodeTagsBlock } from '~/components/node/NodeTagsBlock';
import { INodeRelated } from '~/redux/node/types'; import { INodeRelated } from '~/redux/node/types';
@ -21,6 +21,9 @@ interface IProps {
commentsCount: number; commentsCount: number;
isLoadingComments: boolean; isLoadingComments: boolean;
related: INodeRelated; related: INodeRelated;
onDeleteComment: (id: IComment['id'], locked: boolean) => void;
onLoadMoreComments: () => void;
onShowPhotoswipe: (images: IFile[], index: number) => void;
} }
const NodeBottomBlock: FC<IProps> = ({ const NodeBottomBlock: FC<IProps> = ({
@ -31,6 +34,9 @@ const NodeBottomBlock: FC<IProps> = ({
commentsCount, commentsCount,
commentsOrder, commentsOrder,
related, related,
onDeleteComment,
onLoadMoreComments,
onShowPhotoswipe,
}) => { }) => {
const { inline } = useNodeBlocks(node, isLoading); const { inline } = useNodeBlocks(node, isLoading);
const { is_user } = useUser(); const { is_user } = useUser();
@ -53,6 +59,9 @@ const NodeBottomBlock: FC<IProps> = ({
count={commentsCount} count={commentsCount}
order={commentsOrder} order={commentsOrder}
node={node} node={node}
onDelete={onDeleteComment}
onLoadMoreComments={onLoadMoreComments}
onShowPhotoswipe={onShowPhotoswipe}
/> />
{is_user && !isLoading && <NodeCommentForm nodeId={node?.id} />} {is_user && !isLoading && <NodeCommentForm nodeId={node?.id} />}

View file

@ -18,56 +18,58 @@ interface IProps {
count: INodeState['comment_count']; count: INodeState['comment_count'];
user: IUser; user: IUser;
order?: 'ASC' | 'DESC'; order?: 'ASC' | 'DESC';
onDelete: (id: IComment['id'], locked: boolean) => void;
onLoadMoreComments: () => void;
onShowPhotoswipe: (images: IFile[], index: number) => void;
} }
const NodeComments: FC<IProps> = memo(({ comments, user, count = 0, order = 'DESC' }) => { const NodeComments: FC<IProps> = memo(
const dispatch = useDispatch(); ({
const left = useMemo(() => Math.max(0, count - comments.length), [comments, count]); onLoadMoreComments,
onDelete,
onShowPhotoswipe,
comments,
user,
count = 0,
order = 'DESC',
}) => {
const left = useMemo(() => Math.max(0, count - comments.length), [comments, count]);
const groupped: ICommentGroup[] = useMemo( const groupped: ICommentGroup[] = useMemo(
() => (order === 'DESC' ? [...comments].reverse() : comments).reduce(groupCommentsByUser, []), () => (order === 'DESC' ? [...comments].reverse() : comments).reduce(groupCommentsByUser, []),
[comments, order] [comments, order]
); );
const onDelete = useCallback( const more = useMemo(
(id: IComment['id'], locked: boolean) => dispatch(nodeLockComment(id, locked)), () =>
[dispatch] left > 0 && (
); <div className={styles.more} onClick={onLoadMoreComments}>
const onLoadMoreComments = useCallback(() => dispatch(nodeLoadMoreComments()), [dispatch]); Показать ещё{' '}
const onShowPhotoswipe = useCallback( {plural(Math.min(left, COMMENTS_DISPLAY), 'комментарий', 'комментария', 'комментариев')}
(images: IFile[], index: number) => dispatch(modalShowPhotoswipe(images, index)), {left > COMMENTS_DISPLAY ? ` из ${left} оставшихся` : ''}
[dispatch] </div>
); ),
[left, onLoadMoreComments]
);
const more = useMemo( return (
() => <div className={styles.wrap}>
left > 0 && ( {order === 'DESC' && more}
<div className={styles.more} onClick={onLoadMoreComments}>
Показать ещё{' '}
{plural(Math.min(left, COMMENTS_DISPLAY), 'комментарий', 'комментария', 'комментариев')}
{left > COMMENTS_DISPLAY ? ` из ${left} оставшихся` : ''}
</div>
),
[left, onLoadMoreComments]
);
return ( {groupped.map(group => (
<div className={styles.wrap}> <Comment
{order === 'DESC' && more} key={group.ids.join()}
comment_group={group}
can_edit={canEditComment(group, user)}
onDelete={onDelete}
modalShowPhotoswipe={onShowPhotoswipe}
/>
))}
{groupped.map(group => ( {order === 'ASC' && more}
<Comment </div>
key={group.ids.join()} );
comment_group={group} }
can_edit={canEditComment(group, user)} );
onDelete={onDelete}
modalShowPhotoswipe={onShowPhotoswipe}
/>
))}
{order === 'ASC' && more}
</div>
);
});
export { NodeComments }; export { NodeComments };

View file

@ -1,7 +1,7 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { NodeNoComments } from '~/components/node/NodeNoComments'; import { NodeNoComments } from '~/components/node/NodeNoComments';
import { NodeComments } from '~/components/node/NodeComments'; import { NodeComments } from '~/components/node/NodeComments';
import { IComment, INode } from '~/redux/types'; import { IComment, IFile, INode } from '~/redux/types';
import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks'; import { useNodeBlocks } from '~/utils/hooks/node/useNodeBlocks';
import { useUser } from '~/utils/hooks/user/userUser'; import { useUser } from '~/utils/hooks/user/userUser';
@ -12,16 +12,36 @@ interface IProps {
count: number; count: number;
isLoading: boolean; isLoading: boolean;
isLoadingComments: boolean; isLoadingComments: boolean;
onDelete: (id: IComment['id'], locked: boolean) => void;
onLoadMoreComments: () => void;
onShowPhotoswipe: (images: IFile[], index: number) => void;
} }
const NodeCommentsBlock: FC<IProps> = ({ isLoading, isLoadingComments, node, comments, count }) => { const NodeCommentsBlock: FC<IProps> = ({
onLoadMoreComments,
onDelete,
onShowPhotoswipe,
isLoading,
isLoadingComments,
node,
comments,
count,
}) => {
const user = useUser(); const user = useUser();
const { inline } = useNodeBlocks(node, isLoading); const { inline } = useNodeBlocks(node, isLoading);
return isLoading || isLoadingComments || (!comments.length && !inline) ? ( return isLoading || isLoadingComments || (!comments.length && !inline) ? (
<NodeNoComments is_loading={isLoadingComments || isLoading} /> <NodeNoComments is_loading={isLoadingComments || isLoading} />
) : ( ) : (
<NodeComments count={count} comments={comments} user={user} order="DESC" /> <NodeComments
count={count}
comments={comments}
user={user}
order="DESC"
onLoadMoreComments={onLoadMoreComments}
onDelete={onDelete}
onShowPhotoswipe={onShowPhotoswipe}
/>
); );
}; };

View file

@ -1,5 +1,6 @@
import { IComment, INode } from '~/redux/types'; import { IComment, INode } from '~/redux/types';
import { ISocialProvider } from '~/redux/auth/types'; import { ISocialProvider } from '~/redux/auth/types';
import { COMMENTS_DISPLAY } from '~/redux/node/constants';
export const API = { export const API = {
BASE: process.env.REACT_APP_API_HOST, BASE: process.env.REACT_APP_API_HOST,
@ -28,6 +29,8 @@ export const API = {
GET_NODE: (id: number | string) => `/node/${id}`, GET_NODE: (id: number | string) => `/node/${id}`,
COMMENT: (id: INode['id']) => `/node/${id}/comment`, COMMENT: (id: INode['id']) => `/node/${id}/comment`,
COMMENT_INFINITE: (id: INode['id'], skip: number) =>
`/node/${id}/comment?take=${COMMENTS_DISPLAY}&skip=${skip}`,
RELATED: (id: INode['id']) => `/node/${id}/related`, RELATED: (id: INode['id']) => `/node/${id}/related`,
UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`, UPDATE_TAGS: (id: INode['id']) => `/node/${id}/tags`,
POST_LIKE: (id: INode['id']) => `/node/${id}/like`, POST_LIKE: (id: INode['id']) => `/node/${id}/like`,

View file

@ -115,6 +115,9 @@ const BorisLayout: FC<IProps> = () => {
commentCount={node.comment_count} commentCount={node.comment_count}
node={node.current} node={node.current}
comments={node.comments} comments={node.comments}
onDelete={console.log}
onLoadMoreComments={console.log}
onShowPhotoswipe={console.log}
/> />
</Switch> </Switch>
} }

View file

@ -1,4 +1,4 @@
import React, { FC, memo } from 'react'; import React, { FC, memo, useCallback } from 'react';
import { Route, RouteComponentProps } from 'react-router'; import { Route, RouteComponentProps } from 'react-router';
import { selectNode } from '~/redux/node/selectors'; import { selectNode } from '~/redux/node/selectors';
import { Card } from '~/components/containers/Card'; import { Card } from '~/components/containers/Card';
@ -20,6 +20,10 @@ import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { useNodeFetcher } from '~/utils/hooks/node/useNodeFetcher'; import { useNodeFetcher } from '~/utils/hooks/node/useNodeFetcher';
import { useNodeComments } from '~/utils/hooks/node/useNodeComments';
import { IFile } from '~/redux/types';
import { modalShowPhotoswipe } from '~/redux/modal/actions';
import { useDispatch } from 'react-redux';
type IProps = RouteComponentProps<{ id: string }> & {}; type IProps = RouteComponentProps<{ id: string }> & {};
@ -30,8 +34,16 @@ const NodeLayout: FC<IProps> = memo(
}, },
}) => { }) => {
const { node, isLoading } = useNodeFetcher(parseInt(id, 10)); const { node, isLoading } = useNodeFetcher(parseInt(id, 10));
const {
comments,
isLoading: isLoadingComments,
count: commentsCount,
onDelete,
onLoadMoreComments,
onShowPhotoswipe,
} = useNodeComments(parseInt(id, 10));
const { comments, comment_count, is_loading_comments, related } = useShallowSelect(selectNode); const { related } = useShallowSelect(selectNode);
useNodeCoverImage(node); useNodeCoverImage(node);
useScrollToTop([id]); useScrollToTop([id]);
@ -52,12 +64,15 @@ const NodeLayout: FC<IProps> = memo(
<NodeBottomBlock <NodeBottomBlock
node={node} node={node}
isLoadingComments={is_loading_comments}
comments={comments} comments={comments}
isLoading={isLoading} isLoading={isLoading}
commentsCount={comment_count} isLoadingComments={isLoadingComments}
commentsCount={commentsCount}
commentsOrder="DESC" commentsOrder="DESC"
related={related} related={related}
onShowPhotoswipe={onShowPhotoswipe}
onDeleteComment={onDelete}
onLoadMoreComments={onLoadMoreComments}
/> />
<Footer /> <Footer />

View file

@ -0,0 +1,54 @@
import { IComment, IFile, INode } from '~/redux/types';
import useSWRInfinite from 'swr/infinite';
import { ApiGetNodeCommentsResponse } from '~/redux/node/api';
import { api, cleanResult } from '~/utils/api';
import { API } from '~/constants/api';
import { useCallback, useMemo } from 'react';
import { COMMENTS_DISPLAY } from '~/redux/node/constants';
import { nodeLockComment } from '~/redux/node/actions';
import { useDispatch } from 'react-redux';
import { modalShowPhotoswipe } from '~/redux/modal/actions';
export const fetcher = (url: string) => api.get<ApiGetNodeCommentsResponse>(url).then(cleanResult);
export const useNodeComments = (id: INode['id']) => {
const dispatch = useDispatch();
const getKey = useCallback(
(pageIndex, previousPageData) => {
if (previousPageData && !previousPageData?.comments?.length) return null;
return API.NODE.COMMENT_INFINITE(id, pageIndex * COMMENTS_DISPLAY);
},
[id]
);
const { data, error, isValidating, size, setSize } = useSWRInfinite(getKey, fetcher);
const comments = useMemo<IComment[]>(
() => (data || []).reduce((acc, { comments }) => [...acc, ...comments], [] as IComment[]),
[data]
);
const count = useMemo<number>(() => {
if (!data) {
return 0;
}
return data[data.length - 1].comment_count || 0;
}, [data]);
const isLoading = !data && !isValidating;
const onDelete = useCallback(
(id: IComment['id'], locked: boolean) => dispatch(nodeLockComment(id, locked)),
[dispatch]
);
const onLoadMoreComments = useCallback(() => setSize(size + 1), [size, setSize]);
const onShowPhotoswipe = useCallback(
(images: IFile[], index: number) => dispatch(modalShowPhotoswipe(images, index)),
[dispatch]
);
return { comments, count, error, isLoading, onDelete, onLoadMoreComments, onShowPhotoswipe };
};

View file

@ -5,7 +5,7 @@ import { apiGetNode } from '~/redux/node/api';
export const useNodeFetcher = (id: INode['id']) => { export const useNodeFetcher = (id: INode['id']) => {
const { data, error, isValidating } = useSWR(`${id}`, apiGetNode); const { data, error, isValidating } = useSWR(`${id}`, apiGetNode);
const node = data?.node; const node = data?.node;
const isLoading = !node && !isValidating; const isLoading = !data && !isValidating;
return { node, error, isLoading }; return { node, error, isLoading };
}; };