mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
fixed some paths according to new backend
This commit is contained in:
parent
2f6207feaa
commit
4912fd255c
27 changed files with 228 additions and 217 deletions
|
@ -1,6 +1,6 @@
|
||||||
NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
|
# NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
|
||||||
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
|
# NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
|
||||||
# NEXT_PUBLIC_API_HOST=http://localhost:8888/
|
NEXT_PUBLIC_API_HOST=http://localhost:8888/
|
||||||
# NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
|
NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
|
||||||
# NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
|
# NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
|
||||||
# NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
|
# NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
|
||||||
|
|
12
.eslintrc.js
12
.eslintrc.js
|
@ -23,6 +23,18 @@ module.exports = {
|
||||||
pathGroupsExcludedImportTypes: ['react'],
|
pathGroupsExcludedImportTypes: ['react'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'no-restricted-imports': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
name: 'ramda',
|
||||||
|
message:
|
||||||
|
'import from \'~/utils/ramda\' instead',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 7,
|
ecmaVersion: 7,
|
||||||
|
|
|
@ -5,10 +5,16 @@ import { api, cleanResult } from '~/utils/api';
|
||||||
|
|
||||||
export const postCellView = ({ id, flow }: PostCellViewRequest) =>
|
export const postCellView = ({ id, flow }: PostCellViewRequest) =>
|
||||||
api
|
api
|
||||||
.post<PostCellViewResult>(API.NODE.SET_CELL_VIEW(id), { flow })
|
.post<PostCellViewResult>(API.NODES.SET_CELL_VIEW(id), { flow })
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
export const getSearchResults = ({ text, skip, take }: GetSearchResultsRequest) =>
|
export const getSearchResults = ({
|
||||||
|
text,
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
}: GetSearchResultsRequest) =>
|
||||||
api
|
api
|
||||||
.get<GetSearchResultsResult>(API.SEARCH.NODES, { params: { text, skip, take } })
|
.get<GetSearchResultsResult>(API.SEARCH.NODES, {
|
||||||
|
params: { text, skip, take },
|
||||||
|
})
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
|
@ -44,7 +44,7 @@ export type ApiGetNodeCommentsResponse = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const apiPostNode = ({ node }: ApiPostNodeRequest) =>
|
export const apiPostNode = ({ node }: ApiPostNodeRequest) =>
|
||||||
api.post<ApiPostNodeResult>(API.NODE.SAVE, node).then(cleanResult);
|
api.post<ApiPostNodeResult>(API.NODES.SAVE, node).then(cleanResult);
|
||||||
|
|
||||||
export const getNodeDiff = ({
|
export const getNodeDiff = ({
|
||||||
start,
|
start,
|
||||||
|
@ -56,7 +56,7 @@ export const getNodeDiff = ({
|
||||||
with_valid,
|
with_valid,
|
||||||
}: GetNodeDiffRequest) =>
|
}: GetNodeDiffRequest) =>
|
||||||
api
|
api
|
||||||
.get<GetNodeDiffResult>(API.NODE.GET_DIFF, {
|
.get<GetNodeDiffResult>(API.NODES.LIST, {
|
||||||
params: {
|
params: {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
|
@ -74,7 +74,7 @@ export const apiGetNode = (
|
||||||
config?: AxiosRequestConfig,
|
config?: AxiosRequestConfig,
|
||||||
) =>
|
) =>
|
||||||
api
|
api
|
||||||
.get<ApiGetNodeResponse>(API.NODE.GET_NODE(id), config)
|
.get<ApiGetNodeResponse>(API.NODES.GET(id), config)
|
||||||
.then(cleanResult)
|
.then(cleanResult)
|
||||||
.then((data) => ({ node: data.node, last_seen: data.last_seen }));
|
.then((data) => ({ node: data.node, last_seen: data.last_seen }));
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => {
|
||||||
const cancelToken = axios.CancelToken.source();
|
const cancelToken = axios.CancelToken.source();
|
||||||
return {
|
return {
|
||||||
request: api
|
request: api
|
||||||
.get<ApiGetNodeResponse>(API.NODE.GET_NODE(id), {
|
.get<ApiGetNodeResponse>(API.NODES.GET(id), {
|
||||||
cancelToken: cancelToken.token,
|
cancelToken: cancelToken.token,
|
||||||
})
|
})
|
||||||
.then(cleanResult),
|
.then(cleanResult),
|
||||||
|
@ -91,7 +91,7 @@ export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const apiPostComment = ({ id, data }: ApiPostCommentRequest) =>
|
export const apiPostComment = ({ id, data }: ApiPostCommentRequest) =>
|
||||||
api.post<ApiPostCommentResult>(API.NODE.COMMENT(id), data).then(cleanResult);
|
api.post<ApiPostCommentResult>(API.NODES.COMMENT(id), data).then(cleanResult);
|
||||||
|
|
||||||
export const apiGetNodeComments = ({
|
export const apiGetNodeComments = ({
|
||||||
id,
|
id,
|
||||||
|
@ -99,35 +99,33 @@ export const apiGetNodeComments = ({
|
||||||
skip = 0,
|
skip = 0,
|
||||||
}: ApiGetNodeCommentsRequest) =>
|
}: ApiGetNodeCommentsRequest) =>
|
||||||
api
|
api
|
||||||
.get<ApiGetNodeCommentsResponse>(API.NODE.COMMENT(id), {
|
.get<ApiGetNodeCommentsResponse>(API.NODES.COMMENT(id), {
|
||||||
params: { take, skip },
|
params: { take, skip },
|
||||||
})
|
})
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
export const apiGetNodeRelated = ({ id }: ApiGetNodeRelatedRequest) =>
|
export const apiGetNodeRelated = ({ id }: ApiGetNodeRelatedRequest) =>
|
||||||
api.get<ApiGetNodeRelatedResult>(API.NODE.RELATED(id)).then(cleanResult);
|
api.get<ApiGetNodeRelatedResult>(API.NODES.RELATED(id)).then(cleanResult);
|
||||||
|
|
||||||
export const apiPostNodeTags = ({ id, tags }: ApiPostNodeTagsRequest) =>
|
export const apiPostNodeTags = ({ id, tags }: ApiPostNodeTagsRequest) =>
|
||||||
api
|
api
|
||||||
.post<ApiPostNodeTagsResult>(API.NODE.UPDATE_TAGS(id), { tags })
|
.post<ApiPostNodeTagsResult>(API.NODES.UPDATE_TAGS(id), { tags })
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
export const apiDeleteNodeTag = ({ id, tagId }: ApiDeleteNodeTagsRequest) =>
|
export const apiDeleteNodeTag = ({ id, tagId }: ApiDeleteNodeTagsRequest) =>
|
||||||
api
|
api
|
||||||
.delete<ApiDeleteNodeTagsResult>(API.NODE.DELETE_TAG(id, tagId))
|
.delete<ApiDeleteNodeTagsResult>(API.NODES.DELETE_TAG(id, tagId))
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
export const apiPostNodeLike = ({ id }: ApiPostNodeLikeRequest) =>
|
export const apiPostNodeLike = ({ id }: ApiPostNodeLikeRequest) =>
|
||||||
api.post<ApiPostNodeLikeResult>(API.NODE.POST_LIKE(id)).then(cleanResult);
|
api.post<ApiPostNodeLikeResult>(API.NODES.LIKE(id)).then(cleanResult);
|
||||||
|
|
||||||
export const apiPostNodeHeroic = ({ id }: ApiPostNodeHeroicRequest) =>
|
export const apiPostNodeHeroic = ({ id }: ApiPostNodeHeroicRequest) =>
|
||||||
api
|
api.post<ApiPostNodeHeroicResponse>(API.NODES.HEROIC(id)).then(cleanResult);
|
||||||
.post<ApiPostNodeHeroicResponse>(API.NODE.POST_HEROIC(id))
|
|
||||||
.then(cleanResult);
|
|
||||||
|
|
||||||
export const apiLockNode = ({ id, is_locked }: ApiLockNodeRequest) =>
|
export const apiLockNode = ({ id, is_locked }: ApiLockNodeRequest) =>
|
||||||
api
|
api
|
||||||
.post<ApiLockNodeResult>(API.NODE.POST_LOCK(id), { is_locked })
|
.delete<ApiLockNodeResult>(API.NODES.DELETE(id), { params: { is_locked } })
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
export const apiLockComment = ({
|
export const apiLockComment = ({
|
||||||
|
@ -136,7 +134,7 @@ export const apiLockComment = ({
|
||||||
nodeId,
|
nodeId,
|
||||||
}: ApiLockCommentRequest) =>
|
}: ApiLockCommentRequest) =>
|
||||||
api
|
api
|
||||||
.delete<ApiLockcommentResult>(API.NODE.LOCK_COMMENT(nodeId, id), {
|
.delete<ApiLockcommentResult>(API.NODES.LOCK_COMMENT(nodeId, id), {
|
||||||
params: {
|
params: {
|
||||||
is_locked: isLocked,
|
is_locked: isLocked,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
import { Avatar } from '~/components/common/Avatar';
|
import { Avatar } from '~/components/common/Avatar';
|
||||||
import { MenuButton } from '~/components/menu';
|
import { MenuButton } from '~/components/menu/MenuButton';
|
||||||
import { ProfileQuickInfo } from '~/containers/profile/ProfileQuickInfo';
|
import { ProfileQuickInfo } from '~/containers/profile/ProfileQuickInfo';
|
||||||
import { IUser } from '~/types/auth';
|
import { IUser } from '~/types/auth';
|
||||||
import { path } from '~/utils/ramda';
|
import { path } from '~/utils/ramda';
|
||||||
|
@ -16,7 +16,11 @@ const CommentAvatar: FC<Props> = ({ user, className }) => {
|
||||||
<MenuButton
|
<MenuButton
|
||||||
position="auto"
|
position="auto"
|
||||||
icon={
|
icon={
|
||||||
<Avatar url={path(['photo', 'url'], user)} username={user.username} className={className} />
|
<Avatar
|
||||||
|
url={path(['photo', 'url'], user)}
|
||||||
|
username={user.username}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ProfileQuickInfo user={user} />
|
<ProfileQuickInfo user={user} />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
|
|
||||||
import { SortableAudioGrid, SortableImageGrid } from '~/components/sortable';
|
import { SortableAudioGrid } from '~/components/sortable/SortableAudioGrid';
|
||||||
|
import { SortableImageGrid } from '~/components/sortable/SortableImageGrid';
|
||||||
import { COMMENT_FILE_TYPES } from '~/constants/uploads';
|
import { COMMENT_FILE_TYPES } from '~/constants/uploads';
|
||||||
import { useFileDropZone } from '~/hooks';
|
import { useFileDropZone } from '~/hooks';
|
||||||
import { IFile } from '~/types';
|
import { IFile } from '~/types';
|
||||||
|
@ -27,34 +28,36 @@ const CommentFormAttaches: FC = () => {
|
||||||
|
|
||||||
const onImageMove = useCallback(
|
const onImageMove = useCallback(
|
||||||
(newFiles: IFile[]) => {
|
(newFiles: IFile[]) => {
|
||||||
setFiles([...filesAudios, ...newFiles.filter(it => it)]);
|
setFiles([...filesAudios, ...newFiles.filter((it) => it)]);
|
||||||
},
|
},
|
||||||
[setFiles, filesImages, filesAudios]
|
[setFiles, filesImages, filesAudios],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onAudioMove = useCallback(
|
const onAudioMove = useCallback(
|
||||||
(newFiles: IFile[]) => {
|
(newFiles: IFile[]) => {
|
||||||
setFiles([...filesImages, ...newFiles]);
|
setFiles([...filesImages, ...newFiles]);
|
||||||
},
|
},
|
||||||
[setFiles, filesImages, filesAudios]
|
[setFiles, filesImages, filesAudios],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onFileDelete = useCallback(
|
const onFileDelete = useCallback(
|
||||||
(fileId: IFile['id']) => {
|
(fileId: IFile['id']) => {
|
||||||
setFiles(files.filter(file => file.id !== fileId));
|
setFiles(files.filter((file) => file.id !== fileId));
|
||||||
},
|
},
|
||||||
[files, setFiles]
|
[files, setFiles],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onAudioTitleChange = useCallback(
|
const onAudioTitleChange = useCallback(
|
||||||
(fileId: IFile['id'], title: string) => {
|
(fileId: IFile['id'], title: string) => {
|
||||||
setFiles(
|
setFiles(
|
||||||
files.map(file =>
|
files.map((file) =>
|
||||||
file.id === fileId ? { ...file, metadata: { ...file.metadata, title } } : file
|
file.id === fileId
|
||||||
)
|
? { ...file, metadata: { ...file.metadata, title } }
|
||||||
|
: file,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[files, setFiles]
|
[files, setFiles],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasAttaches) return null;
|
if (!hasAttaches) return null;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
|
|
||||||
import { SortableAudioGrid } from '~/components/sortable';
|
import { SortableAudioGrid } from '~/components/sortable/SortableAudioGrid';
|
||||||
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
||||||
import { IFile } from '~/types';
|
import { IFile } from '~/types';
|
||||||
|
|
||||||
|
@ -15,25 +15,27 @@ const AudioGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
||||||
(newFiles: IFile[]) => {
|
(newFiles: IFile[]) => {
|
||||||
setFiles(newFiles);
|
setFiles(newFiles);
|
||||||
},
|
},
|
||||||
[setFiles, files]
|
[setFiles, files],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(remove_id: IFile['id']) => {
|
(remove_id: IFile['id']) => {
|
||||||
setFiles(files.filter(file => file && file.id !== remove_id));
|
setFiles(files.filter((file) => file && file.id !== remove_id));
|
||||||
},
|
},
|
||||||
[setFiles, files]
|
[setFiles, files],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onTitleChange = useCallback(
|
const onTitleChange = useCallback(
|
||||||
(changeId: IFile['id'], title: string) => {
|
(changeId: IFile['id'], title: string) => {
|
||||||
setFiles(
|
setFiles(
|
||||||
files.map(file =>
|
files.map((file) =>
|
||||||
file && file.id === changeId ? { ...file, metadata: { ...file.metadata, title } } : file
|
file && file.id === changeId
|
||||||
)
|
? { ...file, metadata: { ...file.metadata, title } }
|
||||||
|
: file,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[setFiles, files]
|
[setFiles, files],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from 'react';
|
||||||
|
|
||||||
import { SortableImageGrid } from '~/components/sortable';
|
import { SortableImageGrid } from '~/components/sortable/SortableImageGrid';
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||||
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
||||||
import { IFile } from '~/types';
|
import { IFile } from '~/types';
|
||||||
|
@ -16,14 +16,14 @@ const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
|
||||||
|
|
||||||
const onMove = useCallback(
|
const onMove = useCallback(
|
||||||
(newFiles: IFile[]) => {
|
(newFiles: IFile[]) => {
|
||||||
setFiles(newFiles.filter(it => it));
|
setFiles(newFiles.filter((it) => it));
|
||||||
},
|
},
|
||||||
[setFiles, files],
|
[setFiles, files],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(id: IFile['id']) => {
|
(id: IFile['id']) => {
|
||||||
setFiles(files.filter(file => file && file.id !== id));
|
setFiles(files.filter((file) => file && file.id !== id));
|
||||||
},
|
},
|
||||||
[setFiles, files],
|
[setFiles, files],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
export * from './VerticalMenu';
|
|
||||||
export * from './HorizontalMenu';
|
|
||||||
export * from './MenuButton';
|
|
||||||
export * from './MenuItemWithIcon';
|
|
||||||
export * from './SeparatedMenu';
|
|
|
@ -1,38 +0,0 @@
|
||||||
import React, { FC } from 'react';
|
|
||||||
|
|
||||||
import { Filler } from '~/components/containers/Filler';
|
|
||||||
import { Group } from '~/components/containers/Group';
|
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MenuButton: FC<IProps> = ({
|
|
||||||
title,
|
|
||||||
icon,
|
|
||||||
description,
|
|
||||||
}) => (
|
|
||||||
<div
|
|
||||||
className={styles.button}
|
|
||||||
>
|
|
||||||
<Group horizontal>
|
|
||||||
<div className={styles.icon}>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
|
|
||||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
|
||||||
<path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Filler>
|
|
||||||
<div className={styles.title}>{title}</div>
|
|
||||||
{ description && <div className={styles.description}>{description}</div> }
|
|
||||||
</Filler>
|
|
||||||
</Group>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export { MenuButton };
|
|
|
@ -1,29 +0,0 @@
|
||||||
@import 'src/styles/variables';
|
|
||||||
|
|
||||||
.button {
|
|
||||||
fill: white;
|
|
||||||
height: 48px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
flex: 0 0 38px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font: $font_16_semibold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
font: $font_12_regular;
|
|
||||||
color: $gray_75;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
|
@ -3,7 +3,9 @@ import React, { VFC } from 'react';
|
||||||
import Tippy from '@tippyjs/react';
|
import Tippy from '@tippyjs/react';
|
||||||
|
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { MenuButton, MenuItemWithIcon, SeparatedMenu } from '~/components/menu';
|
import { MenuButton } from '~/components/menu/MenuButton';
|
||||||
|
import { MenuItemWithIcon } from '~/components/menu/MenuItemWithIcon';
|
||||||
|
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React, { memo, VFC } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { SeparatedMenu } from '~/components/menu';
|
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
|
||||||
import { NodeEditMenu } from '~/components/node/NodeEditMenu';
|
import { NodeEditMenu } from '~/components/node/NodeEditMenu';
|
||||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||||
import { getPrettyDate } from '~/utils/dom';
|
import { getPrettyDate } from '~/utils/dom';
|
||||||
|
@ -35,7 +35,6 @@ interface IProps {
|
||||||
|
|
||||||
const NodeTitle: VFC<IProps> = memo(
|
const NodeTitle: VFC<IProps> = memo(
|
||||||
({
|
({
|
||||||
id,
|
|
||||||
title,
|
title,
|
||||||
username,
|
username,
|
||||||
createdAt,
|
createdAt,
|
||||||
|
@ -69,7 +68,9 @@ const NodeTitle: VFC<IProps> = memo(
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Placeholder width="100px" />
|
<Placeholder width="100px" />
|
||||||
) : (
|
) : (
|
||||||
`~${username.toLocaleLowerCase()}, ${getPrettyDate(createdAt)}`
|
`~${username.toLocaleLowerCase()}, ${getPrettyDate(
|
||||||
|
createdAt,
|
||||||
|
)}`
|
||||||
)}
|
)}
|
||||||
</aside>
|
</aside>
|
||||||
)}
|
)}
|
||||||
|
@ -90,7 +91,9 @@ const NodeTitle: VFC<IProps> = memo(
|
||||||
|
|
||||||
{canLike && (
|
{canLike && (
|
||||||
<div
|
<div
|
||||||
className={classNames(styles.button, styles.like, { [styles.is_liked]: isLiked })}
|
className={classNames(styles.button, styles.like, {
|
||||||
|
[styles.is_liked]: isLiked,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{isLiked ? (
|
{isLiked ? (
|
||||||
<Icon icon="heart_full" size={24} onClick={onLike} />
|
<Icon icon="heart_full" size={24} onClick={onLike} />
|
||||||
|
@ -107,7 +110,7 @@ const NodeTitle: VFC<IProps> = memo(
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export { NodeTitle };
|
export { NodeTitle };
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { rectSortingStrategy, SortableContext } from '@dnd-kit/sortable';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
|
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
|
||||||
import { useSortableActions } from '~/hooks/sortable';
|
import { useSortableActions } from '~/hooks/sortable/useSortableActions';
|
||||||
import { DivProps } from '~/utils/types';
|
import { DivProps } from '~/utils/types';
|
||||||
|
|
||||||
import { SortableItem } from '../SortableItem';
|
import { SortableItem } from '../SortableItem';
|
||||||
|
@ -16,7 +16,7 @@ interface SortableGridProps<
|
||||||
ItemRendererProps extends {},
|
ItemRendererProps extends {},
|
||||||
LockedRendererProps extends {},
|
LockedRendererProps extends {},
|
||||||
Item extends {},
|
Item extends {},
|
||||||
Locked extends {}
|
Locked extends {},
|
||||||
> {
|
> {
|
||||||
items: Item[];
|
items: Item[];
|
||||||
locked: Locked[];
|
locked: Locked[];
|
||||||
|
@ -44,18 +44,18 @@ const SortableGrid = <RIP, RLP, I, L>({
|
||||||
onSortEnd,
|
onSortEnd,
|
||||||
size,
|
size,
|
||||||
}: SortableGridProps<RIP, RLP, I, L>) => {
|
}: SortableGridProps<RIP, RLP, I, L>) => {
|
||||||
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
|
const { sensors, onDragEnd, onDragStart, draggingItem, ids } =
|
||||||
items,
|
useSortableActions(items, getID, onSortEnd);
|
||||||
getID,
|
|
||||||
onSortEnd
|
|
||||||
);
|
|
||||||
|
|
||||||
const gridStyle = useMemo<DivProps['style']>(
|
const gridStyle = useMemo<DivProps['style']>(
|
||||||
() =>
|
() =>
|
||||||
size
|
size
|
||||||
? { gridTemplateColumns: size && `repeat(auto-fill, minmax(${size}px, 1fr))` }
|
? {
|
||||||
|
gridTemplateColumns:
|
||||||
|
size && `repeat(auto-fill, minmax(${size}px, 1fr))`,
|
||||||
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
[size]
|
[size],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -67,30 +67,35 @@ const SortableGrid = <RIP, RLP, I, L>({
|
||||||
>
|
>
|
||||||
<SortableContext items={ids} strategy={rectSortingStrategy}>
|
<SortableContext items={ids} strategy={rectSortingStrategy}>
|
||||||
<div className={classNames(styles.grid, className)} style={gridStyle}>
|
<div className={classNames(styles.grid, className)} style={gridStyle}>
|
||||||
{items.map(item => (
|
{items.map((item) => (
|
||||||
<SortableItem
|
<SortableItem
|
||||||
key={getID(item)}
|
key={getID(item)}
|
||||||
id={getID(item)}
|
id={getID(item)}
|
||||||
className={
|
className={
|
||||||
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
|
draggingItem && getID(item) === getID(draggingItem)
|
||||||
|
? styles.dragging
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{createElement(renderItem, { ...renderItemProps, item })}
|
{createElement(renderItem, { ...renderItemProps, item })}
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{locked.map(item =>
|
{locked.map((item) =>
|
||||||
createElement(renderLocked, {
|
createElement(renderLocked, {
|
||||||
...renderLockedProps,
|
...renderLockedProps,
|
||||||
locked: item,
|
locked: item,
|
||||||
key: getLockedID(item),
|
key: getLockedID(item),
|
||||||
})
|
}),
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DragOverlay>
|
<DragOverlay>
|
||||||
{draggingItem ? (
|
{draggingItem ? (
|
||||||
<DragOverlayItem>
|
<DragOverlayItem>
|
||||||
{createElement(renderItem, { ...renderItemProps, item: draggingItem })}
|
{createElement(renderItem, {
|
||||||
|
...renderItemProps,
|
||||||
|
item: draggingItem,
|
||||||
|
})}
|
||||||
</DragOverlayItem>
|
</DragOverlayItem>
|
||||||
) : null}
|
) : null}
|
||||||
</DragOverlay>
|
</DragOverlay>
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import React, { createElement, FC } from 'react';
|
import React, { createElement, FC } from 'react';
|
||||||
|
|
||||||
import { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core';
|
import { closestCenter, DndContext, DragOverlay } from '@dnd-kit/core';
|
||||||
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
import {
|
||||||
|
SortableContext,
|
||||||
|
verticalListSortingStrategy,
|
||||||
|
} from '@dnd-kit/sortable';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
|
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
|
||||||
import { SortableItem } from '~/components/sortable/SortableItem';
|
import { SortableItem } from '~/components/sortable/SortableItem';
|
||||||
import { useSortableActions } from '~/hooks/sortable';
|
import { useSortableActions } from '~/hooks/sortable/useSortableActions';
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
@ -14,7 +17,7 @@ interface SortableListProps<
|
||||||
RenderItemProps extends {},
|
RenderItemProps extends {},
|
||||||
RenderLockedProps extends {},
|
RenderLockedProps extends {},
|
||||||
Item extends {},
|
Item extends {},
|
||||||
Locked extends {}
|
Locked extends {},
|
||||||
> {
|
> {
|
||||||
items: Item[];
|
items: Item[];
|
||||||
locked: Locked[];
|
locked: Locked[];
|
||||||
|
@ -40,11 +43,8 @@ const SortableList = <RIP, RLP, I, L>({
|
||||||
renderLockedProps,
|
renderLockedProps,
|
||||||
onSortEnd,
|
onSortEnd,
|
||||||
}: SortableListProps<RIP, RLP, I, L>) => {
|
}: SortableListProps<RIP, RLP, I, L>) => {
|
||||||
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
|
const { sensors, onDragEnd, onDragStart, draggingItem, ids } =
|
||||||
items,
|
useSortableActions(items, getID, onSortEnd);
|
||||||
getID,
|
|
||||||
onSortEnd
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndContext
|
<DndContext
|
||||||
|
@ -55,30 +55,39 @@ const SortableList = <RIP, RLP, I, L>({
|
||||||
>
|
>
|
||||||
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
|
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
|
||||||
<div className={classNames(styles.grid, className)}>
|
<div className={classNames(styles.grid, className)}>
|
||||||
{items.map(item => (
|
{items.map((item) => (
|
||||||
<SortableItem
|
<SortableItem
|
||||||
key={getID(item)}
|
key={getID(item)}
|
||||||
id={getID(item)}
|
id={getID(item)}
|
||||||
className={
|
className={
|
||||||
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
|
draggingItem && getID(item) === getID(draggingItem)
|
||||||
|
? styles.dragging
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{createElement(renderItem, { ...renderItemProps, item, key: getID(item) })}
|
{createElement(renderItem, {
|
||||||
|
...renderItemProps,
|
||||||
|
item,
|
||||||
|
key: getID(item),
|
||||||
|
})}
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{locked.map(item =>
|
{locked.map((item) =>
|
||||||
createElement(renderLocked, {
|
createElement(renderLocked, {
|
||||||
...renderLockedProps,
|
...renderLockedProps,
|
||||||
locked: item,
|
locked: item,
|
||||||
key: getLockedID(item),
|
key: getLockedID(item),
|
||||||
})
|
}),
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DragOverlay>
|
<DragOverlay>
|
||||||
{draggingItem ? (
|
{draggingItem ? (
|
||||||
<DragOverlayItem>
|
<DragOverlayItem>
|
||||||
{createElement(renderItem, { ...renderItemProps, item: draggingItem })}
|
{createElement(renderItem, {
|
||||||
|
...renderItemProps,
|
||||||
|
item: draggingItem,
|
||||||
|
})}
|
||||||
</DragOverlayItem>
|
</DragOverlayItem>
|
||||||
) : null}
|
) : null}
|
||||||
</DragOverlay>
|
</DragOverlay>
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './SortableImageGrid';
|
|
||||||
export * from './SortableAudioGrid';
|
|
|
@ -23,23 +23,23 @@ export const API = {
|
||||||
ATTACH_SOCIAL: `/oauth/attach`,
|
ATTACH_SOCIAL: `/oauth/attach`,
|
||||||
LOGIN_WITH_SOCIAL: `/oauth/login`,
|
LOGIN_WITH_SOCIAL: `/oauth/login`,
|
||||||
},
|
},
|
||||||
NODE: {
|
NODES: {
|
||||||
SAVE: '/nodes/',
|
SAVE: '/nodes/',
|
||||||
GET_DIFF: '/nodes/',
|
LIST: '/nodes/',
|
||||||
GET_NODE: (id: number | string) => `/nodes/${id}`,
|
GET: (id: number | string) => `/nodes/${id}`,
|
||||||
|
DELETE: (id: INode['id']) => `/nodes/${id}`,
|
||||||
|
LIKE: (id: INode['id']) => `/nodes/${id}/like`,
|
||||||
|
HEROIC: (id: INode['id']) => `/nodes/${id}/heroic`,
|
||||||
|
SET_CELL_VIEW: (id: INode['id']) => `/nodes/${id}/cell-view`,
|
||||||
RELATED: (id: INode['id']) => `/nodes/${id}/related`,
|
RELATED: (id: INode['id']) => `/nodes/${id}/related`,
|
||||||
|
|
||||||
UPDATE_TAGS: (id: INode['id']) => `/nodes/${id}/tags`,
|
UPDATE_TAGS: (id: INode['id']) => `/nodes/${id}/tags`,
|
||||||
DELETE_TAG: (id: INode['id'], tagId: ITag['ID']) =>
|
DELETE_TAG: (id: INode['id'], tagId: ITag['ID']) =>
|
||||||
`/nodes/${id}/tags/${tagId}`,
|
`/nodes/${id}/tags/${tagId}`,
|
||||||
POST_LIKE: (id: INode['id']) => `/nodes/${id}/like`,
|
|
||||||
POST_HEROIC: (id: INode['id']) => `/nodes/${id}/heroic`,
|
|
||||||
POST_LOCK: (id: INode['id']) => `/nodes/${id}/lock`,
|
|
||||||
SET_CELL_VIEW: (id: INode['id']) => `/nodes/${id}/cell-view`,
|
|
||||||
|
|
||||||
COMMENT: (id: INode['id'] | string) => `/nodes/${id}/comment`,
|
COMMENT: (id: INode['id'] | string) => `/nodes/${id}/comments`,
|
||||||
LOCK_COMMENT: (id: INode['id'], comment_id: IComment['id']) =>
|
LOCK_COMMENT: (id: INode['id'], comment_id: IComment['id']) =>
|
||||||
`/nodes/${id}/comment/${comment_id}`,
|
`/nodes/${id}/comments/${comment_id}`,
|
||||||
},
|
},
|
||||||
SEARCH: {
|
SEARCH: {
|
||||||
NODES: '/search/nodes',
|
NODES: '/search/nodes',
|
||||||
|
|
|
@ -3,8 +3,7 @@ import React, { FC } from 'react';
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from '~/components/containers/Group';
|
||||||
import { Padder } from '~/components/containers/Padder';
|
import { Padder } from '~/components/containers/Padder';
|
||||||
import { Button } from '~/components/input/Button';
|
import { Button } from '~/components/input/Button';
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { MenuButton } from '~/components/menu/MenuButton';
|
||||||
import { MenuButton } from '~/components/menu';
|
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
@ -12,17 +11,30 @@ interface ProfileSidebarLogoutButtonProps {
|
||||||
onLogout?: () => void;
|
onLogout?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileSidebarLogoutButton: FC<ProfileSidebarLogoutButtonProps> = ({ onLogout }) => (
|
const ProfileSidebarLogoutButton: FC<ProfileSidebarLogoutButtonProps> = ({
|
||||||
<MenuButton icon={<Button color="link" iconRight="logout">Выйти</Button>} position="top-end">
|
onLogout,
|
||||||
|
}) => (
|
||||||
|
<MenuButton
|
||||||
|
icon={
|
||||||
|
<Button color="link" iconRight="logout">
|
||||||
|
Выйти
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
position="top-end"
|
||||||
|
>
|
||||||
<Padder className={styles.wrapper}>
|
<Padder className={styles.wrapper}>
|
||||||
<Group>
|
<Group>
|
||||||
<h5>Захотелось наружу?</h5>
|
<h5>Захотелось наружу?</h5>
|
||||||
<div>Там холодно, страшно и больше не раздают пончики!</div>
|
<div>Там холодно, страшно и больше не раздают пончики!</div>
|
||||||
<div />
|
<div />
|
||||||
<div><Button onClick={onLogout} color="primary" stretchy>Выпустите меня!</Button></div>
|
<div>
|
||||||
|
<Button onClick={onLogout} color="primary" stretchy>
|
||||||
|
Выпустите меня!
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
</Padder>
|
</Padder>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
export { ProfileSidebarLogoutButton }
|
export { ProfileSidebarLogoutButton };
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import React, { useCallback, useEffect, useMemo, VFC } from 'react';
|
import React, { useCallback, useEffect, useMemo, VFC } from 'react';
|
||||||
|
|
||||||
import { isNil } from 'ramda';
|
|
||||||
|
|
||||||
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
||||||
import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes';
|
import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes';
|
||||||
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
|
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
|
||||||
|
@ -13,6 +11,7 @@ import { ProfileSidebarMenu } from '~/containers/profile/ProfileSidebarMenu';
|
||||||
import { useAuth } from '~/hooks/auth/useAuth';
|
import { useAuth } from '~/hooks/auth/useAuth';
|
||||||
import { useUser } from '~/hooks/auth/useUser';
|
import { useUser } from '~/hooks/auth/useUser';
|
||||||
import type { SidebarComponentProps } from '~/types/sidebar';
|
import type { SidebarComponentProps } from '~/types/sidebar';
|
||||||
|
import { isNil } from '~/utils/ramda';
|
||||||
|
|
||||||
const tabs = ['profile', 'bookmarks'] as const;
|
const tabs = ['profile', 'bookmarks'] as const;
|
||||||
type TabName = typeof tabs[number];
|
type TabName = typeof tabs[number];
|
||||||
|
|
|
@ -8,16 +8,14 @@ import { COMMENTS_DISPLAY } from '~/constants/node';
|
||||||
import { IComment } from '~/types';
|
import { IComment } from '~/types';
|
||||||
import { flatten, isNil } from '~/utils/ramda';
|
import { flatten, isNil } from '~/utils/ramda';
|
||||||
|
|
||||||
const getKey: (nodeId: number) => SWRInfiniteKeyLoader = (nodeId: number) => (
|
const getKey: (nodeId: number) => SWRInfiniteKeyLoader =
|
||||||
pageIndex,
|
(nodeId: number) => (pageIndex, previousPageData: IComment[]) => {
|
||||||
previousPageData: IComment[]
|
|
||||||
) => {
|
|
||||||
if (pageIndex > 0 && !previousPageData?.length) return null;
|
if (pageIndex > 0 && !previousPageData?.length) return null;
|
||||||
return `${API.NODE.COMMENT(nodeId)}?page=${pageIndex}`;
|
return `${API.NODES.COMMENT(nodeId)}?page=${pageIndex}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractKey = (key: string) => {
|
const extractKey = (key: string) => {
|
||||||
const re = new RegExp(`${API.NODE.COMMENT('\\d+')}\\?page=(\\d+)`);
|
const re = new RegExp(`${API.NODES.COMMENT('\\d+')}\\?page=(\\d+)`);
|
||||||
const match = key.match(re);
|
const match = key.match(re);
|
||||||
|
|
||||||
if (!match || !Array.isArray(match) || isNil(match[1])) {
|
if (!match || !Array.isArray(match) || isNil(match[1])) {
|
||||||
|
@ -41,14 +39,26 @@ export const useGetComments = (nodeId: number, fallbackData?: IComment[]) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fallbackData: fallbackData && [fallbackData],
|
fallbackData: fallbackData && [fallbackData],
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const comments = useMemo(() => flatten(data || []), [data]);
|
const comments = useMemo(() => flatten(data || []), [data]);
|
||||||
const hasMore = (data?.[size - 1]?.length || 0) >= COMMENTS_DISPLAY ||
|
const hasMore =
|
||||||
|
(data?.[size - 1]?.length || 0) >= COMMENTS_DISPLAY ||
|
||||||
(!!data?.length && data?.length > 0 && isValidating);
|
(!!data?.length && data?.length > 0 && isValidating);
|
||||||
|
|
||||||
const onLoadMoreComments = useCallback(() => setSize(size + 1), [setSize, size]);
|
const onLoadMoreComments = useCallback(
|
||||||
|
() => setSize(size + 1),
|
||||||
|
[setSize, size],
|
||||||
|
);
|
||||||
|
|
||||||
return { comments, hasMore, onLoadMoreComments, isLoading: !data && isValidating, mutate, data, isLoadingMore: !!data?.length && isValidating };
|
return {
|
||||||
|
comments,
|
||||||
|
hasMore,
|
||||||
|
onLoadMoreComments,
|
||||||
|
isLoading: !data && isValidating,
|
||||||
|
mutate,
|
||||||
|
data,
|
||||||
|
isLoadingMore: !!data?.length && isValidating,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,8 +8,9 @@ import { INode } from '~/types';
|
||||||
import { ApiGetNodeRelatedResult } from '~/types/node';
|
import { ApiGetNodeRelatedResult } from '~/types/node';
|
||||||
|
|
||||||
export const useGetNodeRelated = (id?: INode['id']) => {
|
export const useGetNodeRelated = (id?: INode['id']) => {
|
||||||
const { data, isValidating, mutate } = useSWR<ApiGetNodeRelatedResult>(API.NODE.RELATED(id), () =>
|
const { data, isValidating, mutate } = useSWR<ApiGetNodeRelatedResult>(
|
||||||
apiGetNodeRelated({ id })
|
API.NODES.RELATED(id),
|
||||||
|
() => apiGetNodeRelated({ id }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const refresh = useCallback(() => mutate(data, true), [data, mutate]);
|
const refresh = useCallback(() => mutate(data, true), [data, mutate]);
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { ApiGetNodeResponse } from '~/types/node';
|
||||||
|
|
||||||
const getKey = (nodeId: number, userId = 0) =>
|
const getKey = (nodeId: number, userId = 0) =>
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
url: API.NODE.GET_NODE(nodeId),
|
url: API.NODES.GET(nodeId),
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ export const useLoadNode = (id: number, fallbackData?: ApiGetNodeResponse) => {
|
||||||
const { data, isValidating, mutate } = useSWR<ApiGetNodeResponse>(
|
const { data, isValidating, mutate } = useSWR<ApiGetNodeResponse>(
|
||||||
getKey(id, user.id),
|
getKey(id, user.id),
|
||||||
() => apiGetNode({ id }),
|
() => apiGetNode({ id }),
|
||||||
{ fallbackData, revalidateOnMount: true }
|
{ fallbackData, revalidateOnMount: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
|
@ -33,7 +33,7 @@ export const useLoadNode = (id: number, fallbackData?: ApiGetNodeResponse) => {
|
||||||
|
|
||||||
await mutate({ node: { ...data.node, ...node } }, true);
|
await mutate({ node: { ...data.node, ...node } }, true);
|
||||||
},
|
},
|
||||||
[data, mutate]
|
[data, mutate],
|
||||||
);
|
);
|
||||||
|
|
||||||
useOnNodeSeen(data?.node);
|
useOnNodeSeen(data?.node);
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export * from './useSortableActions';
|
|
|
@ -23,6 +23,13 @@ import { NodeRelatedProvider } from '~/utils/providers/NodeRelatedProvider';
|
||||||
import { uniqBy } from '~/utils/ramda';
|
import { uniqBy } from '~/utils/ramda';
|
||||||
|
|
||||||
export const getStaticPaths = async () => {
|
export const getStaticPaths = async () => {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
return {
|
||||||
|
paths: [],
|
||||||
|
fallback: 'blocking',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const recent = await getNodeDiff({
|
const recent = await getNodeDiff({
|
||||||
with_heroes: false,
|
with_heroes: false,
|
||||||
with_recent: true,
|
with_recent: true,
|
||||||
|
@ -30,40 +37,48 @@ export const getStaticPaths = async () => {
|
||||||
with_valid: false,
|
with_valid: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const recentIDs = uniqBy(it => it.id, [
|
const recentIDs = uniqBy(
|
||||||
|
(it) => it.id,
|
||||||
|
[
|
||||||
...(recent.after || []),
|
...(recent.after || []),
|
||||||
...(recent.before || []),
|
...(recent.before || []),
|
||||||
...(recent.recent || []),
|
...(recent.recent || []),
|
||||||
])
|
],
|
||||||
.filter(it => it.id)
|
)
|
||||||
.map(it => it.id!.toString());
|
.filter((it) => it.id)
|
||||||
|
.map((it) => it.id!.toString());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
paths: recentIDs.map(id => ({ params: { id } })),
|
paths: recentIDs.map((id) => ({ params: { id } })),
|
||||||
fallback: 'blocking',
|
fallback: 'blocking',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStaticProps = async (
|
export const getStaticProps = async (
|
||||||
context
|
context,
|
||||||
): Promise<GetStaticPropsResult<{ fallbackData: ApiGetNodeResponse; comments?: IComment[] }>> => {
|
): Promise<
|
||||||
|
GetStaticPropsResult<{
|
||||||
|
fallbackData: ApiGetNodeResponse;
|
||||||
|
comments?: IComment[];
|
||||||
|
}>
|
||||||
|
> => {
|
||||||
try {
|
try {
|
||||||
if (!context.params?.id) {
|
if (!context.params?.id) {
|
||||||
return { notFound: true };
|
return { notFound: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = parseInt(context.params.id, 10);
|
const id = parseInt(context.params.id, 10);
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return { notFound: true };
|
return { notFound: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallbackData = await apiGetNode({ id });
|
const [fallbackData, { comments }] = await Promise.all([
|
||||||
|
apiGetNode({ id }),
|
||||||
const comments = await apiGetNodeComments({
|
apiGetNodeComments({
|
||||||
id,
|
id,
|
||||||
take: COMMENTS_DISPLAY,
|
take: COMMENTS_DISPLAY,
|
||||||
});
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
@ -71,7 +86,7 @@ export const getStaticProps = async (
|
||||||
...fallbackData,
|
...fallbackData,
|
||||||
last_seen: fallbackData.last_seen ?? null,
|
last_seen: fallbackData.last_seen ?? null,
|
||||||
},
|
},
|
||||||
comments: comments.comments,
|
comments,
|
||||||
},
|
},
|
||||||
revalidate: 7 * 86400, // every week
|
revalidate: 7 * 86400, // every week
|
||||||
};
|
};
|
||||||
|
@ -83,11 +98,15 @@ export const getStaticProps = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = RouteComponentProps<{ id: string }> & InferGetStaticPropsType<typeof getStaticProps>;
|
type Props = RouteComponentProps<{ id: string }> &
|
||||||
|
InferGetStaticPropsType<typeof getStaticProps>;
|
||||||
|
|
||||||
const NodePage: FC<Props> = observer(props => {
|
const NodePage: FC<Props> = observer((props) => {
|
||||||
const id = useNodePageParams();
|
const id = useNodePageParams();
|
||||||
const { node, isLoading, update, lastSeen } = useLoadNode(parseInt(id, 10), props.fallbackData);
|
const { node, isLoading, update, lastSeen } = useLoadNode(
|
||||||
|
parseInt(id, 10),
|
||||||
|
props.fallbackData,
|
||||||
|
);
|
||||||
|
|
||||||
const onShowImageModal = useImageModal();
|
const onShowImageModal = useImageModal();
|
||||||
|
|
||||||
|
@ -101,9 +120,11 @@ const NodePage: FC<Props> = observer(props => {
|
||||||
isLoadingMore: isLoadingMoreComments,
|
isLoadingMore: isLoadingMoreComments,
|
||||||
} = useNodeComments(parseInt(id, 10), props.comments);
|
} = useNodeComments(parseInt(id, 10), props.comments);
|
||||||
|
|
||||||
const { onDelete: onTagDelete, onChange: onTagsChange, onClick: onTagClick } = useNodeTags(
|
const {
|
||||||
parseInt(id, 10)
|
onDelete: onTagDelete,
|
||||||
);
|
onChange: onTagsChange,
|
||||||
|
onClick: onTagClick,
|
||||||
|
} = useNodeTags(parseInt(id, 10));
|
||||||
const [canEdit] = useNodePermissions(node);
|
const [canEdit] = useNodePermissions(node);
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
|
|
|
@ -9,12 +9,12 @@ import {
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { has, omit } from 'ramda';
|
|
||||||
|
|
||||||
import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
|
import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
|
||||||
import { SidebarName } from '~/constants/sidebar';
|
import { SidebarName } from '~/constants/sidebar';
|
||||||
import { sidebarComponents } from '~/constants/sidebar/components';
|
import { sidebarComponents } from '~/constants/sidebar/components';
|
||||||
import { SidebarComponent, SidebarProps } from '~/types/sidebar';
|
import { SidebarComponent, SidebarProps } from '~/types/sidebar';
|
||||||
|
import { has, omit } from '~/utils/ramda';
|
||||||
|
|
||||||
type ContextValue = typeof SidebarContext extends Context<infer U> ? U : never;
|
type ContextValue = typeof SidebarContext extends Context<infer U> ? U : never;
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,8 @@ import React, {
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { keys } from 'ramda';
|
|
||||||
|
|
||||||
import { Theme } from '~/constants/themes';
|
import { Theme } from '~/constants/themes';
|
||||||
|
import { keys } from '~/utils/ramda';
|
||||||
|
|
||||||
interface ProvidersProps {}
|
interface ProvidersProps {}
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue