diff --git a/.env.local b/.env.local index 9c946691..5046a0a7 100644 --- a/.env.local +++ b/.env.local @@ -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.vault48.org/ -NEXT_PUBLIC_REMOTE_CURRENT=https://pig.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/ diff --git a/src/api/notes/index.ts b/src/api/notes/index.ts new file mode 100644 index 00000000..21206e45 --- /dev/null +++ b/src/api/notes/index.ts @@ -0,0 +1,8 @@ +import { ApiGetNotesRequest, ApiGetNotesResponse } from '~/api/notes/types'; +import { URLS } from '~/constants/urls'; +import { api, cleanResult } from '~/utils/api'; + +export const apiGetNotes = ({ limit, offset, search }: ApiGetNotesRequest) => + api + .get(URLS.NOTES, { params: { limit, offset, search } }) + .then(cleanResult); diff --git a/src/api/notes/types.ts b/src/api/notes/types.ts new file mode 100644 index 00000000..921b686e --- /dev/null +++ b/src/api/notes/types.ts @@ -0,0 +1,12 @@ +import { Note } from '~/types/notes'; + +export interface ApiGetNotesRequest { + limit: number; + offset: number; + search: string; +} + +export interface ApiGetNotesResponse { + list: Note[]; + totalCount: number; +} diff --git a/src/components/notes/NoteCard/index.tsx b/src/components/notes/NoteCard/index.tsx new file mode 100644 index 00000000..41392263 --- /dev/null +++ b/src/components/notes/NoteCard/index.tsx @@ -0,0 +1,24 @@ +import React, { VFC } from 'react'; + +import { Card } from '~/components/containers/Card'; +import { Markdown } from '~/components/containers/Markdown'; +import { Padder } from '~/components/containers/Padder'; +import { formatText, getPrettyDate } from '~/utils/dom'; + +import styles from './styles.module.scss'; + +interface NoteCardProps { + content: string; + createdAt: string; +} + +const NoteCard: VFC = ({ content, createdAt }) => ( + + + + + {getPrettyDate(createdAt)} + +); + +export { NoteCard }; diff --git a/src/components/notes/NoteCard/styles.module.scss b/src/components/notes/NoteCard/styles.module.scss new file mode 100644 index 00000000..4a378faa --- /dev/null +++ b/src/components/notes/NoteCard/styles.module.scss @@ -0,0 +1,18 @@ +@import "src/styles/variables"; +@import "src/styles/mixins"; + +.note { + min-width: 0; + word-break: break-word; + padding: 0; + + & > * { + @include row_shadow; + } +} + +.footer { + font: $font_12_regular; + text-align: right; + opacity: 0.5; +} diff --git a/src/constants/urls.ts b/src/constants/urls.ts index 5b041551..d9214301 100644 --- a/src/constants/urls.ts +++ b/src/constants/urls.ts @@ -22,6 +22,7 @@ export const URLS = { NOTES: '/settings/notes', TRASH: '/settings/trash', }, + NOTES: '/notes/', }; export const ImagePresets = { diff --git a/src/containers/settings/SettingsNotes/index.tsx b/src/containers/settings/SettingsNotes/index.tsx index 6420bf08..54b31127 100644 --- a/src/containers/settings/SettingsNotes/index.tsx +++ b/src/containers/settings/SettingsNotes/index.tsx @@ -5,12 +5,16 @@ import Masonry from 'react-masonry-css'; import { Card } from '~/components/containers/Card'; import { Filler } from '~/components/containers/Filler'; import { Group } from '~/components/containers/Group'; +import { Markdown } from '~/components/containers/Markdown'; import { Padder } from '~/components/containers/Padder'; import { Button } from '~/components/input/Button'; import { Icon } from '~/components/input/Icon'; import { InputText } from '~/components/input/InputText'; import { Textarea } from '~/components/input/Textarea'; import { HorizontalMenu } from '~/components/menu/HorizontalMenu'; +import { NoteCard } from '~/components/notes/NoteCard'; +import { useGetNotes } from '~/hooks/notes/useGetNotes'; +import { formatText } from '~/utils/dom'; import styles from './styles.module.scss'; @@ -21,14 +25,9 @@ const breakpointCols = { 1280: 1, }; -const sampleNotes = [...new Array(40)].map((_, i) => ( - - {i} - -)); - const SettingsNotes: VFC = () => { const [text, setText] = useState(''); + const { notes } = useGetNotes(''); return (
@@ -61,7 +60,9 @@ const SettingsNotes: VFC = () => { - {sampleNotes} + {notes.map(note => ( + + ))}
); diff --git a/src/hooks/notes/useGetNotes.ts b/src/hooks/notes/useGetNotes.ts new file mode 100644 index 00000000..2ff6cf73 --- /dev/null +++ b/src/hooks/notes/useGetNotes.ts @@ -0,0 +1,58 @@ +import { useCallback, useMemo } from 'react'; + +import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite'; + +import { getLabNodes } from '~/api/lab'; +import { apiGetNotes } from '~/api/notes'; +import { ApiGetNotesRequest } from '~/api/notes/types'; +import { useAuth } from '~/hooks/auth/useAuth'; +import { useUser } from '~/hooks/auth/useUser'; +import { GetLabNodesRequest, ILabNode, LabNodesSort } from '~/types/lab'; +import { flatten, uniqBy } from '~/utils/ramda'; + +const DEFAULT_COUNT = 20; + +const getKey: (isUser: boolean, search: string) => SWRInfiniteKeyLoader = (isUser, search) => ( + index, + prev: ILabNode[] +) => { + if (!isUser) return null; + if (index > 0 && (!prev?.length || prev.length < 20)) return null; + + const props: GetLabNodesRequest = { + limit: DEFAULT_COUNT, + offset: index * DEFAULT_COUNT, + search: search || '', + }; + + return JSON.stringify(props); +}; + +const parseKey = (key: string): ApiGetNotesRequest => { + try { + return JSON.parse(key); + } catch (error) { + return { limit: DEFAULT_COUNT, offset: 0, search: '' }; + } +}; + +export const useGetNotes = (search: string) => { + const { isUser } = useAuth(); + + const { data, isValidating, size, setSize, mutate } = useSWRInfinite( + getKey(isUser, search), + async (key: string) => { + const result = await apiGetNotes(parseKey(key)); + return result.list; + }, + { + dedupingInterval: 300, + } + ); + + const notes = useMemo(() => uniqBy(n => n.id, flatten(data || [])), [data]); + const hasMore = (data?.[size - 1]?.length || 0) >= 1; + const loadMore = useCallback(() => setSize(size + 1), [setSize, size]); + + return { notes, hasMore, loadMore, isLoading: !data && isValidating }; +}; diff --git a/src/types/notes/index.ts b/src/types/notes/index.ts new file mode 100644 index 00000000..0ae0e730 --- /dev/null +++ b/src/types/notes/index.ts @@ -0,0 +1,5 @@ +export interface Note { + id: number; + content: string; + created_at: string; +}