mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
added note dropping and editing
This commit is contained in:
parent
cedf0adcfa
commit
1bb08f72e6
11 changed files with 224 additions and 101 deletions
|
@ -1,20 +1,31 @@
|
||||||
import {
|
import {
|
||||||
ApiGetNotesRequest,
|
ApiGetNotesRequest as ApiListNotesRequest,
|
||||||
ApiGetNotesResponse,
|
ApiGetNotesResponse,
|
||||||
ApiPostNoteRequest,
|
ApiCreateNoteRequest,
|
||||||
ApiPostNoteResponse,
|
ApiUpdateNoteResponse,
|
||||||
|
ApiUpdateNoteRequest,
|
||||||
} from "~/api/notes/types";
|
} from "~/api/notes/types";
|
||||||
import { URLS } from "~/constants/urls";
|
import { URLS } from "~/constants/urls";
|
||||||
import { api, cleanResult } from "~/utils/api";
|
import { api, cleanResult } from "~/utils/api";
|
||||||
|
|
||||||
export const apiGetNotes = ({ limit, offset, search }: ApiGetNotesRequest) =>
|
export const apiListNotes = ({ limit, offset, search }: ApiListNotesRequest) =>
|
||||||
api
|
api
|
||||||
.get<ApiGetNotesResponse>(URLS.NOTES, { params: { limit, offset, search } })
|
.get<ApiGetNotesResponse>(URLS.NOTES, { params: { limit, offset, search } })
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
export const apiPostNote = ({ text }: ApiPostNoteRequest) =>
|
export const apiCreateNote = ({ text }: ApiCreateNoteRequest) =>
|
||||||
api
|
api
|
||||||
.post<ApiPostNoteResponse>(URLS.NOTES, {
|
.post<ApiUpdateNoteResponse>(URLS.NOTES, {
|
||||||
text,
|
text,
|
||||||
})
|
})
|
||||||
.then(cleanResult);
|
.then(cleanResult);
|
||||||
|
|
||||||
|
export const apiDeleteNote = (id: number) =>
|
||||||
|
api.delete(URLS.NOTE(id)).then(cleanResult);
|
||||||
|
|
||||||
|
export const apiUpdateNote = ({ id, text }: ApiUpdateNoteRequest) =>
|
||||||
|
api
|
||||||
|
.put<ApiUpdateNoteResponse>(URLS.NOTE(id), {
|
||||||
|
content: text,
|
||||||
|
})
|
||||||
|
.then(cleanResult);
|
||||||
|
|
|
@ -11,8 +11,13 @@ export interface ApiGetNotesResponse {
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiPostNoteRequest {
|
export interface ApiCreateNoteRequest {
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiPostNoteResponse extends Note {}
|
export interface ApiUpdateNoteResponse extends Note {}
|
||||||
|
|
||||||
|
export interface ApiUpdateNoteRequest {
|
||||||
|
id: number;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { VFC } from "react";
|
import React, { useCallback, useState, VFC } from "react";
|
||||||
|
|
||||||
import { Card } from "~/components/containers/Card";
|
import { Card } from "~/components/containers/Card";
|
||||||
import { Markdown } from "~/components/containers/Markdown";
|
import { Markdown } from "~/components/containers/Markdown";
|
||||||
|
@ -6,25 +6,59 @@ import { Padder } from "~/components/containers/Padder";
|
||||||
import { NoteMenu } from "~/components/notes/NoteMenu";
|
import { NoteMenu } from "~/components/notes/NoteMenu";
|
||||||
import { formatText, getPrettyDate } from "~/utils/dom";
|
import { formatText, getPrettyDate } from "~/utils/dom";
|
||||||
|
|
||||||
|
import { NoteCreationForm } from "../NoteCreationForm";
|
||||||
|
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
interface NoteCardProps {
|
interface NoteCardProps {
|
||||||
content: string;
|
content: string;
|
||||||
|
remove: () => Promise<void>;
|
||||||
|
update: (text: string, callback?: () => void) => Promise<void>;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NoteCard: VFC<NoteCardProps> = ({ content, createdAt }) => (
|
const NoteCard: VFC<NoteCardProps> = ({
|
||||||
<Card className={styles.note}>
|
content,
|
||||||
<Padder>
|
createdAt,
|
||||||
<NoteMenu onEdit={console.log} onDelete={console.log} />
|
remove,
|
||||||
<Markdown
|
update,
|
||||||
className={styles.wrap}
|
}) => {
|
||||||
dangerouslySetInnerHTML={{ __html: formatText(content) }}
|
const [editing, setEditing] = useState(false);
|
||||||
/>
|
|
||||||
</Padder>
|
|
||||||
|
|
||||||
<Padder className={styles.footer}>{getPrettyDate(createdAt)}</Padder>
|
const toggleEditing = useCallback(() => setEditing(v => !v), []);
|
||||||
</Card>
|
const onUpdate = useCallback(
|
||||||
);
|
(text: string, callback?: () => void) =>
|
||||||
|
update(text, () => {
|
||||||
|
setEditing(false);
|
||||||
|
callback?.();
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={styles.note}>
|
||||||
|
{editing ? (
|
||||||
|
<NoteCreationForm
|
||||||
|
text={content}
|
||||||
|
onSubmit={onUpdate}
|
||||||
|
onCancel={toggleEditing}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Padder>
|
||||||
|
<NoteMenu onEdit={toggleEditing} onDelete={remove} />
|
||||||
|
|
||||||
|
<Markdown
|
||||||
|
className={styles.wrap}
|
||||||
|
dangerouslySetInnerHTML={{ __html: formatText(content) }}
|
||||||
|
/>
|
||||||
|
</Padder>
|
||||||
|
|
||||||
|
<Padder className={styles.footer}>{getPrettyDate(createdAt)}</Padder>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export { NoteCard };
|
export { NoteCard };
|
||||||
|
|
|
@ -16,7 +16,8 @@ import { showErrorToast } from "~/utils/errors/showToast";
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
interface NoteCreationFormProps {
|
interface NoteCreationFormProps {
|
||||||
onSubmit: (text: string, callback: (note: Note) => void) => void;
|
text?: string;
|
||||||
|
onSubmit: (text: string, callback: () => void) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,15 +28,16 @@ const validationSchema = object({
|
||||||
type Values = Asserts<typeof validationSchema>;
|
type Values = Asserts<typeof validationSchema>;
|
||||||
|
|
||||||
const NoteCreationForm: FC<NoteCreationFormProps> = ({
|
const NoteCreationForm: FC<NoteCreationFormProps> = ({
|
||||||
|
text = "",
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
}) => {
|
}) => {
|
||||||
const placeholder = useRandomPhrase("SIMPLE");
|
const placeholder = useRandomPhrase("SIMPLE");
|
||||||
|
|
||||||
const submit = useCallback<FormikConfig<Values>["onSubmit"]>(
|
const submit = useCallback<FormikConfig<Values>["onSubmit"]>(
|
||||||
async ({ text }, { resetForm, setSubmitting, setErrors }) => {
|
async (values, { resetForm, setSubmitting, setErrors }) => {
|
||||||
try {
|
try {
|
||||||
await onSubmit(text, () => resetForm());
|
await onSubmit(values.text, () => resetForm());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = getErrorMessage(error);
|
const message = getErrorMessage(error);
|
||||||
if (message) {
|
if (message) {
|
||||||
|
@ -58,8 +60,9 @@ const NoteCreationForm: FC<NoteCreationFormProps> = ({
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
touched,
|
touched,
|
||||||
handleBlur,
|
handleBlur,
|
||||||
|
isSubmitting,
|
||||||
} = useFormik<Values>({
|
} = useFormik<Values>({
|
||||||
initialValues: { text: "" },
|
initialValues: { text },
|
||||||
validationSchema,
|
validationSchema,
|
||||||
onSubmit: submit,
|
onSubmit: submit,
|
||||||
});
|
});
|
||||||
|
@ -85,7 +88,7 @@ const NoteCreationForm: FC<NoteCreationFormProps> = ({
|
||||||
Отмена
|
Отмена
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button size="mini" type="submit" color="gray">
|
<Button size="mini" type="submit" color="gray" loading={isSubmitting}>
|
||||||
ОК
|
ОК
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
|
@ -1,48 +1,49 @@
|
||||||
import { FlowDisplayVariant, INode } from '~/types';
|
import { FlowDisplayVariant, INode } from "~/types";
|
||||||
|
|
||||||
export const URLS = {
|
export const URLS = {
|
||||||
BASE: '/',
|
BASE: "/",
|
||||||
LAB: '/lab',
|
LAB: "/lab",
|
||||||
BORIS: '/boris',
|
BORIS: "/boris",
|
||||||
AUTH: {
|
AUTH: {
|
||||||
LOGIN: '/auth/login',
|
LOGIN: "/auth/login",
|
||||||
},
|
},
|
||||||
EXAMPLES: {
|
EXAMPLES: {
|
||||||
EDITOR: '/examples/edit',
|
EDITOR: "/examples/edit",
|
||||||
IMAGE: '/examples/image',
|
IMAGE: "/examples/image",
|
||||||
},
|
},
|
||||||
ERRORS: {
|
ERRORS: {
|
||||||
NOT_FOUND: '/lost',
|
NOT_FOUND: "/lost",
|
||||||
BACKEND_DOWN: '/oopsie',
|
BACKEND_DOWN: "/oopsie",
|
||||||
},
|
},
|
||||||
NODE_URL: (id: INode['id'] | string) => `/post${id}`,
|
NODE_URL: (id: INode["id"] | string) => `/post${id}`,
|
||||||
PROFILE_PAGE: (username: string) => `/profile/${username}`,
|
PROFILE_PAGE: (username: string) => `/profile/${username}`,
|
||||||
SETTINGS: {
|
SETTINGS: {
|
||||||
BASE: '/settings',
|
BASE: "/settings",
|
||||||
NOTES: '/settings/notes',
|
NOTES: "/settings/notes",
|
||||||
TRASH: '/settings/trash',
|
TRASH: "/settings/trash",
|
||||||
},
|
},
|
||||||
NOTES: '/notes/',
|
NOTES: "/notes/",
|
||||||
|
NOTE: (id: number) => `/notes/${id}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImagePresets = {
|
export const ImagePresets = {
|
||||||
'1600': '1600',
|
"1600": "1600",
|
||||||
'600': '600',
|
"600": "600",
|
||||||
'300': '300',
|
"300": "300",
|
||||||
cover: 'cover',
|
cover: "cover",
|
||||||
small_hero: 'small_hero',
|
small_hero: "small_hero",
|
||||||
avatar: 'avatar',
|
avatar: "avatar",
|
||||||
flow_square: 'flow_square',
|
flow_square: "flow_square",
|
||||||
flow_vertical: 'flow_vertical',
|
flow_vertical: "flow_vertical",
|
||||||
flow_horizontal: 'flow_horizontal',
|
flow_horizontal: "flow_horizontal",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const flowDisplayToPreset: Record<
|
export const flowDisplayToPreset: Record<
|
||||||
FlowDisplayVariant,
|
FlowDisplayVariant,
|
||||||
typeof ImagePresets[keyof typeof ImagePresets]
|
typeof ImagePresets[keyof typeof ImagePresets]
|
||||||
> = {
|
> = {
|
||||||
single: 'flow_square',
|
single: "flow_square",
|
||||||
quadro: 'flow_square',
|
quadro: "flow_square",
|
||||||
vertical: 'flow_vertical',
|
vertical: "flow_vertical",
|
||||||
horizontal: 'flow_horizontal',
|
horizontal: "flow_horizontal",
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { FC, useState, VFC } from "react";
|
import { FC, useCallback, useState, VFC } from "react";
|
||||||
|
|
||||||
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 { Button } from "~/components/input/Button";
|
import { Button } from "~/components/input/Button";
|
||||||
import { NoteCard } from "~/components/notes/NoteCard";
|
import { NoteCard } from "~/components/notes/NoteCard";
|
||||||
import { NoteCreationForm } from "~/components/notes/NoteCreationForm";
|
import { NoteCreationForm } from "~/components/notes/NoteCreationForm";
|
||||||
|
import { useConfirmation } from "~/hooks/dom/useConfirmation";
|
||||||
import { NoteProvider, useNotesContext } from "~/utils/providers/NoteProvider";
|
import { NoteProvider, useNotesContext } from "~/utils/providers/NoteProvider";
|
||||||
|
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
|
@ -12,12 +13,22 @@ import styles from "./styles.module.scss";
|
||||||
interface SettingsNotesProps {}
|
interface SettingsNotesProps {}
|
||||||
|
|
||||||
const List = () => {
|
const List = () => {
|
||||||
const { notes } = useNotesContext();
|
const { notes, remove, update } = useNotesContext();
|
||||||
|
const confirm = useConfirmation();
|
||||||
|
|
||||||
|
const onRemove = useCallback(
|
||||||
|
async (id: number) => {
|
||||||
|
confirm("Удалить? Это удалит заметку навсегда", () => remove(id));
|
||||||
|
},
|
||||||
|
[remove],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{notes.map(note => (
|
{notes.map(note => (
|
||||||
<NoteCard
|
<NoteCard
|
||||||
|
remove={() => onRemove(note.id)}
|
||||||
|
update={(text, callback) => update(note.id, text, callback)}
|
||||||
key={note.id}
|
key={note.id}
|
||||||
content={note.content}
|
content={note.content}
|
||||||
createdAt={note.created_at}
|
createdAt={note.created_at}
|
||||||
|
@ -28,7 +39,7 @@ const List = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Form: FC<{ onCancel: () => void }> = ({ onCancel }) => {
|
const Form: FC<{ onCancel: () => void }> = ({ onCancel }) => {
|
||||||
const { submit } = useNotesContext();
|
const { create: submit } = useNotesContext();
|
||||||
return <NoteCreationForm onSubmit={submit} onCancel={onCancel} />;
|
return <NoteCreationForm onSubmit={submit} onCancel={onCancel} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
import React, { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
FC,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import { TagAutocomplete } from '~/components/tags/TagAutocomplete';
|
import { TagAutocomplete } from "~/components/tags/TagAutocomplete";
|
||||||
import { TagWrapper } from '~/components/tags/TagWrapper';
|
import { TagWrapper } from "~/components/tags/TagWrapper";
|
||||||
import { useTagAutocomplete } from '~/hooks/tag/useTagAutocomplete';
|
import { useTagAutocomplete } from "~/hooks/tag/useTagAutocomplete";
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
const placeholder = 'Добавить';
|
const placeholder = "Добавить";
|
||||||
|
|
||||||
const prepareInput = (input: string): string[] => {
|
const prepareInput = (input: string): string[] => {
|
||||||
return input
|
return input
|
||||||
.split(',')
|
.split(",")
|
||||||
.map((title: string) =>
|
.map((title: string) =>
|
||||||
title
|
title
|
||||||
.trim()
|
.trim()
|
||||||
.substring(0, 64)
|
.substring(0, 64)
|
||||||
.toLowerCase()
|
.toLowerCase(),
|
||||||
)
|
)
|
||||||
.filter(el => el.length > 0);
|
.filter(el => el.length > 0);
|
||||||
};
|
};
|
||||||
|
@ -29,7 +37,7 @@ interface IProps {
|
||||||
|
|
||||||
const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
||||||
const [focused, setFocused] = useState(false);
|
const [focused, setFocused] = useState(false);
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState("");
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
const wrapper = useRef<HTMLDivElement>(null);
|
const wrapper = useRef<HTMLDivElement>(null);
|
||||||
const options = useTagAutocomplete(input, exclude);
|
const options = useTagAutocomplete(input, exclude);
|
||||||
|
@ -37,7 +45,7 @@ const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
||||||
const onInput = useCallback(
|
const onInput = useCallback(
|
||||||
({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
|
({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
setInput(value || '');
|
setInput(value || "");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,36 +55,35 @@ const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
||||||
onAppend(items.slice(0, items.length - 1));
|
onAppend(items.slice(0, items.length - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
setInput(items[items.length - 1] || '');
|
setInput(items[items.length - 1] || "");
|
||||||
},
|
},
|
||||||
[onAppend]
|
[onAppend],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onKeyDown = useCallback(
|
const onKeyDown = useCallback(
|
||||||
({ key }) => {
|
({ key }) => {
|
||||||
if (key === 'Escape' && ref.current) {
|
if (key === "Escape" && ref.current) {
|
||||||
setInput('');
|
setInput("");
|
||||||
ref.current.blur();
|
ref.current.blur();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'Backspace' && input === '') {
|
if (key === "Backspace" && input === "") {
|
||||||
setInput(onClearTag() || '');
|
setInput(onClearTag() || "");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === ',' || key === 'Comma') {
|
if (key === "," || key === "Comma") {
|
||||||
const created = prepareInput(input);
|
const created = prepareInput(input);
|
||||||
|
|
||||||
if (created.length) {
|
if (created.length) {
|
||||||
console.log('appending?!!')
|
|
||||||
onAppend(created);
|
onAppend(created);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInput('');
|
setInput("");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[input, setInput, onClearTag, onAppend]
|
[input, setInput, onClearTag, onAppend],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onFocus = useCallback(() => setFocused(true), []);
|
const onFocus = useCallback(() => setFocused(true), []);
|
||||||
|
@ -94,39 +101,45 @@ const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
|
|
||||||
if (input.trim()) {
|
if (input.trim()) {
|
||||||
setInput('');
|
setInput("");
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit([]);
|
onSubmit([]);
|
||||||
},
|
},
|
||||||
[input, setInput, onSubmit]
|
[input, setInput, onSubmit],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onAutocompleteSelect = useCallback(
|
const onAutocompleteSelect = useCallback(
|
||||||
(val: string) => {
|
(val: string) => {
|
||||||
setInput('');
|
setInput("");
|
||||||
|
|
||||||
if (!val.trim()) {
|
if (!val.trim()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onAppend([val]);
|
onAppend([val]);
|
||||||
},
|
},
|
||||||
[onAppend, setInput]
|
[onAppend, setInput],
|
||||||
);
|
);
|
||||||
|
|
||||||
const feature = useMemo(() => (input?.substr(0, 1) === '/' ? 'green' : ''), [input]);
|
const feature = useMemo(() => (input?.substr(0, 1) === "/" ? "green" : ""), [
|
||||||
|
input,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!focused) return;
|
if (!focused) return;
|
||||||
|
|
||||||
document.addEventListener('click', onBlur);
|
document.addEventListener("click", onBlur);
|
||||||
return () => document.removeEventListener('click', onBlur);
|
return () => document.removeEventListener("click", onBlur);
|
||||||
}, [onBlur, focused]);
|
}, [onBlur, focused]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrap} ref={wrapper}>
|
<div className={styles.wrap} ref={wrapper}>
|
||||||
<TagWrapper title={input || placeholder} hasInput={true} feature={feature}>
|
<TagWrapper
|
||||||
|
title={input || placeholder}
|
||||||
|
hasInput={true}
|
||||||
|
feature={feature}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={input}
|
value={input}
|
||||||
|
|
11
src/hooks/dom/useConfirmation.ts
Normal file
11
src/hooks/dom/useConfirmation.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
export const useConfirmation = () =>
|
||||||
|
useCallback((prompt = "", onApprove: () => {}, onReject?: () => {}) => {
|
||||||
|
if (!window.confirm(prompt || "Уверен?")) {
|
||||||
|
onReject?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onApprove();
|
||||||
|
}, []);
|
|
@ -1,14 +1,17 @@
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
import useSWRInfinite from "swr/infinite";
|
import useSWRInfinite, { SWRInfiniteKeyLoader } from "swr/infinite";
|
||||||
import { SWRInfiniteKeyLoader } from "swr/infinite";
|
|
||||||
|
|
||||||
import { apiGetNotes, apiPostNote } from "~/api/notes";
|
import {
|
||||||
|
apiCreateNote,
|
||||||
|
apiDeleteNote,
|
||||||
|
apiListNotes,
|
||||||
|
apiUpdateNote,
|
||||||
|
} from "~/api/notes";
|
||||||
import { ApiGetNotesRequest } from "~/api/notes/types";
|
import { ApiGetNotesRequest } from "~/api/notes/types";
|
||||||
import { useAuth } from "~/hooks/auth/useAuth";
|
import { useAuth } from "~/hooks/auth/useAuth";
|
||||||
import { GetLabNodesRequest, ILabNode } from "~/types/lab";
|
import { GetLabNodesRequest, ILabNode } from "~/types/lab";
|
||||||
import { Note } from "~/types/notes";
|
import { Note } from "~/types/notes";
|
||||||
import { showErrorToast } from "~/utils/errors/showToast";
|
|
||||||
import { flatten, uniqBy } from "~/utils/ramda";
|
import { flatten, uniqBy } from "~/utils/ramda";
|
||||||
|
|
||||||
const DEFAULT_COUNT = 20;
|
const DEFAULT_COUNT = 20;
|
||||||
|
@ -43,7 +46,7 @@ export const useNotes = (search: string) => {
|
||||||
const { data, isValidating, size, setSize, mutate } = useSWRInfinite(
|
const { data, isValidating, size, setSize, mutate } = useSWRInfinite(
|
||||||
getKey(isUser, search),
|
getKey(isUser, search),
|
||||||
async (key: string) => {
|
async (key: string) => {
|
||||||
const result = await apiGetNotes(parseKey(key));
|
const result = await apiListNotes(parseKey(key));
|
||||||
return result.list;
|
return result.list;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -51,17 +54,44 @@ export const useNotes = (search: string) => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const submit = useCallback(
|
const create = useCallback(
|
||||||
async (text: string, onSuccess: (note: Note) => void) => {
|
async (text: string, onSuccess?: (note: Note) => void) => {
|
||||||
const result = await apiPostNote({ text });
|
const result = await apiCreateNote({ text });
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
mutate(data?.map((it, index) => (index === 0 ? [result, ...it] : it)));
|
await mutate(
|
||||||
|
data?.map((it, index) => (index === 0 ? [result, ...it] : it)),
|
||||||
|
{ revalidate: false },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccess(result);
|
onSuccess?.(result);
|
||||||
},
|
},
|
||||||
[],
|
[mutate, data],
|
||||||
|
);
|
||||||
|
|
||||||
|
const remove = useCallback(
|
||||||
|
async (id: number, onSuccess?: () => void) => {
|
||||||
|
await apiDeleteNote(id);
|
||||||
|
await mutate(
|
||||||
|
data?.map(page => page.filter(it => it.id !== id)),
|
||||||
|
{ revalidate: false },
|
||||||
|
);
|
||||||
|
onSuccess?.();
|
||||||
|
},
|
||||||
|
[mutate, data],
|
||||||
|
);
|
||||||
|
|
||||||
|
const update = useCallback(
|
||||||
|
async (id: number, text: string, onSuccess?: () => void) => {
|
||||||
|
const result = await apiUpdateNote({ id, text });
|
||||||
|
await mutate(
|
||||||
|
data?.map(page => page.map(it => (it.id === id ? result : it))),
|
||||||
|
{ revalidate: false },
|
||||||
|
);
|
||||||
|
onSuccess?.();
|
||||||
|
},
|
||||||
|
[mutate, data],
|
||||||
);
|
);
|
||||||
|
|
||||||
const notes = useMemo(() => uniqBy(n => n.id, flatten(data || [])), [data]);
|
const notes = useMemo(() => uniqBy(n => n.id, flatten(data || [])), [data]);
|
||||||
|
@ -74,8 +104,10 @@ export const useNotes = (search: string) => {
|
||||||
hasMore,
|
hasMore,
|
||||||
loadMore,
|
loadMore,
|
||||||
isLoading: !data && isValidating,
|
isLoading: !data && isValidating,
|
||||||
submit,
|
create,
|
||||||
|
remove,
|
||||||
|
update,
|
||||||
}),
|
}),
|
||||||
[notes, hasMore, loadMore, data, isValidating, submit],
|
[notes, hasMore, loadMore, data, isValidating, create, remove],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,9 @@ const NoteContext = createContext<ReturnType<typeof useNotes>>({
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
loadMore: async () => Promise.resolve(undefined),
|
loadMore: async () => Promise.resolve(undefined),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
submit: () => Promise.resolve(),
|
create: () => Promise.resolve(),
|
||||||
|
remove: () => Promise.resolve(),
|
||||||
|
update: (id: number, text: string) => Promise.resolve(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NoteProvider: FC = ({ children }) => {
|
export const NoteProvider: FC = ({ children }) => {
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue