mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
loading node with hook
This commit is contained in:
parent
35ce593ed8
commit
5e1e575ee3
19 changed files with 117 additions and 66 deletions
|
@ -38,6 +38,7 @@
|
||||||
"redux-saga": "^1.1.1",
|
"redux-saga": "^1.1.1",
|
||||||
"sticky-sidebar": "^3.3.1",
|
"sticky-sidebar": "^3.3.1",
|
||||||
"swiper": "^6.7.0",
|
"swiper": "^6.7.0",
|
||||||
|
"swr": "^1.0.1",
|
||||||
"throttle-debounce": "^2.1.0",
|
"throttle-debounce": "^2.1.0",
|
||||||
"typescript": "^4.0.5",
|
"typescript": "^4.0.5",
|
||||||
"typograf": "^6.11.3",
|
"typograf": "^6.11.3",
|
||||||
|
|
|
@ -14,7 +14,7 @@ import StickyBox from 'react-sticky-box/dist/esnext';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node?: INode;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
commentsOrder: 'ASC' | 'DESC';
|
commentsOrder: 'ASC' | 'DESC';
|
||||||
comments: IComment[];
|
comments: IComment[];
|
||||||
|
@ -35,7 +35,7 @@ const NodeBottomBlock: FC<IProps> = ({
|
||||||
const { inline } = useNodeBlocks(node, isLoading);
|
const { inline } = useNodeBlocks(node, isLoading);
|
||||||
const { is_user } = useUser();
|
const { is_user } = useUser();
|
||||||
|
|
||||||
if (node.deleted_at) {
|
if (node?.deleted_at) {
|
||||||
return <NodeDeletedBadge />;
|
return <NodeDeletedBadge />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ const NodeBottomBlock: FC<IProps> = ({
|
||||||
node={node}
|
node={node}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{is_user && !isLoading && <NodeCommentForm nodeId={node.id} />}
|
{is_user && !isLoading && <NodeCommentForm nodeId={node?.id} />}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<div className={styles.panel}>
|
<div className={styles.panel}>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useUser } from '~/utils/hooks/user/userUser';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
order: 'ASC' | 'DESC';
|
order: 'ASC' | 'DESC';
|
||||||
node: INode;
|
node?: INode;
|
||||||
comments: IComment[];
|
comments: IComment[];
|
||||||
count: number;
|
count: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useNodeActions } from '~/utils/hooks/node/useNodeActions';
|
||||||
import { shallowEqual } from 'react-redux';
|
import { shallowEqual } from 'react-redux';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node?: INode;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { URLS } from '~/constants/urls';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: Partial<INode>;
|
node?: Partial<INode>;
|
||||||
stack?: boolean;
|
stack?: boolean;
|
||||||
|
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
|
@ -26,8 +26,8 @@ interface IProps {
|
||||||
|
|
||||||
const NodePanelInner: FC<IProps> = memo(
|
const NodePanelInner: FC<IProps> = memo(
|
||||||
({
|
({
|
||||||
node: { id, title, user, is_liked, is_heroic, deleted_at, created_at, like_count },
|
|
||||||
stack,
|
stack,
|
||||||
|
node,
|
||||||
|
|
||||||
canStar,
|
canStar,
|
||||||
canEdit,
|
canEdit,
|
||||||
|
@ -45,15 +45,15 @@ const NodePanelInner: FC<IProps> = memo(
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.panel}>
|
<div className={styles.panel}>
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
{isLoading ? <Placeholder width="40%" /> : title || '...'}
|
{isLoading ? <Placeholder width="40%" /> : node?.title || '...'}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{user && user.username && (
|
{node?.user && node?.user.username && (
|
||||||
<div className={styles.name}>
|
<div className={styles.name}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Placeholder width="100px" />
|
<Placeholder width="100px" />
|
||||||
) : (
|
) : (
|
||||||
`~${user.username.toLocaleLowerCase()}, ${getPrettyDate(created_at)}`
|
`~${node?.user.username.toLocaleLowerCase()}, ${getPrettyDate(node?.created_at)}`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -67,8 +67,8 @@ const NodePanelInner: FC<IProps> = memo(
|
||||||
|
|
||||||
<div className={styles.editor_buttons}>
|
<div className={styles.editor_buttons}>
|
||||||
{canStar && (
|
{canStar && (
|
||||||
<div className={classNames(styles.star, { is_heroic })}>
|
<div className={classNames(styles.star, { is_heroic: node?.is_heroic })}>
|
||||||
{is_heroic ? (
|
{node?.is_heroic ? (
|
||||||
<Icon icon="star_full" size={24} onClick={onStar} />
|
<Icon icon="star_full" size={24} onClick={onStar} />
|
||||||
) : (
|
) : (
|
||||||
<Icon icon="star" size={24} onClick={onStar} />
|
<Icon icon="star" size={24} onClick={onStar} />
|
||||||
|
@ -77,10 +77,14 @@ const NodePanelInner: FC<IProps> = memo(
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Icon icon={deleted_at ? 'locked' : 'unlocked'} size={24} onClick={onLock} />
|
<Icon
|
||||||
|
icon={node?.deleted_at ? 'locked' : 'unlocked'}
|
||||||
|
size={24}
|
||||||
|
onClick={onLock}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link to={URLS.NODE_EDIT_URL(id)}>
|
<Link to={URLS.NODE_EDIT_URL(node?.id)}>
|
||||||
<Icon icon="edit" size={24} onClick={onEdit} />
|
<Icon icon="edit" size={24} onClick={onEdit} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,15 +93,15 @@ const NodePanelInner: FC<IProps> = memo(
|
||||||
|
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
{canLike && (
|
{canLike && (
|
||||||
<div className={classNames(styles.like, { is_liked })}>
|
<div className={classNames(styles.like, { is_liked: node?.is_liked })}>
|
||||||
{is_liked ? (
|
{node?.is_liked ? (
|
||||||
<Icon icon="heart_full" size={24} onClick={onLike} />
|
<Icon icon="heart_full" size={24} onClick={onLike} />
|
||||||
) : (
|
) : (
|
||||||
<Icon icon="heart" size={24} onClick={onLike} />
|
<Icon icon="heart" size={24} onClick={onLike} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!!like_count && like_count > 0 && (
|
{!!node?.like_count && node.like_count > 0 && (
|
||||||
<div className={styles.like_count}>{like_count}</div>
|
<div className={styles.like_count}>{node.like_count}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -8,12 +8,12 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
node: INode;
|
node?: INode;
|
||||||
related: INodeRelated;
|
related: INodeRelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeRelatedBlock: FC<IProps> = ({ isLoading, node, related }) => {
|
const NodeRelatedBlock: FC<IProps> = ({ isLoading, node, related }) => {
|
||||||
if (isLoading) {
|
if (isLoading || !node?.id) {
|
||||||
return <NodeRelatedPlaceholder />;
|
return <NodeRelatedPlaceholder />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@ import { Tags } from '~/components/tags/Tags';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
is_editable?: boolean;
|
is_editable?: boolean;
|
||||||
tags: ITag[];
|
tags?: ITag[];
|
||||||
onChange?: (tags: string[]) => void;
|
onChange?: (tags: string[]) => void;
|
||||||
onTagClick?: (tag: Partial<ITag>) => void;
|
onTagClick?: (tag: Partial<ITag>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeTags: FC<IProps> = memo(({ is_editable, tags, onChange, onTagClick }) => {
|
const NodeTags: FC<IProps> = memo(({ is_editable, tags = [], onChange, onTagClick }) => {
|
||||||
return (
|
return (
|
||||||
<Tags tags={tags} is_editable={is_editable} onTagsChange={onChange} onTagClick={onTagClick} />
|
<Tags tags={tags} is_editable={is_editable} onTagsChange={onChange} onTagClick={onTagClick} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,8 +8,8 @@ import { NodeTags } from '~/components/node/NodeTags';
|
||||||
import { useUser } from '~/utils/hooks/user/userUser';
|
import { useUser } from '~/utils/hooks/user/userUser';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
node: INode;
|
node?: INode;
|
||||||
isLoading: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
|
const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
|
||||||
|
@ -19,7 +19,7 @@ const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
|
||||||
|
|
||||||
const onTagsChange = useCallback(
|
const onTagsChange = useCallback(
|
||||||
(tags: string[]) => {
|
(tags: string[]) => {
|
||||||
dispatch(nodeUpdateTags(node.id, tags));
|
dispatch(nodeUpdateTags(node?.id, tags));
|
||||||
},
|
},
|
||||||
[dispatch, node]
|
[dispatch, node]
|
||||||
);
|
);
|
||||||
|
@ -42,7 +42,7 @@ const NodeTagsBlock: FC<IProps> = ({ node, isLoading }) => {
|
||||||
return (
|
return (
|
||||||
<NodeTags
|
<NodeTags
|
||||||
is_editable={is_user}
|
is_editable={is_user}
|
||||||
tags={node.tags}
|
tags={node?.tags}
|
||||||
onChange={onTagsChange}
|
onChange={onTagsChange}
|
||||||
onTagClick={onTagClick}
|
onTagClick={onTagClick}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { Card } from '~/components/containers/Card';
|
||||||
import { NodePanel } from '~/components/node/NodePanel';
|
import { NodePanel } from '~/components/node/NodePanel';
|
||||||
import { Footer } from '~/components/main/Footer';
|
import { Footer } from '~/components/main/Footer';
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
|
||||||
import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
||||||
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||||
import { Container } from '~/containers/main/Container';
|
import { Container } from '~/containers/main/Container';
|
||||||
|
@ -19,6 +18,9 @@ import { URLS } from '~/constants/urls';
|
||||||
import { EditorEditDialog } from '~/containers/dialogs/EditorEditDialog';
|
import { EditorEditDialog } from '~/containers/dialogs/EditorEditDialog';
|
||||||
import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen';
|
import { useOnNodeSeen } from '~/utils/hooks/node/useOnNodeSeen';
|
||||||
|
|
||||||
|
import styles from './styles.module.scss';
|
||||||
|
import { useNodeFetcher } from '~/utils/hooks/node/useNodeFetcher';
|
||||||
|
|
||||||
type IProps = RouteComponentProps<{ id: string }> & {};
|
type IProps = RouteComponentProps<{ id: string }> & {};
|
||||||
|
|
||||||
const NodeLayout: FC<IProps> = memo(
|
const NodeLayout: FC<IProps> = memo(
|
||||||
|
@ -27,21 +29,16 @@ const NodeLayout: FC<IProps> = memo(
|
||||||
params: { id },
|
params: { id },
|
||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { node, isLoading } = useNodeFetcher(parseInt(id, 10));
|
||||||
is_loading,
|
|
||||||
current,
|
|
||||||
comments,
|
|
||||||
comment_count,
|
|
||||||
is_loading_comments,
|
|
||||||
related,
|
|
||||||
} = useShallowSelect(selectNode);
|
|
||||||
|
|
||||||
useNodeCoverImage(current);
|
const { comments, comment_count, is_loading_comments, related } = useShallowSelect(selectNode);
|
||||||
|
|
||||||
|
useNodeCoverImage(node);
|
||||||
useScrollToTop([id]);
|
useScrollToTop([id]);
|
||||||
useLoadNode(id, is_loading);
|
useOnNodeSeen(node);
|
||||||
useOnNodeSeen(current);
|
useLoadNode(id, isLoading);
|
||||||
|
|
||||||
const { head, block } = useNodeBlocks(current, is_loading);
|
const { head, block } = useNodeBlocks(node, isLoading);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
|
@ -51,13 +48,13 @@ const NodeLayout: FC<IProps> = memo(
|
||||||
<Card className={styles.node} seamless>
|
<Card className={styles.node} seamless>
|
||||||
{block}
|
{block}
|
||||||
|
|
||||||
<NodePanel node={current} isLoading={is_loading} />
|
<NodePanel node={node} isLoading={isLoading} />
|
||||||
|
|
||||||
<NodeBottomBlock
|
<NodeBottomBlock
|
||||||
node={current}
|
node={node}
|
||||||
isLoadingComments={is_loading_comments}
|
isLoadingComments={is_loading_comments}
|
||||||
comments={comments}
|
comments={comments}
|
||||||
isLoading={is_loading}
|
isLoading={isLoading}
|
||||||
commentsCount={comment_count}
|
commentsCount={comment_count}
|
||||||
commentsOrder="DESC"
|
commentsOrder="DESC"
|
||||||
related={related}
|
related={related}
|
||||||
|
@ -69,7 +66,7 @@ const NodeLayout: FC<IProps> = memo(
|
||||||
|
|
||||||
<SidebarRouter prefix="/post:id" />
|
<SidebarRouter prefix="/post:id" />
|
||||||
|
|
||||||
<Route path={URLS.NODE_EDIT_URL(':id')} component={EditorEditDialog} />
|
<Route path={URLS.NODE_EDIT_URL(':id')} render={() => <EditorEditDialog />} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,8 @@ export const getNodeDiff = ({
|
||||||
})
|
})
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
export const apiGetNode = ({ id }: ApiGetNodeRequest, config?: AxiosRequestConfig) =>
|
export const apiGetNode = (id: INode['id']) =>
|
||||||
api.get<ApiGetNodeResult>(API.NODE.GET_NODE(id), config).then(cleanResult);
|
api.get<ApiGetNodeResult>(API.NODE.GET_NODE(id!)).then(cleanResult);
|
||||||
|
|
||||||
export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => {
|
export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => {
|
||||||
const cancelToken = axios.CancelToken.source();
|
const cancelToken = axios.CancelToken.source();
|
||||||
|
|
|
@ -149,7 +149,7 @@ function* onNodeLoad({ id }: ReturnType<typeof nodeLoadNode>) {
|
||||||
yield put(nodeSetLoading(true));
|
yield put(nodeSetLoading(true));
|
||||||
yield put(nodeSetLoadingComments(true));
|
yield put(nodeSetLoadingComments(true));
|
||||||
|
|
||||||
const { node }: Unwrap<typeof apiGetNode> = yield call(apiGetNode, { id });
|
const { node }: Unwrap<typeof apiGetNode> = yield call(apiGetNode, id);
|
||||||
|
|
||||||
yield put(nodeSetCurrent(node));
|
yield put(nodeSetCurrent(node));
|
||||||
yield put(nodeSetLoading(false));
|
yield put(nodeSetLoading(false));
|
||||||
|
@ -240,7 +240,7 @@ function* onEditSaga({ id }: ReturnType<typeof nodeEdit>) {
|
||||||
|
|
||||||
yield put(modalShowDialog(DIALOGS.LOADING));
|
yield put(modalShowDialog(DIALOGS.LOADING));
|
||||||
|
|
||||||
const { node }: Unwrap<typeof apiGetNode> = yield call(apiGetNode, { id });
|
const { node }: Unwrap<typeof apiGetNode> = yield call(apiGetNode, id);
|
||||||
|
|
||||||
if (!node.type || !has(node.type, NODE_EDITOR_DIALOGS)) return;
|
if (!node.type || !has(node.type, NODE_EDITOR_DIALOGS)) return;
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,33 @@ import { useCallback } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { nodeEdit, nodeLike, nodeLock, nodeStar } from '~/redux/node/actions';
|
import { nodeEdit, nodeLike, nodeLock, nodeStar } from '~/redux/node/actions';
|
||||||
|
|
||||||
export const useNodeActions = (node: INode) => {
|
export const useNodeActions = (node?: INode) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const onEdit = useCallback(() => dispatch(nodeEdit(node.id)), [dispatch, nodeEdit, node]);
|
const onEdit = useCallback(() => {
|
||||||
const onLike = useCallback(() => dispatch(nodeLike(node.id)), [dispatch, nodeLike, node]);
|
if (!node?.id) {
|
||||||
const onStar = useCallback(() => dispatch(nodeStar(node.id)), [dispatch, nodeStar, node]);
|
return;
|
||||||
const onLock = useCallback(() => dispatch(nodeLock(node.id, !node.deleted_at)), [
|
}
|
||||||
dispatch,
|
dispatch(nodeEdit(node.id));
|
||||||
nodeLock,
|
}, [node]);
|
||||||
node,
|
const onLike = useCallback(() => {
|
||||||
]);
|
if (!node?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(nodeLike(node.id));
|
||||||
|
}, [node]);
|
||||||
|
const onStar = useCallback(() => {
|
||||||
|
if (!node?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(nodeStar(node.id));
|
||||||
|
}, [node]);
|
||||||
|
const onLock = useCallback(() => {
|
||||||
|
if (!node?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(nodeLock(node.id, !node.deleted_at));
|
||||||
|
}, [node]);
|
||||||
|
|
||||||
return { onEdit, onLike, onStar, onLock };
|
return { onEdit, onLike, onStar, onLock };
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,13 +10,14 @@ import {
|
||||||
} from '~/redux/node/constants';
|
} from '~/redux/node/constants';
|
||||||
|
|
||||||
// useNodeBlocks returns head, block and inline blocks of node
|
// useNodeBlocks returns head, block and inline blocks of node
|
||||||
export const useNodeBlocks = (node: INode, isLoading: boolean) => {
|
export const useNodeBlocks = (node?: INode, isLoading?: boolean) => {
|
||||||
const createNodeBlock = useCallback(
|
const createNodeBlock = useCallback(
|
||||||
(block?: FC<INodeComponentProps>, key = 0) =>
|
(block?: FC<INodeComponentProps>, key = 0) =>
|
||||||
|
!isNil(node) &&
|
||||||
!isNil(block) &&
|
!isNil(block) &&
|
||||||
createElement(block, {
|
createElement(block, {
|
||||||
node,
|
node,
|
||||||
isLoading,
|
isLoading: !!isLoading,
|
||||||
key: `${node.id}-${key}`,
|
key: `${node.id}-${key}`,
|
||||||
}),
|
}),
|
||||||
[node, isLoading]
|
[node, isLoading]
|
||||||
|
|
|
@ -3,14 +3,19 @@ import { useEffect } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { nodeSetCoverImage } from '~/redux/node/actions';
|
import { nodeSetCoverImage } from '~/redux/node/actions';
|
||||||
|
|
||||||
export const useNodeCoverImage = (node: INode) => {
|
export const useNodeCoverImage = (node?: INode) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!node?.cover) {
|
||||||
|
dispatch(nodeSetCoverImage(undefined));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(nodeSetCoverImage(node.cover));
|
dispatch(nodeSetCoverImage(node.cover));
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
dispatch(nodeSetCoverImage(undefined));
|
dispatch(nodeSetCoverImage(undefined));
|
||||||
};
|
};
|
||||||
}, [dispatch, node.cover, node.id]);
|
}, [node?.cover]);
|
||||||
};
|
};
|
||||||
|
|
11
src/utils/hooks/node/useNodeFetcher.ts
Normal file
11
src/utils/hooks/node/useNodeFetcher.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { INode } from '~/redux/types';
|
||||||
|
import { apiGetNode } from '~/redux/node/api';
|
||||||
|
|
||||||
|
export const useNodeFetcher = (id: INode['id']) => {
|
||||||
|
const { data, error, isValidating } = useSWR(`${id}`, apiGetNode);
|
||||||
|
const node = data?.node;
|
||||||
|
const isLoading = !node && !isValidating;
|
||||||
|
|
||||||
|
return { node, error, isLoading };
|
||||||
|
};
|
|
@ -4,7 +4,7 @@ import { useShallowSelect } from '~/utils/hooks/useShallowSelect';
|
||||||
import { selectUser } from '~/redux/auth/selectors';
|
import { selectUser } from '~/redux/auth/selectors';
|
||||||
import { INode } from '~/redux/types';
|
import { INode } from '~/redux/types';
|
||||||
|
|
||||||
export const useNodePermissions = (node: INode) => {
|
export const useNodePermissions = (node?: INode) => {
|
||||||
const user = useShallowSelect(selectUser);
|
const user = useShallowSelect(selectUser);
|
||||||
const edit = useMemo(() => canEditNode(node, user), [node, user]);
|
const edit = useMemo(() => canEditNode(node, user), [node, user]);
|
||||||
const like = useMemo(() => canLikeNode(node, user), [node, user]);
|
const like = useMemo(() => canLikeNode(node, user), [node, user]);
|
||||||
|
|
|
@ -4,9 +4,13 @@ import { labSeenNode } from '~/redux/lab/actions';
|
||||||
import { flowSeenNode } from '~/redux/flow/actions';
|
import { flowSeenNode } from '~/redux/flow/actions';
|
||||||
|
|
||||||
// useOnNodeSeen updates node seen status across all needed places
|
// useOnNodeSeen updates node seen status across all needed places
|
||||||
export const useOnNodeSeen = (node: INode) => {
|
export const useOnNodeSeen = (node?: INode) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove node from updated
|
// Remove node from updated
|
||||||
if (node.is_promoted) {
|
if (node.is_promoted) {
|
||||||
dispatch(flowSeenNode(node.id));
|
dispatch(flowSeenNode(node.id));
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { IUser } from '~/redux/auth/types';
|
||||||
import { path } from 'ramda';
|
import { path } from 'ramda';
|
||||||
import { NODE_TYPES } from '~/redux/node/constants';
|
import { NODE_TYPES } from '~/redux/node/constants';
|
||||||
|
|
||||||
export const canEditNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
export const canEditNode = (node?: Partial<INode>, user?: Partial<IUser>): boolean =>
|
||||||
path(['role'], user) === USER_ROLES.ADMIN ||
|
path(['role'], user) === USER_ROLES.ADMIN ||
|
||||||
(path(['user', 'id'], node) && path(['user', 'id'], node) === path(['id'], user));
|
(path(['user', 'id'], node) && path(['user', 'id'], node) === path(['id'], user));
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ export const canEditComment = (comment: Partial<ICommentGroup>, user: Partial<IU
|
||||||
path(['role'], user) === USER_ROLES.ADMIN ||
|
path(['role'], user) === USER_ROLES.ADMIN ||
|
||||||
(path(['user', 'id'], comment) && path(['user', 'id'], comment) === path(['id'], user));
|
(path(['user', 'id'], comment) && path(['user', 'id'], comment) === path(['id'], user));
|
||||||
|
|
||||||
export const canLikeNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
export const canLikeNode = (node?: Partial<INode>, user?: Partial<IUser>): boolean =>
|
||||||
path(['role'], user) && path(['role'], user) !== USER_ROLES.GUEST;
|
path(['role'], user) && path(['role'], user) !== USER_ROLES.GUEST;
|
||||||
|
|
||||||
export const canStarNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
export const canStarNode = (node?: Partial<INode>, user?: Partial<IUser>): boolean =>
|
||||||
(node.type === NODE_TYPES.IMAGE || node.is_promoted === false) &&
|
(node?.type === NODE_TYPES.IMAGE || node?.is_promoted === false) &&
|
||||||
path(['role'], user) &&
|
path(['role'], user) &&
|
||||||
path(['role'], user) === USER_ROLES.ADMIN;
|
path(['role'], user) === USER_ROLES.ADMIN;
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -4001,6 +4001,11 @@ depd@~1.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||||
|
|
||||||
|
dequal@2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
|
||||||
|
integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
|
||||||
|
|
||||||
des.js@^1.0.0:
|
des.js@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
|
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
|
||||||
|
@ -10881,6 +10886,13 @@ swiper@^6.7.0:
|
||||||
dom7 "^3.0.0"
|
dom7 "^3.0.0"
|
||||||
ssr-window "^3.0.0"
|
ssr-window "^3.0.0"
|
||||||
|
|
||||||
|
swr@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/swr/-/swr-1.0.1.tgz#15f62846b87ee000e52fa07812bb65eb62d79483"
|
||||||
|
integrity sha512-EPQAxSjoD4IaM49rpRHK0q+/NzcwoT8c0/Ylu/u3/6mFj/CWnQVjNJ0MV2Iuw/U+EJSd2TX5czdAwKPYZIG0YA==
|
||||||
|
dependencies:
|
||||||
|
dequal "2.0.2"
|
||||||
|
|
||||||
symbol-observable@^1.2.0:
|
symbol-observable@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue