mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
added notes
This commit is contained in:
parent
592284f438
commit
fce11163aa
9 changed files with 138 additions and 11 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/
|
||||||
|
|
8
src/api/notes/index.ts
Normal file
8
src/api/notes/index.ts
Normal file
|
@ -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<ApiGetNotesResponse>(URLS.NOTES, { params: { limit, offset, search } })
|
||||||
|
.then(cleanResult);
|
12
src/api/notes/types.ts
Normal file
12
src/api/notes/types.ts
Normal file
|
@ -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;
|
||||||
|
}
|
24
src/components/notes/NoteCard/index.tsx
Normal file
24
src/components/notes/NoteCard/index.tsx
Normal file
|
@ -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<NoteCardProps> = ({ content, createdAt }) => (
|
||||||
|
<Card className={styles.note}>
|
||||||
|
<Padder>
|
||||||
|
<Markdown className={styles.wrap} dangerouslySetInnerHTML={{ __html: formatText(content) }} />
|
||||||
|
</Padder>
|
||||||
|
<Padder className={styles.footer}>{getPrettyDate(createdAt)}</Padder>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { NoteCard };
|
18
src/components/notes/NoteCard/styles.module.scss
Normal file
18
src/components/notes/NoteCard/styles.module.scss
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ export const URLS = {
|
||||||
NOTES: '/settings/notes',
|
NOTES: '/settings/notes',
|
||||||
TRASH: '/settings/trash',
|
TRASH: '/settings/trash',
|
||||||
},
|
},
|
||||||
|
NOTES: '/notes/',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImagePresets = {
|
export const ImagePresets = {
|
||||||
|
|
|
@ -5,12 +5,16 @@ import Masonry from 'react-masonry-css';
|
||||||
import { Card } from '~/components/containers/Card';
|
import { Card } from '~/components/containers/Card';
|
||||||
import { Filler } from '~/components/containers/Filler';
|
import { Filler } from '~/components/containers/Filler';
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from '~/components/containers/Group';
|
||||||
|
import { Markdown } from '~/components/containers/Markdown';
|
||||||
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 { Icon } from '~/components/input/Icon';
|
||||||
import { InputText } from '~/components/input/InputText';
|
import { InputText } from '~/components/input/InputText';
|
||||||
import { Textarea } from '~/components/input/Textarea';
|
import { Textarea } from '~/components/input/Textarea';
|
||||||
import { HorizontalMenu } from '~/components/menu/HorizontalMenu';
|
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';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
@ -21,14 +25,9 @@ const breakpointCols = {
|
||||||
1280: 1,
|
1280: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sampleNotes = [...new Array(40)].map((_, i) => (
|
|
||||||
<Card key={i} style={{ height: Math.random() * 400 + 50 }}>
|
|
||||||
{i}
|
|
||||||
</Card>
|
|
||||||
));
|
|
||||||
|
|
||||||
const SettingsNotes: VFC<SettingsNotesProps> = () => {
|
const SettingsNotes: VFC<SettingsNotesProps> = () => {
|
||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
|
const { notes } = useGetNotes('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -61,7 +60,9 @@ const SettingsNotes: VFC<SettingsNotesProps> = () => {
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{sampleNotes}
|
{notes.map(note => (
|
||||||
|
<NoteCard key={note.id} content={note.content} createdAt={note.created_at} />
|
||||||
|
))}
|
||||||
</Masonry>
|
</Masonry>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
58
src/hooks/notes/useGetNotes.ts
Normal file
58
src/hooks/notes/useGetNotes.ts
Normal file
|
@ -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 };
|
||||||
|
};
|
5
src/types/notes/index.ts
Normal file
5
src/types/notes/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export interface Note {
|
||||||
|
id: number;
|
||||||
|
content: string;
|
||||||
|
created_at: string;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue