1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-05-01 23:56:41 +07:00

fixed some paths according to new backend

This commit is contained in:
Fedor Katurov 2022-08-24 17:25:06 +07:00
parent 2f6207feaa
commit 4912fd255c
27 changed files with 228 additions and 217 deletions
.env.local.eslintrc.js
src
api
components
comment
CommentAvatar
CommentFormAttaches
editors
AudioGrid
ImageGrid
menu
node
MenuButton
NodeEditMenu
NodeTitle
sortable
SortableGrid
SortableList
index.ts
constants
containers
profile/ProfileSidebarLogoutButton
sidebars/ProfileSidebar
hooks
pages/node
utils/providers
tsconfig.tsbuildinfo

View file

@ -1,6 +1,6 @@
NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
# NEXT_PUBLIC_API_HOST=http://localhost:8888/
# NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
# NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
# NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
NEXT_PUBLIC_API_HOST=http://localhost:8888/
NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
# NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
# NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/

View file

@ -23,6 +23,18 @@ module.exports = {
pathGroupsExcludedImportTypes: ['react'],
},
],
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'ramda',
message:
'import from \'~/utils/ramda\' instead',
},
],
},
]
},
parserOptions: {
ecmaVersion: 7,

View file

@ -5,10 +5,16 @@ import { api, cleanResult } from '~/utils/api';
export const postCellView = ({ id, flow }: PostCellViewRequest) =>
api
.post<PostCellViewResult>(API.NODE.SET_CELL_VIEW(id), { flow })
.post<PostCellViewResult>(API.NODES.SET_CELL_VIEW(id), { flow })
.then(cleanResult);
export const getSearchResults = ({ text, skip, take }: GetSearchResultsRequest) =>
export const getSearchResults = ({
text,
skip,
take,
}: GetSearchResultsRequest) =>
api
.get<GetSearchResultsResult>(API.SEARCH.NODES, { params: { text, skip, take } })
.get<GetSearchResultsResult>(API.SEARCH.NODES, {
params: { text, skip, take },
})
.then(cleanResult);

View file

@ -44,7 +44,7 @@ export type ApiGetNodeCommentsResponse = {
};
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 = ({
start,
@ -56,7 +56,7 @@ export const getNodeDiff = ({
with_valid,
}: GetNodeDiffRequest) =>
api
.get<GetNodeDiffResult>(API.NODE.GET_DIFF, {
.get<GetNodeDiffResult>(API.NODES.LIST, {
params: {
start,
end,
@ -74,7 +74,7 @@ export const apiGetNode = (
config?: AxiosRequestConfig,
) =>
api
.get<ApiGetNodeResponse>(API.NODE.GET_NODE(id), config)
.get<ApiGetNodeResponse>(API.NODES.GET(id), config)
.then(cleanResult)
.then((data) => ({ node: data.node, last_seen: data.last_seen }));
@ -82,7 +82,7 @@ export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => {
const cancelToken = axios.CancelToken.source();
return {
request: api
.get<ApiGetNodeResponse>(API.NODE.GET_NODE(id), {
.get<ApiGetNodeResponse>(API.NODES.GET(id), {
cancelToken: cancelToken.token,
})
.then(cleanResult),
@ -91,7 +91,7 @@ export const apiGetNodeWithCancel = ({ id }: ApiGetNodeRequest) => {
};
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 = ({
id,
@ -99,35 +99,33 @@ export const apiGetNodeComments = ({
skip = 0,
}: ApiGetNodeCommentsRequest) =>
api
.get<ApiGetNodeCommentsResponse>(API.NODE.COMMENT(id), {
.get<ApiGetNodeCommentsResponse>(API.NODES.COMMENT(id), {
params: { take, skip },
})
.then(cleanResult);
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) =>
api
.post<ApiPostNodeTagsResult>(API.NODE.UPDATE_TAGS(id), { tags })
.post<ApiPostNodeTagsResult>(API.NODES.UPDATE_TAGS(id), { tags })
.then(cleanResult);
export const apiDeleteNodeTag = ({ id, tagId }: ApiDeleteNodeTagsRequest) =>
api
.delete<ApiDeleteNodeTagsResult>(API.NODE.DELETE_TAG(id, tagId))
.delete<ApiDeleteNodeTagsResult>(API.NODES.DELETE_TAG(id, tagId))
.then(cleanResult);
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) =>
api
.post<ApiPostNodeHeroicResponse>(API.NODE.POST_HEROIC(id))
.then(cleanResult);
api.post<ApiPostNodeHeroicResponse>(API.NODES.HEROIC(id)).then(cleanResult);
export const apiLockNode = ({ id, is_locked }: ApiLockNodeRequest) =>
api
.post<ApiLockNodeResult>(API.NODE.POST_LOCK(id), { is_locked })
.delete<ApiLockNodeResult>(API.NODES.DELETE(id), { params: { is_locked } })
.then(cleanResult);
export const apiLockComment = ({
@ -136,7 +134,7 @@ export const apiLockComment = ({
nodeId,
}: ApiLockCommentRequest) =>
api
.delete<ApiLockcommentResult>(API.NODE.LOCK_COMMENT(nodeId, id), {
.delete<ApiLockcommentResult>(API.NODES.LOCK_COMMENT(nodeId, id), {
params: {
is_locked: isLocked,
},

View file

@ -1,7 +1,7 @@
import React, { FC } from 'react';
import { Avatar } from '~/components/common/Avatar';
import { MenuButton } from '~/components/menu';
import { MenuButton } from '~/components/menu/MenuButton';
import { ProfileQuickInfo } from '~/containers/profile/ProfileQuickInfo';
import { IUser } from '~/types/auth';
import { path } from '~/utils/ramda';
@ -16,7 +16,11 @@ const CommentAvatar: FC<Props> = ({ user, className }) => {
<MenuButton
position="auto"
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} />

View file

@ -1,6 +1,7 @@
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 { useFileDropZone } from '~/hooks';
import { IFile } from '~/types';
@ -27,34 +28,36 @@ const CommentFormAttaches: FC = () => {
const onImageMove = useCallback(
(newFiles: IFile[]) => {
setFiles([...filesAudios, ...newFiles.filter(it => it)]);
setFiles([...filesAudios, ...newFiles.filter((it) => it)]);
},
[setFiles, filesImages, filesAudios]
[setFiles, filesImages, filesAudios],
);
const onAudioMove = useCallback(
(newFiles: IFile[]) => {
setFiles([...filesImages, ...newFiles]);
},
[setFiles, filesImages, filesAudios]
[setFiles, filesImages, filesAudios],
);
const onFileDelete = useCallback(
(fileId: IFile['id']) => {
setFiles(files.filter(file => file.id !== fileId));
setFiles(files.filter((file) => file.id !== fileId));
},
[files, setFiles]
[files, setFiles],
);
const onAudioTitleChange = useCallback(
(fileId: IFile['id'], title: string) => {
setFiles(
files.map(file =>
file.id === fileId ? { ...file, metadata: { ...file.metadata, title } } : file
)
files.map((file) =>
file.id === fileId
? { ...file, metadata: { ...file.metadata, title } }
: file,
),
);
},
[files, setFiles]
[files, setFiles],
);
if (!hasAttaches) return null;

View file

@ -1,6 +1,6 @@
import React, { FC, useCallback } from 'react';
import { SortableAudioGrid } from '~/components/sortable';
import { SortableAudioGrid } from '~/components/sortable/SortableAudioGrid';
import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types';
@ -15,25 +15,27 @@ const AudioGrid: FC<IProps> = ({ files, setFiles, locked }) => {
(newFiles: IFile[]) => {
setFiles(newFiles);
},
[setFiles, files]
[setFiles, files],
);
const onDrop = useCallback(
(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(
(changeId: IFile['id'], title: string) => {
setFiles(
files.map(file =>
file && file.id === changeId ? { ...file, metadata: { ...file.metadata, title } } : file
)
files.map((file) =>
file && file.id === changeId
? { ...file, metadata: { ...file.metadata, title } }
: file,
),
);
},
[setFiles, files]
[setFiles, files],
);
return (

View file

@ -1,6 +1,6 @@
import React, { FC, useCallback } from 'react';
import { SortableImageGrid } from '~/components/sortable';
import { SortableImageGrid } from '~/components/sortable/SortableImageGrid';
import { useWindowSize } from '~/hooks/dom/useWindowSize';
import { UploadStatus } from '~/store/uploader/UploaderStore';
import { IFile } from '~/types';
@ -16,14 +16,14 @@ const ImageGrid: FC<IProps> = ({ files, setFiles, locked }) => {
const onMove = useCallback(
(newFiles: IFile[]) => {
setFiles(newFiles.filter(it => it));
setFiles(newFiles.filter((it) => it));
},
[setFiles, files],
);
const onDrop = useCallback(
(id: IFile['id']) => {
setFiles(files.filter(file => file && file.id !== id));
setFiles(files.filter((file) => file && file.id !== id));
},
[setFiles, files],
);

View file

@ -1,5 +0,0 @@
export * from './VerticalMenu';
export * from './HorizontalMenu';
export * from './MenuButton';
export * from './MenuItemWithIcon';
export * from './SeparatedMenu';

View file

@ -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 };

View file

@ -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;
}

View file

@ -3,7 +3,9 @@ import React, { VFC } from 'react';
import Tippy from '@tippyjs/react';
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 styles from './styles.module.scss';

View file

@ -3,7 +3,7 @@ import React, { memo, VFC } from 'react';
import classNames from 'classnames';
import { Icon } from '~/components/input/Icon';
import { SeparatedMenu } from '~/components/menu';
import { SeparatedMenu } from '~/components/menu/SeparatedMenu';
import { NodeEditMenu } from '~/components/node/NodeEditMenu';
import { Placeholder } from '~/components/placeholders/Placeholder';
import { getPrettyDate } from '~/utils/dom';
@ -35,7 +35,6 @@ interface IProps {
const NodeTitle: VFC<IProps> = memo(
({
id,
title,
username,
createdAt,
@ -69,7 +68,9 @@ const NodeTitle: VFC<IProps> = memo(
{isLoading ? (
<Placeholder width="100px" />
) : (
`~${username.toLocaleLowerCase()}, ${getPrettyDate(createdAt)}`
`~${username.toLocaleLowerCase()}, ${getPrettyDate(
createdAt,
)}`
)}
</aside>
)}
@ -90,7 +91,9 @@ const NodeTitle: VFC<IProps> = memo(
{canLike && (
<div
className={classNames(styles.button, styles.like, { [styles.is_liked]: isLiked })}
className={classNames(styles.button, styles.like, {
[styles.is_liked]: isLiked,
})}
>
{isLiked ? (
<Icon icon="heart_full" size={24} onClick={onLike} />
@ -107,7 +110,7 @@ const NodeTitle: VFC<IProps> = memo(
</div>
</div>
);
}
},
);
export { NodeTitle };

View file

@ -5,7 +5,7 @@ import { rectSortingStrategy, SortableContext } from '@dnd-kit/sortable';
import classNames from 'classnames';
import { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
import { useSortableActions } from '~/hooks/sortable';
import { useSortableActions } from '~/hooks/sortable/useSortableActions';
import { DivProps } from '~/utils/types';
import { SortableItem } from '../SortableItem';
@ -16,7 +16,7 @@ interface SortableGridProps<
ItemRendererProps extends {},
LockedRendererProps extends {},
Item extends {},
Locked extends {}
Locked extends {},
> {
items: Item[];
locked: Locked[];
@ -44,18 +44,18 @@ const SortableGrid = <RIP, RLP, I, L>({
onSortEnd,
size,
}: SortableGridProps<RIP, RLP, I, L>) => {
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
items,
getID,
onSortEnd
);
const { sensors, onDragEnd, onDragStart, draggingItem, ids } =
useSortableActions(items, getID, onSortEnd);
const gridStyle = useMemo<DivProps['style']>(
() =>
size
? { gridTemplateColumns: size && `repeat(auto-fill, minmax(${size}px, 1fr))` }
? {
gridTemplateColumns:
size && `repeat(auto-fill, minmax(${size}px, 1fr))`,
}
: undefined,
[size]
[size],
);
return (
@ -67,30 +67,35 @@ const SortableGrid = <RIP, RLP, I, L>({
>
<SortableContext items={ids} strategy={rectSortingStrategy}>
<div className={classNames(styles.grid, className)} style={gridStyle}>
{items.map(item => (
{items.map((item) => (
<SortableItem
key={getID(item)}
id={getID(item)}
className={
draggingItem && getID(item) === getID(draggingItem) ? styles.dragging : undefined
draggingItem && getID(item) === getID(draggingItem)
? styles.dragging
: undefined
}
>
{createElement(renderItem, { ...renderItemProps, item })}
</SortableItem>
))}
{locked.map(item =>
{locked.map((item) =>
createElement(renderLocked, {
...renderLockedProps,
locked: item,
key: getLockedID(item),
})
}),
)}
<DragOverlay>
{draggingItem ? (
<DragOverlayItem>
{createElement(renderItem, { ...renderItemProps, item: draggingItem })}
{createElement(renderItem, {
...renderItemProps,
item: draggingItem,
})}
</DragOverlayItem>
) : null}
</DragOverlay>

View file

@ -1,12 +1,15 @@
import React, { createElement, FC } from 'react';
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 { DragOverlayItem } from '~/components/sortable/DragOverlayItem';
import { SortableItem } from '~/components/sortable/SortableItem';
import { useSortableActions } from '~/hooks/sortable';
import { useSortableActions } from '~/hooks/sortable/useSortableActions';
import styles from './styles.module.scss';
@ -14,7 +17,7 @@ interface SortableListProps<
RenderItemProps extends {},
RenderLockedProps extends {},
Item extends {},
Locked extends {}
Locked extends {},
> {
items: Item[];
locked: Locked[];
@ -40,11 +43,8 @@ const SortableList = <RIP, RLP, I, L>({
renderLockedProps,
onSortEnd,
}: SortableListProps<RIP, RLP, I, L>) => {
const { sensors, onDragEnd, onDragStart, draggingItem, ids } = useSortableActions(
items,
getID,
onSortEnd
);
const { sensors, onDragEnd, onDragStart, draggingItem, ids } =
useSortableActions(items, getID, onSortEnd);
return (
<DndContext
@ -55,30 +55,39 @@ const SortableList = <RIP, RLP, I, L>({
>
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
<div className={classNames(styles.grid, className)}>
{items.map(item => (
{items.map((item) => (
<SortableItem
key={getID(item)}
id={getID(item)}
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>
))}
{locked.map(item =>
{locked.map((item) =>
createElement(renderLocked, {
...renderLockedProps,
locked: item,
key: getLockedID(item),
})
}),
)}
<DragOverlay>
{draggingItem ? (
<DragOverlayItem>
{createElement(renderItem, { ...renderItemProps, item: draggingItem })}
{createElement(renderItem, {
...renderItemProps,
item: draggingItem,
})}
</DragOverlayItem>
) : null}
</DragOverlay>

View file

@ -1,2 +0,0 @@
export * from './SortableImageGrid';
export * from './SortableAudioGrid';

View file

@ -23,23 +23,23 @@ export const API = {
ATTACH_SOCIAL: `/oauth/attach`,
LOGIN_WITH_SOCIAL: `/oauth/login`,
},
NODE: {
NODES: {
SAVE: '/nodes/',
GET_DIFF: '/nodes/',
GET_NODE: (id: number | string) => `/nodes/${id}`,
LIST: '/nodes/',
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`,
UPDATE_TAGS: (id: INode['id']) => `/nodes/${id}/tags`,
DELETE_TAG: (id: INode['id'], tagId: ITag['ID']) =>
`/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']) =>
`/nodes/${id}/comment/${comment_id}`,
`/nodes/${id}/comments/${comment_id}`,
},
SEARCH: {
NODES: '/search/nodes',

View file

@ -3,8 +3,7 @@ import React, { FC } from 'react';
import { Group } from '~/components/containers/Group';
import { Padder } from '~/components/containers/Padder';
import { Button } from '~/components/input/Button';
import { Icon } from '~/components/input/Icon';
import { MenuButton } from '~/components/menu';
import { MenuButton } from '~/components/menu/MenuButton';
import styles from './styles.module.scss';
@ -12,17 +11,30 @@ interface ProfileSidebarLogoutButtonProps {
onLogout?: () => void;
}
const ProfileSidebarLogoutButton: FC<ProfileSidebarLogoutButtonProps> = ({ onLogout }) => (
<MenuButton icon={<Button color="link" iconRight="logout">Выйти</Button>} position="top-end">
const ProfileSidebarLogoutButton: FC<ProfileSidebarLogoutButtonProps> = ({
onLogout,
}) => (
<MenuButton
icon={
<Button color="link" iconRight="logout">
Выйти
</Button>
}
position="top-end"
>
<Padder className={styles.wrapper}>
<Group>
<h5>Захотелось наружу?</h5>
<div>Там холодно, страшно и больше не раздают пончики!</div>
<div />
<div><Button onClick={onLogout} color="primary" stretchy>Выпустите меня!</Button></div>
<div>
<Button onClick={onLogout} color="primary" stretchy>
Выпустите меня!
</Button>
</div>
</Group>
</Padder>
</MenuButton>
);
export { ProfileSidebarLogoutButton }
export { ProfileSidebarLogoutButton };

View file

@ -1,7 +1,5 @@
import React, { useCallback, useEffect, useMemo, VFC } from 'react';
import { isNil } from 'ramda';
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
import { ProfileSidebarNotes } from '~/components/profile/ProfileSidebarNotes';
import { ProfileSidebarSettings } from '~/components/profile/ProfileSidebarSettings';
@ -13,6 +11,7 @@ import { ProfileSidebarMenu } from '~/containers/profile/ProfileSidebarMenu';
import { useAuth } from '~/hooks/auth/useAuth';
import { useUser } from '~/hooks/auth/useUser';
import type { SidebarComponentProps } from '~/types/sidebar';
import { isNil } from '~/utils/ramda';
const tabs = ['profile', 'bookmarks'] as const;
type TabName = typeof tabs[number];

View file

@ -8,16 +8,14 @@ import { COMMENTS_DISPLAY } from '~/constants/node';
import { IComment } from '~/types';
import { flatten, isNil } from '~/utils/ramda';
const getKey: (nodeId: number) => SWRInfiniteKeyLoader = (nodeId: number) => (
pageIndex,
previousPageData: IComment[]
) => {
if (pageIndex > 0 && !previousPageData?.length) return null;
return `${API.NODE.COMMENT(nodeId)}?page=${pageIndex}`;
};
const getKey: (nodeId: number) => SWRInfiniteKeyLoader =
(nodeId: number) => (pageIndex, previousPageData: IComment[]) => {
if (pageIndex > 0 && !previousPageData?.length) return null;
return `${API.NODES.COMMENT(nodeId)}?page=${pageIndex}`;
};
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);
if (!match || !Array.isArray(match) || isNil(match[1])) {
@ -41,14 +39,26 @@ export const useGetComments = (nodeId: number, fallbackData?: IComment[]) => {
},
{
fallbackData: fallbackData && [fallbackData],
}
},
);
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);
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,
};
};

View file

@ -8,8 +8,9 @@ import { INode } from '~/types';
import { ApiGetNodeRelatedResult } from '~/types/node';
export const useGetNodeRelated = (id?: INode['id']) => {
const { data, isValidating, mutate } = useSWR<ApiGetNodeRelatedResult>(API.NODE.RELATED(id), () =>
apiGetNodeRelated({ id })
const { data, isValidating, mutate } = useSWR<ApiGetNodeRelatedResult>(
API.NODES.RELATED(id),
() => apiGetNodeRelated({ id }),
);
const refresh = useCallback(() => mutate(data, true), [data, mutate]);

View file

@ -12,7 +12,7 @@ import { ApiGetNodeResponse } from '~/types/node';
const getKey = (nodeId: number, userId = 0) =>
JSON.stringify({
url: API.NODE.GET_NODE(nodeId),
url: API.NODES.GET(nodeId),
userId,
});
@ -21,7 +21,7 @@ export const useLoadNode = (id: number, fallbackData?: ApiGetNodeResponse) => {
const { data, isValidating, mutate } = useSWR<ApiGetNodeResponse>(
getKey(id, user.id),
() => apiGetNode({ id }),
{ fallbackData, revalidateOnMount: true }
{ fallbackData, revalidateOnMount: true },
);
const update = useCallback(
@ -33,7 +33,7 @@ export const useLoadNode = (id: number, fallbackData?: ApiGetNodeResponse) => {
await mutate({ node: { ...data.node, ...node } }, true);
},
[data, mutate]
[data, mutate],
);
useOnNodeSeen(data?.node);

View file

@ -1 +0,0 @@
export * from './useSortableActions';

View file

@ -23,6 +23,13 @@ import { NodeRelatedProvider } from '~/utils/providers/NodeRelatedProvider';
import { uniqBy } from '~/utils/ramda';
export const getStaticPaths = async () => {
if (process.env.NODE_ENV === 'development') {
return {
paths: [],
fallback: 'blocking',
};
}
const recent = await getNodeDiff({
with_heroes: false,
with_recent: true,
@ -30,40 +37,48 @@ export const getStaticPaths = async () => {
with_valid: false,
});
const recentIDs = uniqBy(it => it.id, [
...(recent.after || []),
...(recent.before || []),
...(recent.recent || []),
])
.filter(it => it.id)
.map(it => it.id!.toString());
const recentIDs = uniqBy(
(it) => it.id,
[
...(recent.after || []),
...(recent.before || []),
...(recent.recent || []),
],
)
.filter((it) => it.id)
.map((it) => it.id!.toString());
return {
paths: recentIDs.map(id => ({ params: { id } })),
paths: recentIDs.map((id) => ({ params: { id } })),
fallback: 'blocking',
};
};
export const getStaticProps = async (
context
): Promise<GetStaticPropsResult<{ fallbackData: ApiGetNodeResponse; comments?: IComment[] }>> => {
context,
): Promise<
GetStaticPropsResult<{
fallbackData: ApiGetNodeResponse;
comments?: IComment[];
}>
> => {
try {
if (!context.params?.id) {
return { notFound: true };
}
const id = parseInt(context.params.id, 10);
if (!id) {
return { notFound: true };
}
const fallbackData = await apiGetNode({ id });
const comments = await apiGetNodeComments({
id,
take: COMMENTS_DISPLAY,
});
const [fallbackData, { comments }] = await Promise.all([
apiGetNode({ id }),
apiGetNodeComments({
id,
take: COMMENTS_DISPLAY,
}),
]);
return {
props: {
@ -71,7 +86,7 @@ export const getStaticProps = async (
...fallbackData,
last_seen: fallbackData.last_seen ?? null,
},
comments: comments.comments,
comments,
},
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 { node, isLoading, update, lastSeen } = useLoadNode(parseInt(id, 10), props.fallbackData);
const { node, isLoading, update, lastSeen } = useLoadNode(
parseInt(id, 10),
props.fallbackData,
);
const onShowImageModal = useImageModal();
@ -101,9 +120,11 @@ const NodePage: FC<Props> = observer(props => {
isLoadingMore: isLoadingMoreComments,
} = useNodeComments(parseInt(id, 10), props.comments);
const { onDelete: onTagDelete, onChange: onTagsChange, onClick: onTagClick } = useNodeTags(
parseInt(id, 10)
);
const {
onDelete: onTagDelete,
onChange: onTagsChange,
onClick: onTagClick,
} = useNodeTags(parseInt(id, 10));
const [canEdit] = useNodePermissions(node);
if (!node) {

View file

@ -9,12 +9,12 @@ import {
} from 'react';
import { useRouter } from 'next/router';
import { has, omit } from 'ramda';
import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
import { SidebarName } from '~/constants/sidebar';
import { sidebarComponents } from '~/constants/sidebar/components';
import { SidebarComponent, SidebarProps } from '~/types/sidebar';
import { has, omit } from '~/utils/ramda';
type ContextValue = typeof SidebarContext extends Context<infer U> ? U : never;

View file

@ -7,9 +7,8 @@ import React, {
useState,
} from 'react';
import { keys } from 'ramda';
import { Theme } from '~/constants/themes';
import { keys } from '~/utils/ramda';
interface ProvidersProps {}

File diff suppressed because one or more lines are too long