diff --git a/src/components/comment/Comment/index.tsx b/src/components/comment/Comment/index.tsx index b7a8b22f..6ee92c31 100644 --- a/src/components/comment/Comment/index.tsx +++ b/src/components/comment/Comment/index.tsx @@ -1,10 +1,8 @@ import React, { FC, HTMLAttributes, memo } from 'react'; import { CommentWrapper } from '~/components/containers/CommentWrapper'; -import { ICommentGroup } from '~/redux/types'; +import { IComment, ICommentGroup } from '~/redux/types'; import { CommentContent } from '~/components/comment/CommentContent'; import styles from './styles.module.scss'; -import { nodeEditComment, nodeLockComment } from '~/redux/node/actions'; -import { INodeState } from '~/redux/node/reducer'; import { CommendDeleted } from '../../node/CommendDeleted'; import * as MODAL_ACTIONS from '~/redux/modal/actions'; @@ -12,25 +10,21 @@ type IProps = HTMLAttributes & { is_empty?: boolean; is_loading?: boolean; comment_group: ICommentGroup; - comment_data: INodeState['comment_data']; is_same?: boolean; can_edit?: boolean; - onDelete: typeof nodeLockComment; - onEdit: typeof nodeEditComment; + onDelete: (id: IComment['id'], isLocked: boolean) => void; modalShowPhotoswipe: typeof MODAL_ACTIONS.modalShowPhotoswipe; }; const Comment: FC = memo( ({ comment_group, - comment_data, is_empty, is_same, is_loading, className, can_edit, onDelete, - onEdit, modalShowPhotoswipe, ...props }) => { diff --git a/src/components/comment/CommentContent/index.tsx b/src/components/comment/CommentContent/index.tsx index b79f67d7..b215379e 100644 --- a/src/components/comment/CommentContent/index.tsx +++ b/src/components/comment/CommentContent/index.tsx @@ -10,17 +10,16 @@ import { AudioPlayer } from '~/components/media/AudioPlayer'; import classnames from 'classnames'; import { PRESETS } from '~/constants/urls'; import { COMMENT_BLOCK_RENDERERS } from '~/constants/comment'; -import { nodeLockComment } from '~/redux/node/actions'; import { CommentMenu } from '../CommentMenu'; import * as MODAL_ACTIONS from '~/redux/modal/actions'; -import { LocalCommentForm } from '~/components/comment/LocalCommentForm'; +import { CommentForm } from '~/components/comment/CommentForm'; import { useShallowSelect } from '~/utils/hooks/useShallowSelect'; import { selectNode } from '~/redux/node/selectors'; interface IProps { comment: IComment; can_edit: boolean; - onDelete: typeof nodeLockComment; + onDelete: (id: IComment['id'], isLocked: boolean) => void; modalShowPhotoswipe: typeof MODAL_ACTIONS.modalShowPhotoswipe; } @@ -59,7 +58,7 @@ const CommentContent: FC = memo(({ comment, can_edit, onDelete, modalSho ); if (isEditing) { - return ; + return ; } return ( diff --git a/src/components/comment/CommentForm/index.tsx b/src/components/comment/CommentForm/index.tsx index ee246687..8b0a1c8c 100644 --- a/src/components/comment/CommentForm/index.tsx +++ b/src/components/comment/CommentForm/index.tsx @@ -1,238 +1,97 @@ -import React, { FC, KeyboardEventHandler, memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { Textarea } from '~/components/input/Textarea'; -import styles from './styles.module.scss'; -import { Filler } from '~/components/containers/Filler'; +import React, { FC, useCallback, useState } from 'react'; +import { useCommentFormFormik } from '~/utils/hooks/useCommentFormFormik'; +import { FormikProvider } from 'formik'; +import { LocalCommentFormTextarea } from '~/components/comment/LocalCommentFormTextarea'; import { Button } from '~/components/input/Button'; -import assocPath from 'ramda/es/assocPath'; -import { IComment, IFileWithUUID, InputHandler } from '~/redux/types'; -import { connect } from 'react-redux'; -import * as NODE_ACTIONS from '~/redux/node/actions'; -import { selectNode } from '~/redux/node/selectors'; -import { LoaderCircle } from '~/components/input/LoaderCircle'; -import { Group } from '~/components/containers/Group'; -import { UPLOAD_SUBJECTS, UPLOAD_TARGETS, UPLOAD_TYPES } from '~/redux/uploads/constants'; -import uuid from 'uuid4'; -import * as UPLOAD_ACTIONS from '~/redux/uploads/actions'; -import { selectUploads } from '~/redux/uploads/selectors'; -import { IState } from '~/redux/store'; -import { getFileType } from '~/utils/uploader'; -import { useRandomPhrase } from '~/constants/phrases'; -import { ERROR_LITERAL } from '~/constants/errors'; -import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches'; +import { FileUploaderProvider, useFileUploader } from '~/utils/hooks/fileUploader'; +import { UPLOAD_SUBJECTS, UPLOAD_TARGETS } from '~/redux/uploads/constants'; import { CommentFormAttachButtons } from '~/components/comment/CommentFormAttachButtons'; -import { CommentFormDropzone } from '~/components/comment/CommentFormDropzone'; import { CommentFormFormatButtons } from '~/components/comment/CommentFormFormatButtons'; +import { CommentFormAttaches } from '~/components/comment/CommentFormAttaches'; +import { LoaderCircle } from '~/components/input/LoaderCircle'; +import { IComment, INode } from '~/redux/types'; +import { EMPTY_COMMENT } from '~/redux/node/constants'; +import { CommentFormDropzone } from '~/components/comment/CommentFormDropzone'; +import styles from './styles.module.scss'; +import { ERROR_LITERAL } from '~/constants/errors'; +import { Group } from '~/components/containers/Group'; -const mapStateToProps = (state: IState) => ({ - node: selectNode(state), - uploads: selectUploads(state), -}); +interface IProps { + comment?: IComment; + nodeId: INode['id']; + onCancelEdit?: () => void; +} -const mapDispatchToProps = { - nodePostComment: NODE_ACTIONS.nodePostComment, - nodeCancelCommentEdit: NODE_ACTIONS.nodeCancelCommentEdit, - nodeSetCommentData: NODE_ACTIONS.nodeSetCommentData, - uploadUploadFiles: UPLOAD_ACTIONS.uploadUploadFiles, -}; +const CommentForm: FC = ({ comment, nodeId, onCancelEdit }) => { + const [textarea, setTextarea] = useState(); + const uploader = useFileUploader( + UPLOAD_SUBJECTS.COMMENT, + UPLOAD_TARGETS.COMMENTS, + comment?.files + ); + const formik = useCommentFormFormik(comment || EMPTY_COMMENT, nodeId, uploader, onCancelEdit); + const isLoading = formik.isSubmitting || uploader.isUploading; + const isEditing = !!comment?.id; -type IProps = ReturnType & - typeof mapDispatchToProps & { - id: number; - is_before?: boolean; - }; + const clearError = useCallback(() => { + if (formik.status) { + formik.setStatus(''); + } -const CommentFormUnconnected: FC = memo( - ({ - node: { comment_data, is_sending_comment }, - uploads: { statuses, files }, - id, - is_before = false, - nodePostComment, - nodeSetCommentData, - uploadUploadFiles, - nodeCancelCommentEdit, - }) => { - const [textarea, setTextarea] = useState(); - const comment = useMemo(() => comment_data[id], [comment_data, id]); + if (formik.errors.text) { + formik.setErrors({ + ...formik.errors, + text: '', + }); + } + }, [formik]); - const onUpload = useCallback( - (files: File[]) => { - const items: IFileWithUUID[] = files.map( - (file: File): IFileWithUUID => ({ - file, - temp_id: uuid(), - subject: UPLOAD_SUBJECTS.COMMENT, - target: UPLOAD_TARGETS.COMMENTS, - type: getFileType(file), - }) - ); + const error = formik.status || formik.errors.text; - const temps = items.map(file => file.temp_id); - - nodeSetCommentData(id, assocPath(['temp_ids'], [...comment.temp_ids, ...temps], comment)); - uploadUploadFiles(items); - }, - [uploadUploadFiles, comment, id, nodeSetCommentData] - ); - - const onInput = useCallback( - text => { - nodeSetCommentData(id, assocPath(['text'], text, comment)); - }, - [nodeSetCommentData, comment, id] - ); - - useEffect(() => { - const temp_ids = (comment && comment.temp_ids) || []; - const added_files = temp_ids - .map(temp_uuid => statuses[temp_uuid] && statuses[temp_uuid].uuid) - .map(el => !!el && files[el]) - .filter(el => !!el && !comment.files.some(file => file && file.id === el.id)); - - const filtered_temps = temp_ids.filter( - temp_id => - statuses[temp_id] && - (!statuses[temp_id].uuid || !added_files.some(file => file.id === statuses[temp_id].uuid)) - ); - - if (added_files.length) { - nodeSetCommentData(id, { - ...comment, - temp_ids: filtered_temps, - files: [...comment.files, ...added_files], - }); - } - }, [statuses, files]); - - const isUploadingNow = useMemo(() => comment.temp_ids.length > 0, [comment.temp_ids]); - - const onSubmit = useCallback( - event => { - if (event) event.preventDefault(); - if (isUploadingNow || is_sending_comment) return; - - nodePostComment(id, is_before); - }, - [nodePostComment, id, is_before, isUploadingNow, is_sending_comment] - ); - - const onKeyDown = useCallback>( - ({ ctrlKey, key }) => { - if (!!ctrlKey && key === 'Enter') onSubmit(null); - }, - [onSubmit] - ); - - const images = useMemo( - () => comment.files.filter(file => file && file.type === UPLOAD_TYPES.IMAGE), - [comment.files] - ); - - const locked_images = useMemo( - () => - comment.temp_ids - .filter(temp => statuses[temp] && statuses[temp].type === UPLOAD_TYPES.IMAGE) - .map(temp_id => statuses[temp_id]), - [statuses, comment.temp_ids] - ); - - const audios = useMemo( - () => comment.files.filter(file => file && file.type === UPLOAD_TYPES.AUDIO), - [comment.files] - ); - - const locked_audios = useMemo( - () => - comment.temp_ids - .filter(temp => statuses[temp] && statuses[temp].type === UPLOAD_TYPES.AUDIO) - .map(temp_id => statuses[temp_id]), - [statuses, comment.temp_ids] - ); - - const onCancelEdit = useCallback(() => { - nodeCancelCommentEdit(id); - }, [nodeCancelCommentEdit, comment.id]); - - const placeholder = useRandomPhrase('SIMPLE'); - - const clearError = useCallback(() => nodeSetCommentData(id, { error: '' }), [ - id, - nodeSetCommentData, - ]); - - useEffect(() => { - if (comment.error) clearError(); - }, [comment.files, comment.text]); - - const setData = useCallback( - (data: Partial) => { - nodeSetCommentData(id, data); - }, - [nodeSetCommentData, id] - ); - - return ( - <> - -
+ return ( + + + +
-