1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

add comments for guests

This commit is contained in:
Fedor Katurov 2023-10-30 15:16:19 +06:00
parent cbf7b1f616
commit 8abf6177b5
16 changed files with 278 additions and 194 deletions

View file

@ -21,7 +21,7 @@ type IProps = HTMLAttributes<HTMLDivElement> & {
isSame?: boolean;
canEdit?: boolean;
highlighted?: boolean;
saveComment: (data: IComment) => Promise<unknown>;
saveComment: (data: IComment) => Promise<IComment | undefined>;
onDelete: (id: IComment['id'], isLocked: boolean) => void;
onShowImageModal: (images: IFile[], index: number) => void;
};

View file

@ -7,11 +7,15 @@ import { IUser } from '~/types/auth';
import { path } from '~/utils/ramda';
interface Props {
user: IUser;
user?: IUser;
className?: string;
}
const CommentAvatar: FC<Props> = ({ user, className }) => {
if (!user) {
return <Avatar className={className} />;
}
return (
<MenuButton
position="auto"

View file

@ -22,6 +22,7 @@ import { IComment, IFile } from '~/types';
import { formatCommentText, getPrettyDate, getURL } from '~/utils/dom';
import { append, assocPath, path, reduce } from '~/utils/ramda';
import { CommentEditingForm } from '../CommentEditingForm';
import { CommentImageGrid } from '../CommentImageGrid';
import { CommentMenu } from '../CommentMenu';
@ -32,7 +33,7 @@ interface IProps {
nodeId: number;
comment: IComment;
canEdit: boolean;
saveComment: (data: IComment) => Promise<unknown>;
saveComment: (data: IComment) => Promise<IComment | undefined>;
onDelete: (id: IComment['id'], isLocked: boolean) => void;
onShowImageModal: (images: IFile[], index: number) => void;
}
@ -98,7 +99,7 @@ const CommentContent: FC<IProps> = memo(
if (isEditing) {
return (
<CommentForm
<CommentEditingForm
saveComment={saveComment}
nodeId={nodeId}
comment={comment}

View file

@ -0,0 +1,38 @@
import { FC } from 'react';
import { CommentForm } from '~/components/comment/CommentForm';
import { UploadDropzone } from '~/components/upload/UploadDropzone';
import { UploadSubject, UploadTarget } from '~/constants/uploads';
import { useUploader } from '~/hooks/data/useUploader';
import { IComment, INode } from '~/types';
import { UploaderContextProvider } from '~/utils/context/UploaderContextProvider';
interface CommentEditingFormProps {
comment: IComment;
nodeId: INode['id'];
saveComment: (data: IComment) => Promise<IComment | undefined>;
onCancelEdit?: () => void;
}
const CommentEditingForm: FC<CommentEditingFormProps> = ({
saveComment,
comment,
onCancelEdit,
}) => {
const uploader = useUploader(UploadSubject.Comment, UploadTarget.Comments);
return (
<UploadDropzone onUpload={uploader.uploadFiles}>
<UploaderContextProvider value={uploader}>
<CommentForm
saveComment={saveComment}
comment={comment}
onCancelEdit={onCancelEdit}
allowUploads
/>
</UploaderContextProvider>
</UploadDropzone>
);
};
export { CommentEditingForm };

View file

@ -1,4 +1,4 @@
import React, { FC, useCallback, useState } from 'react';
import { FC, useCallback, useMemo, useState } from 'react';
import { FormikProvider } from 'formik';
import { observer } from 'mobx-react-lite';
@ -12,108 +12,116 @@ import { Button } from '~/components/input/Button';
import { UploadDropzone } from '~/components/upload/UploadDropzone';
import { ERROR_LITERAL } from '~/constants/errors';
import { EMPTY_COMMENT } from '~/constants/node';
import { UploadSubject, UploadTarget } from '~/constants/uploads';
import { useCommentFormFormik } from '~/hooks/comments/useCommentFormFormik';
import { useUploader } from '~/hooks/data/useUploader';
import { useInputPasteUpload } from '~/hooks/dom/useInputPasteUpload';
import { IComment, INode } from '~/types';
import { UploaderContextProvider } from '~/utils/context/UploaderContextProvider';
import {
UploaderContextProvider,
useUploaderContext,
} from '~/utils/context/UploaderContextProvider';
import styles from './styles.module.scss';
interface IProps {
comment?: IComment;
nodeId: INode['id'];
saveComment: (data: IComment) => Promise<unknown>;
allowUploads?: boolean;
saveComment: (data: IComment) => Promise<IComment | undefined>;
onCancelEdit?: () => void;
}
const CommentForm: FC<IProps> = observer(({ comment, nodeId, saveComment, onCancelEdit }) => {
const [textarea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
const uploader = useUploader(UploadSubject.Comment, UploadTarget.Comments, comment?.files);
const formik = useCommentFormFormik(
comment || EMPTY_COMMENT,
nodeId,
uploader,
saveComment,
onCancelEdit
);
const isLoading = formik.isSubmitting || uploader.isUploading;
const isEditing = !!comment?.id;
const CommentForm: FC<IProps> = observer(
({ comment, allowUploads, saveComment, onCancelEdit }) => {
const [textarea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
const uploader = useUploaderContext();
const clearError = useCallback(() => {
if (formik.status) {
formik.setStatus('');
}
const formik = useCommentFormFormik(
comment || EMPTY_COMMENT,
uploader.files,
uploader.setFiles,
saveComment,
onCancelEdit,
);
const isLoading = formik.isSubmitting || uploader.isUploading;
const isEditing = !!comment?.id;
if (formik.errors.text) {
formik.setErrors({
...formik.errors,
text: '',
});
}
}, [formik]);
const clearError = useCallback(() => {
if (formik.status) {
formik.setStatus('');
}
const error = formik.status || formik.errors.text;
const onPaste = useInputPasteUpload(uploader.uploadFiles);
if (formik.errors.text) {
formik.setErrors({
...formik.errors,
text: '',
});
}
}, [formik]);
return (
<UploadDropzone onUpload={uploader.uploadFiles}>
const error = formik.status || formik.errors.text;
const onPaste = useInputPasteUpload(uploader.uploadFiles);
return (
<form onSubmit={formik.handleSubmit} className={styles.wrap}>
<FormikProvider value={formik}>
<UploaderContextProvider value={uploader}>
<div className={styles.input}>
<LocalCommentFormTextarea onPaste={onPaste} ref={setTextArea} />
<div className={styles.input}>
<LocalCommentFormTextarea onPaste={onPaste} ref={setTextArea} />
{!!error && (
<div className={styles.error} onClick={clearError}>
{ERROR_LITERAL[error] || error}
</div>
)}
</div>
{!!error && (
<div className={styles.error} onClick={clearError}>
{ERROR_LITERAL[error] || error}
</div>
)}
</div>
<CommentFormAttaches />
{allowUploads && <CommentFormAttaches />}
<div className={styles.buttons}>
<div className={styles.buttons}>
{allowUploads && (
<div className={styles.button_column}>
<CommentFormAttachButtons onUpload={uploader.uploadFiles} />
</div>
)}
<div className={styles.button_column}>
{!!textarea && (
<CommentFormFormatButtons
element={textarea}
handler={formik.handleChange('text')}
/>
)}
</div>
<Filler />
<div className={styles.button_column}>
{isEditing && (
<Button size="small" color="link" type="button" onClick={onCancelEdit}>
Отмена
</Button>
)}
<Button
type="submit"
size="small"
color="gray"
iconRight={!isEditing ? 'enter' : 'check'}
disabled={isLoading}
loading={isLoading}
>
{!isEditing ? 'Сказать' : 'Сохранить'}
</Button>
</div>
<div className={styles.button_column}>
{!!textarea && (
<CommentFormFormatButtons
element={textarea}
handler={formik.handleChange('text')}
/>
)}
</div>
</UploaderContextProvider>
<Filler />
<div className={styles.button_column}>
{isEditing && (
<Button
size="small"
color="link"
type="button"
onClick={onCancelEdit}
>
Отмена
</Button>
)}
<Button
type="submit"
size="small"
color="gray"
iconRight={!isEditing ? 'enter' : 'check'}
disabled={isLoading}
loading={isLoading}
>
{!isEditing ? 'Сказать' : 'Сохранить'}
</Button>
</div>
</div>
</FormikProvider>
</form>
</UploadDropzone>
);
});
);
},
);
export { CommentForm };

View file

@ -10,7 +10,7 @@ import { DivProps } from '~/utils/types';
import styles from './styles.module.scss';
type IProps = DivProps & {
user: IUser;
user?: IUser;
isEmpty?: boolean;
isLoading?: boolean;
isForm?: boolean;
@ -36,7 +36,10 @@ const CommentWrapper: FC<IProps> = ({
{...props}
>
<div className={styles.thumb}>
<CommentAvatar user={user} className={styles.thumb_image} />
<CommentAvatar
user={user}
className={classNames(styles.thumb_image, { [styles.pointer]: user })}
/>
<div className={styles.thumb_user}>~{path(['username'], user)}</div>
</div>

View file

@ -95,7 +95,7 @@ div.thumb_image {
background-size: cover;
flex: 0 0 $comment_height;
will-change: transform;
cursor: pointer;
cursor: default;
@include tablet {
height: 32px;
@ -105,6 +105,10 @@ div.thumb_image {
}
}
.pointer {
cursor: pointer;
}
.thumb_user {
display: none;
flex: 1;

View file

@ -1,22 +0,0 @@
import React, { FC } from 'react';
import { CommentForm } from '~/components/comment/CommentForm';
import { CommentWrapper } from '~/components/containers/CommentWrapper';
import { IComment } from '~/types';
import { IUser } from '~/types/auth';
export interface NodeCommentFormProps {
user: IUser;
nodeId?: number;
saveComment: (comment: IComment) => Promise<unknown>;
}
const NodeCommentForm: FC<NodeCommentFormProps> = ({ user, nodeId, saveComment }) => {
return (
<CommentWrapper user={user} isForm>
<CommentForm nodeId={nodeId} saveComment={saveComment} />
</CommentWrapper>
);
};
export { NodeCommentForm };

View file

@ -1,8 +0,0 @@
import dynamic from 'next/dynamic';
import type { NodeCommentFormProps } from './index';
export const NodeCommentFormSSR = dynamic<NodeCommentFormProps>(
() => import('./index').then(it => it.NodeCommentForm),
{ ssr: false }
);