mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
removed messages reducer
This commit is contained in:
parent
82308d2a91
commit
5849e68258
61 changed files with 314 additions and 898 deletions
|
@ -7,7 +7,7 @@ import {
|
||||||
ApiGetUserMessagesResponse,
|
ApiGetUserMessagesResponse,
|
||||||
ApiSendMessageRequest,
|
ApiSendMessageRequest,
|
||||||
ApiSendMessageResult,
|
ApiSendMessageResult,
|
||||||
} from '~/redux/messages/types';
|
} from '~/api/messages/types';
|
||||||
|
|
||||||
export const apiGetUserMessages = ({ username, after, before }: ApiGetUserMessagesRequest) =>
|
export const apiGetUserMessages = ({ username, after, before }: ApiGetUserMessagesRequest) =>
|
||||||
api
|
api
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from "react";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from "~/components/input/Icon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
icon: string;
|
icon: string;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from "react";
|
||||||
import { GithubIssue } from '~/types/boris';
|
import { GithubIssue } from "~/types/boris";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
import { Placeholder } from "~/components/placeholders/Placeholder";
|
||||||
import { BorisStatsGitCard } from '../BorisStatsGitCard';
|
import { BorisStatsGitCard } from "../BorisStatsGitCard";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
issues: GithubIssue[];
|
issues: GithubIssue[];
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from "react";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { getPrettyDate } from '~/utils/dom';
|
import { getPrettyDate } from "~/utils/dom";
|
||||||
import { GithubIssue } from '~/types/boris';
|
import { GithubIssue } from "~/types/boris";
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
data: GithubIssue;
|
data: GithubIssue;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { FC, memo, useMemo } from 'react';
|
import React, { FC, memo, useMemo } from "react";
|
||||||
import { ICommentBlockProps } from '~/constants/comment';
|
import { ICommentBlockProps } from "~/constants/comment";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { getYoutubeThumb } from '~/utils/dom';
|
import { getYoutubeThumb } from "~/utils/dom";
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from "~/components/input/Icon";
|
||||||
import { useYoutubeMetadata } from '~/hooks/metadata/useYoutubeMetadata';
|
import { useYoutubeMetadata } from "~/hooks/metadata/useYoutubeMetadata";
|
||||||
|
|
||||||
type Props = ICommentBlockProps & {};
|
type Props = ICommentBlockProps & {};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { FC, useCallback, useEffect } from 'react';
|
import React, { FC, useCallback, useEffect } from "react";
|
||||||
import { ButtonGroup } from '~/components/input/ButtonGroup';
|
import { ButtonGroup } from "~/components/input/ButtonGroup";
|
||||||
import { Button } from '~/components/input/Button';
|
import { Button } from "~/components/input/Button";
|
||||||
import { useFormatWrapper, wrapTextInsideInput } from '~/hooks/dom/useFormatWrapper';
|
import { useFormatWrapper, wrapTextInsideInput } from "~/hooks/dom/useFormatWrapper";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
element: HTMLTextAreaElement;
|
element: HTMLTextAreaElement;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
|
import React, { FC, useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { IUser } from '~/redux/auth/types';
|
import { IUser } from "~/redux/auth/types";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { getURL } from '~/utils/dom';
|
import { getURL } from "~/utils/dom";
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from "~/constants/urls";
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
cover: IUser['cover'];
|
cover: IUser['cover'];
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { DetailsHTMLAttributes, FC } from 'react';
|
import React, { DetailsHTMLAttributes, FC } from "react";
|
||||||
import StickyBox from 'react-sticky-box';
|
import StickyBox from "react-sticky-box";
|
||||||
|
|
||||||
interface IProps extends DetailsHTMLAttributes<HTMLDivElement> {
|
interface IProps extends DetailsHTMLAttributes<HTMLDivElement> {
|
||||||
offsetTop?: number;
|
offsetTop?: number;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from "react";
|
||||||
import { SortEnd } from 'react-sortable-hoc';
|
import { SortEnd } from "react-sortable-hoc";
|
||||||
import { IFile } from '~/redux/types';
|
import { IFile } from "~/redux/types";
|
||||||
import { moveArrItem } from '~/utils/fn';
|
import { moveArrItem } from "~/utils/fn";
|
||||||
import { SortableAudioGrid } from '~/components/editors/SortableAudioGrid';
|
import { SortableAudioGrid } from "~/components/editors/SortableAudioGrid";
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
import { UploadStatus } from "~/store/uploader/UploaderStore";
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
files: IFile[];
|
files: IFile[];
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { ChangeEvent, FC, useCallback, useEffect } from 'react';
|
import React, { ChangeEvent, FC, useCallback, useEffect } from "react";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { UploadSubject, UploadTarget, UploadType } from '~/constants/uploads';
|
import { UploadSubject, UploadTarget, UploadType } from "~/constants/uploads";
|
||||||
import { getURL } from '~/utils/dom';
|
import { getURL } from "~/utils/dom";
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from "~/components/input/Icon";
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from "~/constants/urls";
|
||||||
import { IEditorComponentProps } from '~/types/node';
|
import { IEditorComponentProps } from "~/types/node";
|
||||||
import { useNodeFormContext } from '~/hooks/node/useNodeFormFormik';
|
import { useNodeFormContext } from "~/hooks/node/useNodeFormFormik";
|
||||||
import { getFileType } from '~/utils/uploader';
|
import { getFileType } from "~/utils/uploader";
|
||||||
import { useUploader } from '~/hooks/data/useUploader';
|
import { useUploader } from "~/hooks/data/useUploader";
|
||||||
|
|
||||||
type IProps = IEditorComponentProps & {};
|
type IProps = IEditorComponentProps & {};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React, { FC, useCallback } from 'react';
|
import React, { FC, useCallback } from "react";
|
||||||
import { SortEnd } from 'react-sortable-hoc';
|
import { SortEnd } from "react-sortable-hoc";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { IFile } from '~/redux/types';
|
import { IFile } from "~/redux/types";
|
||||||
import { moveArrItem } from '~/utils/fn';
|
import { moveArrItem } from "~/utils/fn";
|
||||||
import { SortableImageGrid } from '~/components/editors/SortableImageGrid';
|
import { SortableImageGrid } from "~/components/editors/SortableImageGrid";
|
||||||
import { UploadStatus } from '~/store/uploader/UploaderStore';
|
import { UploadStatus } from "~/store/uploader/UploaderStore";
|
||||||
import { useWindowSize } from '~/hooks/dom/useWindowSize';
|
import { useWindowSize } from "~/hooks/dom/useWindowSize";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
files: IFile[];
|
files: IFile[];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
|
|
||||||
interface IGodRaysProps {
|
interface IGodRaysProps {
|
||||||
raised?: boolean;
|
raised?: boolean;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
import React, { FC, useCallback, useEffect, useState } from "react";
|
||||||
import classNames from 'classnames';
|
import classNames from "classnames";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from "~/components/containers/Group";
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from "react";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from "react";
|
||||||
import { NodeTags } from '~/components/node/NodeTags';
|
import { NodeTags } from "~/components/node/NodeTags";
|
||||||
import { useTagContext } from '~/utils/context/TagsContextProvider';
|
import { useTagContext } from "~/utils/context/TagsContextProvider";
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from "react";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { path } from 'ramda';
|
import { path } from "ramda";
|
||||||
import { INodeComponentProps } from '~/constants/node';
|
import { INodeComponentProps } from "~/constants/node";
|
||||||
|
|
||||||
interface IProps extends INodeComponentProps {}
|
interface IProps extends INodeComponentProps {}
|
||||||
|
|
||||||
|
|
|
@ -1,78 +1,26 @@
|
||||||
import React, { FC, useCallback } from "react";
|
import React, { FC } from "react";
|
||||||
import { IMessage } from "~/redux/types";
|
import { IMessage } from "~/redux/types";
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
import { formatText, getPrettyDate, getURL } from "~/utils/dom";
|
import { formatText, getPrettyDate, getURL } from "~/utils/dom";
|
||||||
import { PRESETS } from "~/constants/urls";
|
import { PRESETS } from "~/constants/urls";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Group } from "~/components/containers/Group";
|
import { Group } from "~/components/containers/Group";
|
||||||
import { CommentMenu } from "~/components/comment/CommentMenu";
|
|
||||||
import { MessageForm } from "~/components/profile/MessageForm";
|
|
||||||
import { Filler } from "~/components/containers/Filler";
|
|
||||||
import { Button } from "~/components/input/Button";
|
|
||||||
import markdown from "~/styles/common/markdown.module.scss";
|
import markdown from "~/styles/common/markdown.module.scss";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
message: IMessage;
|
message: IMessage;
|
||||||
incoming: boolean;
|
incoming: boolean;
|
||||||
onEdit: (id: number) => void;
|
|
||||||
onDelete: (id: number) => void;
|
|
||||||
onRestore: (id: number) => void;
|
|
||||||
onCancelEdit: () => void;
|
|
||||||
isEditing: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Message: FC<IProps> = ({
|
|
||||||
message,
|
|
||||||
incoming,
|
|
||||||
onEdit,
|
|
||||||
onDelete,
|
|
||||||
isEditing,
|
|
||||||
onCancelEdit,
|
|
||||||
onRestore,
|
|
||||||
}) => {
|
|
||||||
const onEditClicked = useCallback(() => onEdit(message.id), [onEdit, message.id]);
|
|
||||||
const onDeleteClicked = useCallback(() => onDelete(message.id), [onDelete, message.id]);
|
|
||||||
const onRestoreClicked = useCallback(() => onRestore(message.id), [onRestore, message.id]);
|
|
||||||
|
|
||||||
if (message.deleted_at) {
|
|
||||||
return (
|
|
||||||
<div className={classNames(styles.message)}>
|
|
||||||
<Group className={styles.deleted} horizontal>
|
|
||||||
<Filler>Сообщение удалено</Filler>
|
|
||||||
<Button
|
|
||||||
size="mini"
|
|
||||||
onClick={onRestoreClicked}
|
|
||||||
color="link"
|
|
||||||
iconLeft="restore"
|
|
||||||
className={styles.restore}
|
|
||||||
>
|
|
||||||
Восстановить
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={styles.avatar}
|
|
||||||
style={{ backgroundImage: `url("${getURL(message.from.photo, PRESETS.avatar)}")` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Message: FC<IProps> = ({ message, incoming }) => {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.message, { [styles.incoming]: incoming })}>
|
<div className={classNames(styles.message, { [styles.incoming]: incoming })}>
|
||||||
{isEditing ? (
|
|
||||||
<div className={styles.form}>
|
|
||||||
<MessageForm id={message.id} text={message.text} onCancel={onCancelEdit} />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={styles.text}>
|
<div className={styles.text}>
|
||||||
{!incoming && <CommentMenu onEdit={onEditClicked} onDelete={onDeleteClicked} />}
|
|
||||||
<Group
|
<Group
|
||||||
dangerouslySetInnerHTML={{ __html: formatText(message.text) }}
|
dangerouslySetInnerHTML={{ __html: formatText(message.text) }}
|
||||||
className={markdown.wrapper}
|
className={markdown.wrapper}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
|
|
|
@ -11,10 +11,6 @@ $outgoing_color: $comment_bg;
|
||||||
position: relative;
|
position: relative;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
|
||||||
.avatar {
|
|
||||||
// margin: 0 0 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: '';
|
content: '';
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
import React, { FC, KeyboardEventHandler, useCallback, useMemo, useState } from 'react';
|
|
||||||
import styles from './styles.module.scss';
|
|
||||||
import { Textarea } from '~/components/input/Textarea';
|
|
||||||
import { Filler } from '~/components/containers/Filler';
|
|
||||||
import { Button } from '~/components/input/Button';
|
|
||||||
import { Group } from '~/components/containers/Group';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
|
||||||
import * as MESSAGES_ACTIONS from '~/redux/messages/actions';
|
|
||||||
import { ERROR_LITERAL } from '~/constants/errors';
|
|
||||||
import { selectMessages } from '~/redux/messages/selectors';
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
messages: selectMessages(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
messagesSendMessage: MESSAGES_ACTIONS.messagesSendMessage,
|
|
||||||
};
|
|
||||||
|
|
||||||
type IProps = ReturnType<typeof mapStateToProps> &
|
|
||||||
typeof mapDispatchToProps & {
|
|
||||||
id?: number;
|
|
||||||
text?: string;
|
|
||||||
onCancel?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const MessageFormUnconnected: FC<IProps> = ({
|
|
||||||
messages: { is_sending_messages, is_loading_messages, error },
|
|
||||||
messagesSendMessage,
|
|
||||||
|
|
||||||
id = 0,
|
|
||||||
text: initialText = '',
|
|
||||||
onCancel,
|
|
||||||
}) => {
|
|
||||||
const isEditing = useMemo(() => id > 0, [id]);
|
|
||||||
const [text, setText] = useState(initialText);
|
|
||||||
|
|
||||||
const onSuccess = useCallback(() => {
|
|
||||||
setText('');
|
|
||||||
|
|
||||||
if (isEditing && onCancel) {
|
|
||||||
onCancel();
|
|
||||||
}
|
|
||||||
}, [setText, isEditing, onCancel]);
|
|
||||||
|
|
||||||
const onSubmit = useCallback(() => {
|
|
||||||
messagesSendMessage({ text, id }, onSuccess);
|
|
||||||
}, [messagesSendMessage, text, id, onSuccess]);
|
|
||||||
|
|
||||||
const onKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
|
|
||||||
({ ctrlKey, key }) => {
|
|
||||||
if (ctrlKey && key === 'Enter') onSubmit();
|
|
||||||
},
|
|
||||||
[onSubmit]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.wrap}>
|
|
||||||
{error && <div className={styles.error}>{ERROR_LITERAL[error]}</div>}
|
|
||||||
{is_loading_messages && !error && (
|
|
||||||
<Group className={styles.loader} horizontal>
|
|
||||||
<LoaderCircle size={20} />
|
|
||||||
<div>Обновляем</div>
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
<Group className={styles.content}>
|
|
||||||
<Textarea
|
|
||||||
value={text}
|
|
||||||
handler={setText}
|
|
||||||
minRows={1}
|
|
||||||
maxRows={isEditing ? 15 : 5}
|
|
||||||
onKeyDown={onKeyDown}
|
|
||||||
disabled={is_sending_messages}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Group className={styles.buttons} horizontal>
|
|
||||||
<Filler />
|
|
||||||
|
|
||||||
{is_sending_messages && <LoaderCircle size={20} />}
|
|
||||||
|
|
||||||
{isEditing && (
|
|
||||||
<Button size="small" color="link" onClick={onCancel}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
color="gray"
|
|
||||||
iconRight="enter"
|
|
||||||
disabled={is_sending_messages}
|
|
||||||
onClick={onSubmit}
|
|
||||||
>
|
|
||||||
{isEditing ? 'Схоронить' : 'Сказать'}
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Group>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MessageForm = connect(mapStateToProps, mapDispatchToProps)(MessageFormUnconnected);
|
|
||||||
|
|
||||||
export { MessageForm };
|
|
|
@ -1,61 +0,0 @@
|
||||||
@import "src/styles/variables";
|
|
||||||
|
|
||||||
.wrap {
|
|
||||||
@include outer_shadow();
|
|
||||||
padding: $gap;
|
|
||||||
background: $content_bg;
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
padding-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
@include inner_shadow();
|
|
||||||
background: $input_bg_color;
|
|
||||||
border-radius: $radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 0 $gap / 2 $gap / 2 $gap / 2;
|
|
||||||
border-radius: 0 0 $radius $radius;
|
|
||||||
|
|
||||||
:global(.loader-circle) {
|
|
||||||
svg {
|
|
||||||
fill: $wisegreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
left: 5%;
|
|
||||||
width: 90%;
|
|
||||||
background: linear-gradient($red, transparentize($red, 0.1));
|
|
||||||
z-index: 1;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 0 0 $radius $radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
position: absolute;
|
|
||||||
right: 50%;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 2;
|
|
||||||
padding: $gap;
|
|
||||||
text-transform: uppercase;
|
|
||||||
background: $wisegreen;
|
|
||||||
border-radius: $radius;
|
|
||||||
transform: translate(50%, -10px);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { FC, VFC } from 'react';
|
|
||||||
import { LoginDialog } from '~/containers/dialogs/LoginDialog';
|
import { LoginDialog } from '~/containers/dialogs/LoginDialog';
|
||||||
import { LoginSocialRegisterDialog } from '~/containers/dialogs/LoginSocialRegisterDialog';
|
import { LoginSocialRegisterDialog } from '~/containers/dialogs/LoginSocialRegisterDialog';
|
||||||
import { LoadingDialog } from '~/containers/dialogs/LoadingDialog';
|
import { LoadingDialog } from '~/containers/dialogs/LoadingDialog';
|
||||||
|
@ -6,8 +5,7 @@ import { TestDialog } from '~/containers/dialogs/TestDialog';
|
||||||
import { ProfileDialog } from '~/containers/dialogs/ProfileDialog';
|
import { ProfileDialog } from '~/containers/dialogs/ProfileDialog';
|
||||||
import { RestoreRequestDialog } from '~/containers/dialogs/RestoreRequestDialog';
|
import { RestoreRequestDialog } from '~/containers/dialogs/RestoreRequestDialog';
|
||||||
import { RestorePasswordDialog } from '~/containers/dialogs/RestorePasswordDialog';
|
import { RestorePasswordDialog } from '~/containers/dialogs/RestorePasswordDialog';
|
||||||
import { PhotoSwipe, PhotoSwipeProps } from '~/containers/dialogs/PhotoSwipe';
|
import { PhotoSwipe } from '~/containers/dialogs/PhotoSwipe';
|
||||||
import { IDialogProps } from '~/types/modal';
|
|
||||||
|
|
||||||
export enum Dialog {
|
export enum Dialog {
|
||||||
Login = 'Login',
|
Login = 'Login',
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { Group } from "~/components/containers/Group";
|
import { Group } from '~/components/containers/Group';
|
||||||
import { NodeCommentForm } from "~/components/node/NodeCommentForm";
|
import { NodeCommentForm } from '~/components/node/NodeCommentForm';
|
||||||
import { NodeNoComments } from "~/components/node/NodeNoComments";
|
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||||
import { NodeComments } from "~/containers/node/NodeComments";
|
import { NodeComments } from '~/containers/node/NodeComments';
|
||||||
import { Footer } from "~/components/main/Footer";
|
import { Footer } from '~/components/main/Footer';
|
||||||
import { CommentContextProvider, useCommentContext } from "~/utils/context/CommentContextProvider";
|
import { CommentContextProvider, useCommentContext } from '~/utils/context/CommentContextProvider';
|
||||||
import { useUserContext } from "~/utils/context/UserContextProvider";
|
import { useUserContext } from '~/utils/context/UserContextProvider';
|
||||||
import { useNodeContext } from "~/utils/context/NodeContextProvider";
|
import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { FC, MouseEventHandler, useEffect, useRef } from "react";
|
import React, { FC, MouseEventHandler, useEffect, useRef } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { clearAllBodyScrollLocks, disableBodyScroll } from "body-scroll-lock";
|
import { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock';
|
||||||
import { Icon } from "~/components/input/Icon";
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { LoaderCircle } from "~/components/input/LoaderCircle";
|
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
children: React.ReactChild;
|
children: React.ReactChild;
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import React, { createElement, FC, useCallback, useMemo, useState } from "react";
|
import React, { createElement, FC, useCallback, useMemo, useState } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { NODE_EDITORS } from "~/constants/node";
|
import { NODE_EDITORS } from '~/constants/node';
|
||||||
import { BetterScrollDialog } from "../BetterScrollDialog";
|
import { BetterScrollDialog } from '../BetterScrollDialog';
|
||||||
import { CoverBackdrop } from "~/components/containers/CoverBackdrop";
|
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
||||||
import { prop } from "ramda";
|
import { prop } from 'ramda';
|
||||||
import { useNodeFormFormik } from "~/hooks/node/useNodeFormFormik";
|
import { useNodeFormFormik } from '~/hooks/node/useNodeFormFormik';
|
||||||
import { EditorButtons } from "~/components/editors/EditorButtons";
|
import { EditorButtons } from '~/components/editors/EditorButtons';
|
||||||
import { UploadSubject, UploadTarget } from "~/constants/uploads";
|
import { UploadSubject, UploadTarget } from '~/constants/uploads';
|
||||||
import { FormikProvider } from "formik";
|
import { FormikProvider } from 'formik';
|
||||||
import { INode } from "~/redux/types";
|
import { INode } from '~/redux/types';
|
||||||
import { ModalWrapper } from "~/components/dialogs/ModalWrapper";
|
import { ModalWrapper } from '~/components/dialogs/ModalWrapper';
|
||||||
import { useTranslatedError } from "~/hooks/data/useTranslatedError";
|
import { useTranslatedError } from '~/hooks/data/useTranslatedError';
|
||||||
import { useCloseOnEscape } from "~/hooks";
|
import { useCloseOnEscape } from '~/hooks';
|
||||||
import { EditorConfirmClose } from "~/components/editors/EditorConfirmClose";
|
import { EditorConfirmClose } from '~/components/editors/EditorConfirmClose';
|
||||||
import { IDialogProps } from "~/types/modal";
|
import { IDialogProps } from '~/types/modal';
|
||||||
import { useUploader } from "~/hooks/data/useUploader";
|
import { useUploader } from '~/hooks/data/useUploader';
|
||||||
import { UploaderContextProvider } from "~/utils/context/UploaderContextProvider";
|
import { UploaderContextProvider } from '~/utils/context/UploaderContextProvider';
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
interface Props extends IDialogProps {
|
interface Props extends IDialogProps {
|
||||||
node: INode;
|
node: INode;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, VFC } from 'react';
|
import React, { VFC } from 'react';
|
||||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { FC, MouseEventHandler } from "react";
|
import React, { FC, MouseEventHandler } from 'react';
|
||||||
import { Button } from "~/components/input/Button";
|
import { Button } from '~/components/input/Button';
|
||||||
import { Grid } from "~/components/containers/Grid";
|
import { Grid } from '~/components/containers/Grid';
|
||||||
import { Group } from "~/components/containers/Group";
|
import { Group } from '~/components/containers/Group';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { ISocialProvider } from "~/redux/auth/types";
|
import { ISocialProvider } from '~/redux/auth/types';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
openOauthWindow: (provider: ISocialProvider) => MouseEventHandler;
|
openOauthWindow: (provider: ISocialProvider) => MouseEventHandler;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import { Button } from "~/components/input/Button";
|
import { Button } from '~/components/input/Button';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||||
import { IAuthState } from '~/redux/auth/types';
|
import { IAuthState } from '~/redux/auth/types';
|
||||||
import { pick } from 'ramda';
|
import { pick } from 'ramda';
|
||||||
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
||||||
import { MessageForm } from '~/components/profile/MessageForm';
|
|
||||||
import { Tabs } from '~/components/dialogs/Tabs';
|
import { Tabs } from '~/components/dialogs/Tabs';
|
||||||
import { ProfileDescription } from '~/components/profile/ProfileDescription';
|
import { ProfileDescription } from '~/components/profile/ProfileDescription';
|
||||||
import { ProfileMessages } from '~/containers/profile/ProfileMessages';
|
import { ProfileMessages } from '~/containers/profile/ProfileMessages';
|
||||||
|
@ -26,13 +25,9 @@ const mapDispatchToProps = {
|
||||||
|
|
||||||
type IProps = IDialogProps & ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
type IProps = IDialogProps & ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||||
|
|
||||||
const PROFILE_HEADERS = {
|
const PROFILE_HEADERS = {};
|
||||||
// messages: <MessageForm />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROFILE_FOOTERS = {
|
const PROFILE_FOOTERS = {};
|
||||||
messages: <MessageForm />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProfileDialogUnconnected: FC<IProps> = ({
|
const ProfileDialogUnconnected: FC<IProps> = ({
|
||||||
onRequestClose,
|
onRequestClose,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useState, VFC } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState, VFC } from 'react';
|
||||||
import { connect, useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { BetterScrollDialog } from '../BetterScrollDialog';
|
import { BetterScrollDialog } from '../BetterScrollDialog';
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from '~/components/containers/Group';
|
||||||
import { InputText } from '~/components/input/InputText';
|
import { InputText } from '~/components/input/InputText';
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import React, { FC, FormEvent, useCallback, useMemo } from "react";
|
import React, { FC, FormEvent, useCallback, useMemo } from 'react';
|
||||||
import { InputText } from "~/components/input/InputText";
|
import { InputText } from '~/components/input/InputText';
|
||||||
import { FlowRecent } from "~/components/flow/FlowRecent";
|
import { FlowRecent } from '~/components/flow/FlowRecent';
|
||||||
|
|
||||||
import styles from "~/containers/flow/FlowStamp/styles.module.scss";
|
import styles from '~/containers/flow/FlowStamp/styles.module.scss';
|
||||||
import { FlowSearchResults } from "~/components/flow/FlowSearchResults";
|
import { FlowSearchResults } from '~/components/flow/FlowSearchResults';
|
||||||
import { Icon } from "~/components/input/Icon";
|
import { Icon } from '~/components/input/Icon';
|
||||||
import { Group } from "~/components/containers/Group";
|
import { Group } from '~/components/containers/Group';
|
||||||
import { Toggle } from "~/components/input/Toggle";
|
import { Toggle } from '~/components/input/Toggle';
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames';
|
||||||
import { Superpower } from "~/components/boris/Superpower";
|
import { Superpower } from '~/components/boris/Superpower';
|
||||||
import { experimentalFeatures } from "~/constants/features";
|
import { experimentalFeatures } from '~/constants/features';
|
||||||
import { useSearchContext } from "~/utils/providers/SearchProvider";
|
import { useSearchContext } from '~/utils/providers/SearchProvider';
|
||||||
import { useFlowContext } from "~/utils/context/FlowContextProvider";
|
import { useFlowContext } from '~/utils/context/FlowContextProvider';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isFluid: boolean;
|
isFluid: boolean;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { LabBanner } from "~/components/lab/LabBanner";
|
import { LabBanner } from '~/components/lab/LabBanner';
|
||||||
import { Group } from "~/components/containers/Group";
|
import { Group } from '~/components/containers/Group';
|
||||||
import { LabTags } from "~/components/lab/LabTags";
|
import { LabTags } from '~/components/lab/LabTags';
|
||||||
import { LabHeroes } from "~/components/lab/LabHeroes";
|
import { LabHeroes } from '~/components/lab/LabHeroes';
|
||||||
import { FlowRecentItem } from "~/components/flow/FlowRecentItem";
|
import { FlowRecentItem } from '~/components/flow/FlowRecentItem';
|
||||||
import { SubTitle } from "~/components/common/SubTitle";
|
import { SubTitle } from '~/components/common/SubTitle';
|
||||||
import { useLabContext } from "~/utils/context/LabContextProvider";
|
import { useLabContext } from '~/utils/context/LabContextProvider';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { PlayerView } from "~/containers/player/PlayerView";
|
import { PlayerView } from '~/containers/player/PlayerView';
|
||||||
|
|
||||||
type IProps = {};
|
type IProps = {};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import { URLS } from "~/constants/urls";
|
import { URLS } from '~/constants/urls';
|
||||||
import { ErrorNotFound } from "~/containers/pages/ErrorNotFound";
|
import { ErrorNotFound } from '~/containers/pages/ErrorNotFound';
|
||||||
import { Redirect, Route, Switch, useLocation } from "react-router";
|
import { Redirect, Route, Switch, useLocation } from 'react-router';
|
||||||
import { useShallowSelect } from "~/hooks/data/useShallowSelect";
|
import { useShallowSelect } from '~/hooks/data/useShallowSelect';
|
||||||
import { selectAuthUser } from "~/redux/auth/selectors";
|
import { selectAuthUser } from '~/redux/auth/selectors';
|
||||||
import { ProfileLayout } from "~/layouts/ProfileLayout";
|
import { ProfileLayout } from '~/layouts/ProfileLayout';
|
||||||
import FlowPage from "~/pages";
|
import FlowPage from '~/pages';
|
||||||
import BorisPage from "~/pages/boris";
|
import BorisPage from '~/pages/boris';
|
||||||
import NodePage from "~/pages/node/[id]";
|
import NodePage from '~/pages/node/[id]';
|
||||||
import LabPage from "~/pages/lab";
|
import LabPage from '~/pages/lab';
|
||||||
|
|
||||||
interface IProps {}
|
interface IProps {}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from 'react-dom';
|
||||||
import { Route, Switch } from "react-router";
|
import { Route, Switch } from 'react-router';
|
||||||
import { TagSidebar } from "~/containers/sidebars/TagSidebar";
|
import { TagSidebar } from '~/containers/sidebars/TagSidebar';
|
||||||
import { ProfileSidebar } from "~/containers/sidebars/ProfileSidebar";
|
import { ProfileSidebar } from '~/containers/sidebars/ProfileSidebar';
|
||||||
import { Authorized } from "~/components/containers/Authorized";
|
import { Authorized } from '~/components/containers/Authorized';
|
||||||
import { SubmitBar } from "~/components/bars/SubmitBar";
|
import { SubmitBar } from '~/components/bars/SubmitBar';
|
||||||
import { EditorCreateDialog } from "~/containers/dialogs/EditorCreateDialog";
|
import { EditorCreateDialog } from '~/containers/dialogs/EditorCreateDialog';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { FC, memo, useMemo } from "react";
|
import React, { FC, memo, useMemo } from 'react';
|
||||||
|
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { ICommentGroup } from "~/redux/types";
|
import { ICommentGroup } from '~/redux/types';
|
||||||
import { canEditComment } from "~/utils/node";
|
import { canEditComment } from '~/utils/node';
|
||||||
import { useGrouppedComments } from "~/hooks/node/useGrouppedComments";
|
import { useGrouppedComments } from '~/hooks/node/useGrouppedComments';
|
||||||
import { useCommentContext } from "~/utils/context/CommentContextProvider";
|
import { useCommentContext } from '~/utils/context/CommentContextProvider';
|
||||||
import { Comment } from "~/components/comment/Comment";
|
import { Comment } from '~/components/comment/Comment';
|
||||||
import { useUserContext } from "~/utils/context/UserContextProvider";
|
import { useUserContext } from '~/utils/context/UserContextProvider';
|
||||||
import { useNodeContext } from "~/utils/context/NodeContextProvider";
|
import { useNodeContext } from '~/utils/context/NodeContextProvider';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
order: 'ASC' | 'DESC';
|
order: 'ASC' | 'DESC';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { VFC } from "react";
|
import React, { VFC } from 'react';
|
||||||
import { PlayerBar } from "~/components/bars/PlayerBar";
|
import { PlayerBar } from '~/components/bars/PlayerBar';
|
||||||
import { useAudioPlayer } from "~/utils/providers/AudioPlayerProvider";
|
import { useAudioPlayer } from '~/utils/providers/AudioPlayerProvider';
|
||||||
|
|
||||||
interface PlayerViewProps {}
|
interface PlayerViewProps {}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React, { FC, ReactNode } from "react";
|
import React, { FC, ReactNode } from 'react';
|
||||||
import { IAuthState, IUser } from "~/redux/auth/types";
|
import { IAuthState, IUser } from '~/redux/auth/types';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { Group } from "~/components/containers/Group";
|
import { Group } from '~/components/containers/Group';
|
||||||
import { Placeholder } from "~/components/placeholders/Placeholder";
|
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||||
import { getPrettyDate } from "~/utils/dom";
|
import { getPrettyDate } from '~/utils/dom';
|
||||||
import { ProfileTabs } from "../ProfileTabs";
|
import { ProfileTabs } from '../ProfileTabs';
|
||||||
import { ProfileAvatar } from "../ProfileAvatar";
|
import { ProfileAvatar } from '../ProfileAvatar';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
user?: IUser;
|
user?: IUser;
|
||||||
|
|
|
@ -1,130 +1,52 @@
|
||||||
import React, { FC, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors';
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import * as AUTH_ACTIONS from '~/redux/messages/actions';
|
|
||||||
import { Message } from '~/components/profile/Message';
|
import { Message } from '~/components/profile/Message';
|
||||||
import { pick } from 'ramda';
|
|
||||||
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
import { NodeNoComments } from '~/components/node/NodeNoComments';
|
||||||
import { selectMessages } from '~/redux/messages/selectors';
|
import { useShallowSelect } from '~/hooks/data/useShallowSelect';
|
||||||
|
import { selectAuthProfile } from '~/redux/auth/selectors';
|
||||||
|
import { useMessages } from '~/hooks/messages/useMessages';
|
||||||
|
import { useUser } from '~/hooks/user/userUser';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const ProfileMessages: FC = () => {
|
||||||
profile: selectAuthProfile(state),
|
const profile = useShallowSelect(selectAuthProfile);
|
||||||
messages: selectMessages(state),
|
const user = useUser();
|
||||||
user: pick(['id'], selectAuthUser(state)),
|
const { messages, isLoading } = useMessages(profile.user?.username || '');
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
if (!messages.length || profile.is_loading)
|
||||||
messagesGetMessages: AUTH_ACTIONS.messagesGetMessages,
|
return <NodeNoComments is_loading={isLoading || profile.is_loading} />;
|
||||||
messagesRefreshMessages: AUTH_ACTIONS.messagesRefreshMessages,
|
|
||||||
messagesDeleteMessage: AUTH_ACTIONS.messagesDeleteMessage,
|
|
||||||
};
|
|
||||||
|
|
||||||
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
if (messages.length <= 0) {
|
||||||
|
|
||||||
const ProfileMessagesUnconnected: FC<IProps> = ({
|
|
||||||
profile,
|
|
||||||
messages,
|
|
||||||
user: { id },
|
|
||||||
messagesGetMessages,
|
|
||||||
messagesDeleteMessage,
|
|
||||||
messagesRefreshMessages,
|
|
||||||
}) => {
|
|
||||||
const wasAtBottom = useRef(true);
|
|
||||||
const [wrap, setWrap] = useState<HTMLDivElement | undefined>(undefined);
|
|
||||||
const [editingMessageId, setEditingMessageId] = useState(0);
|
|
||||||
|
|
||||||
const onEditMessage = useCallback((id: number) => setEditingMessageId(id), [setEditingMessageId]);
|
|
||||||
const onCancelEdit = useCallback(() => setEditingMessageId(0), [setEditingMessageId]);
|
|
||||||
const onDeleteMessage = useCallback((id: number) => messagesDeleteMessage(id, true), [
|
|
||||||
messagesDeleteMessage,
|
|
||||||
]);
|
|
||||||
const onRestoreMessage = useCallback((id: number) => messagesDeleteMessage(id, false), [
|
|
||||||
messagesDeleteMessage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (profile.is_loading || !profile.user || !profile.user.username) return;
|
|
||||||
|
|
||||||
messagesGetMessages(profile.user.username);
|
|
||||||
}, [messagesGetMessages, profile.is_loading, profile.user]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setInterval(messagesRefreshMessages, 20000);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [messagesRefreshMessages]);
|
|
||||||
|
|
||||||
const storeRef = useCallback(
|
|
||||||
(div: HTMLDivElement) => {
|
|
||||||
if (!div || !div.parentElement) return;
|
|
||||||
const parent = div.parentElement;
|
|
||||||
parent.scrollTo(0, parent.scrollHeight);
|
|
||||||
setWrap(div);
|
|
||||||
},
|
|
||||||
[setWrap]
|
|
||||||
);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const parent = wrap?.parentElement;
|
|
||||||
|
|
||||||
if (!parent) return;
|
|
||||||
|
|
||||||
if (wasAtBottom.current) {
|
|
||||||
parent.scrollTo(0, parent.scrollHeight);
|
|
||||||
}
|
|
||||||
}, [messages.messages, wrap]);
|
|
||||||
|
|
||||||
const onScroll = useCallback(() => {
|
|
||||||
const parent = wrap?.parentElement;
|
|
||||||
|
|
||||||
if (!parent) return;
|
|
||||||
|
|
||||||
const scrollPos = parent.scrollTop + parent.clientHeight;
|
|
||||||
wasAtBottom.current = parent.scrollHeight - scrollPos < 40;
|
|
||||||
}, [wrap]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const parent = wrap?.parentElement;
|
|
||||||
if (!parent) return;
|
|
||||||
|
|
||||||
parent.addEventListener('scroll', onScroll);
|
|
||||||
return () => parent.removeEventListener('scroll', onScroll);
|
|
||||||
}, [wrap, onScroll]);
|
|
||||||
|
|
||||||
if (!messages.messages.length || profile.is_loading)
|
|
||||||
return <NodeNoComments is_loading={messages.is_loading_messages || profile.is_loading} />;
|
|
||||||
|
|
||||||
if (messages.messages.length <= 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.messages} ref={storeRef}>
|
<div className={styles.messages}>
|
||||||
{messages.messages
|
<div className={styles.warning}>
|
||||||
|
<p>В будущем мы собираемся убрать сообщения, превратив их в заметки.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Вся твоя история сообщений, написанных себе, сохранится. Исчезнут только сообщения другим
|
||||||
|
участникам.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Давай обсудим это в <a href="/boris">Борисе</a>, если это так важно.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{messages
|
||||||
.filter(message => !!message.text)
|
.filter(message => !!message.text)
|
||||||
.map((
|
.map((
|
||||||
message // TODO: show files / memo
|
message // TODO: show files / memo
|
||||||
) => (
|
) => (
|
||||||
<Message
|
<Message message={message} incoming={user.id !== message.from.id} key={message.id} />
|
||||||
message={message}
|
|
||||||
incoming={id !== message.from.id}
|
|
||||||
key={message.id}
|
|
||||||
onEdit={onEditMessage}
|
|
||||||
onDelete={onDeleteMessage}
|
|
||||||
isEditing={editingMessageId === message.id}
|
|
||||||
onCancelEdit={onCancelEdit}
|
|
||||||
onRestore={onRestoreMessage}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{!messages.is_loading_messages && messages.messages.length > 0 && (
|
{!isLoading && messages.length > 0 && (
|
||||||
<div className={styles.placeholder}>Когда-нибудь здесь будут еще сообщения</div>
|
<div className={styles.placeholder}>Когда-нибудь здесь будут еще сообщения</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProfileMessages = connect(mapStateToProps, mapDispatchToProps)(ProfileMessagesUnconnected);
|
|
||||||
|
|
||||||
export { ProfileMessages };
|
export { ProfileMessages };
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
padding: $gap;
|
padding: $gap;
|
||||||
background: $node_bg;
|
background: $node_bg;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse !important;
|
flex-direction: column !important;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
|
@ -25,3 +25,19 @@
|
||||||
padding: $gap;
|
padding: $gap;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.warning.warning {
|
||||||
|
padding: $gap;
|
||||||
|
box-shadow: $red 0 0 0 2px;
|
||||||
|
border-radius: $radius;
|
||||||
|
font: $font_14_semibold;
|
||||||
|
margin-bottom: $gap * 2;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: $gap;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import { IAuthState } from "~/redux/auth/types";
|
import { IAuthState } from '~/redux/auth/types';
|
||||||
import { formatText } from "~/utils/dom";
|
import { formatText } from '~/utils/dom';
|
||||||
import { PRESETS } from "~/constants/urls";
|
import { PRESETS } from '~/constants/urls';
|
||||||
import { Placeholder } from "~/components/placeholders/Placeholder";
|
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||||
|
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { Avatar } from "~/components/common/Avatar";
|
import { Avatar } from '~/components/common/Avatar';
|
||||||
import { Markdown } from "~/components/containers/Markdown";
|
import { Markdown } from '~/components/containers/Markdown';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
profile: IAuthState['profile'];
|
profile: IAuthState['profile'];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { StatsRow } from "~/components/common/StatsRow";
|
import { StatsRow } from '~/components/common/StatsRow';
|
||||||
import { SubTitle } from "~/components/common/SubTitle";
|
import { SubTitle } from '~/components/common/SubTitle';
|
||||||
|
|
||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { IAuthState } from "~/redux/auth/types";
|
import { IAuthState } from '~/redux/auth/types';
|
||||||
import { Tabs } from "~/components/dialogs/Tabs";
|
import { Tabs } from '~/components/dialogs/Tabs';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tab: string;
|
tab: string;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { FC, useEffect, useRef } from "react";
|
import React, { FC, useEffect, useRef } from 'react';
|
||||||
import styles from "./styles.module.scss";
|
import styles from './styles.module.scss';
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from 'react-dom';
|
||||||
import { clearAllBodyScrollLocks, disableBodyScroll } from "body-scroll-lock";
|
import { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock';
|
||||||
import { useCloseOnEscape } from "~/hooks";
|
import { useCloseOnEscape } from '~/hooks';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { FC, HTMLAttributes, useCallback, useMemo, useState } from "react";
|
import React, { FC, HTMLAttributes, useCallback, useMemo, useState } from 'react';
|
||||||
import { TagField } from "~/components/containers/TagField";
|
import { TagField } from '~/components/containers/TagField';
|
||||||
import { ITag } from "~/redux/types";
|
import { ITag } from '~/redux/types';
|
||||||
import { uniq } from "ramda";
|
import { uniq } from 'ramda';
|
||||||
import { Tag } from "~/components/tags/Tag";
|
import { Tag } from '~/components/tags/Tag';
|
||||||
import { TagInput } from "~/containers/tags/TagInput";
|
import { TagInput } from '~/containers/tags/TagInput';
|
||||||
import { separateTags } from "~/utils/tag";
|
import { separateTags } from '~/utils/tag';
|
||||||
|
|
||||||
type IProps = HTMLAttributes<HTMLDivElement> & {
|
type IProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
tags: Partial<ITag>[];
|
tags: Partial<ITag>[];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from "react";
|
||||||
|
|
||||||
/** wraps text inside textarea with tags */
|
/** wraps text inside textarea with tags */
|
||||||
export const useFormatWrapper = (onChange: (val: string) => void) => {
|
export const useFormatWrapper = (onChange: (val: string) => void) => {
|
||||||
|
|
17
src/hooks/messages/useMessages.ts
Normal file
17
src/hooks/messages/useMessages.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { API } from '~/constants/api';
|
||||||
|
import { apiGetUserMessages } from '~/api/messages';
|
||||||
|
import { IMessage } from '~/redux/types';
|
||||||
|
|
||||||
|
const getKey = (username: string): string | null => {
|
||||||
|
return username ? `${API.USER.MESSAGES}/${username}` : null;
|
||||||
|
};
|
||||||
|
export const useMessages = (username: string) => {
|
||||||
|
const { data, isValidating } = useSWR(getKey(username), async () =>
|
||||||
|
apiGetUserMessages({ username })
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages: IMessage[] = data?.messages || [];
|
||||||
|
|
||||||
|
return { messages, isLoading: !data && isValidating };
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
import { useModalStore } from '~/store/modal/useModalStore';
|
import { useModalStore } from '~/store/modal/useModalStore';
|
||||||
import { FC, useCallback, VFC } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Dialog, DIALOG_CONTENT } from '~/constants/modal';
|
import { Dialog, DIALOG_CONTENT } from '~/constants/modal';
|
||||||
import { IDialogProps } from '~/types/modal';
|
import { IDialogProps } from '~/types/modal';
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from "react";
|
||||||
import styles from './styles.module.scss';
|
import styles from "./styles.module.scss";
|
||||||
import { Group } from '~/components/containers/Group';
|
import { Group } from "~/components/containers/Group";
|
||||||
import { Container } from '~/containers/main/Container';
|
import { Container } from "~/containers/main/Container";
|
||||||
import StickyBox from 'react-sticky-box';
|
import StickyBox from "react-sticky-box";
|
||||||
import { BorisComments } from '~/containers/boris/BorisComments';
|
import { BorisComments } from "~/containers/boris/BorisComments";
|
||||||
import { Card } from '~/components/containers/Card';
|
import { Card } from "~/components/containers/Card";
|
||||||
import { SidebarRouter } from '~/containers/main/SidebarRouter';
|
import { SidebarRouter } from "~/containers/main/SidebarRouter";
|
||||||
import { BorisSidebar } from '~/components/boris/BorisSidebar';
|
import { BorisSidebar } from "~/components/boris/BorisSidebar";
|
||||||
import { useUserContext } from '~/utils/context/UserContextProvider';
|
import { useUserContext } from "~/utils/context/UserContextProvider";
|
||||||
import { BorisUsageStats } from '~/types/boris';
|
import { BorisUsageStats } from "~/types/boris";
|
||||||
import { Tabs } from '~/components/dialogs/Tabs';
|
import { Tabs } from "~/components/dialogs/Tabs";
|
||||||
import { Superpower } from '~/components/boris/Superpower';
|
import { Superpower } from "~/components/boris/Superpower";
|
||||||
import { BorisUIDemo } from '~/components/boris/BorisUIDemo';
|
import { BorisUIDemo } from "~/components/boris/BorisUIDemo";
|
||||||
|
|
||||||
import boris from '~/sprites/boris_robot.svg';
|
import boris from "~/sprites/boris_robot.svg";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { selectAuthProfile, selectUser } from "~/redux/auth/selectors";
|
||||||
import { ProfilePageLeft } from "~/containers/profile/ProfilePageLeft";
|
import { ProfilePageLeft } from "~/containers/profile/ProfilePageLeft";
|
||||||
import { Container } from "~/containers/main/Container";
|
import { Container } from "~/containers/main/Container";
|
||||||
import { FlowGrid } from "~/components/flow/FlowGrid";
|
import { FlowGrid } from "~/components/flow/FlowGrid";
|
||||||
import { Sticky } from "~/components/containers/Sticky";
|
|
||||||
import { ProfilePageStats } from "~/containers/profile/ProfilePageStats";
|
import { ProfilePageStats } from "~/containers/profile/ProfilePageStats";
|
||||||
import { Card } from "~/components/containers/Card";
|
import { Card } from "~/components/containers/Card";
|
||||||
import { useFlowStore } from "~/store/flow/useFlowStore";
|
import { useFlowStore } from "~/store/flow/useFlowStore";
|
||||||
|
@ -35,8 +34,8 @@ const ProfileLayout: FC<Props> = observer(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={styles.wrap}>
|
<Container className={styles.wrap}>
|
||||||
<div className={styles.left}>
|
<div className={styles.grid}>
|
||||||
<Sticky>
|
<div className={styles.stamp}>
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<ProfilePageLeft profile={profile} username={username} />
|
<ProfilePageLeft profile={profile} username={username} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,10 +49,8 @@ const ProfileLayout: FC<Props> = observer(
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<ProfilePageStats />
|
<ProfilePageStats />
|
||||||
</div>
|
</div>
|
||||||
</Sticky>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.grid}>
|
|
||||||
<FlowGrid nodes={nodes} user={user} onChangeCellView={console.log} />
|
<FlowGrid nodes={nodes} user={user} onChangeCellView={console.log} />
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -2,15 +2,13 @@
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: $cell auto;
|
grid-template-columns: auto;
|
||||||
grid-column-gap: $gap;
|
grid-column-gap: $gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
@include flow_grid;
|
@include flow_grid;
|
||||||
}
|
}
|
||||||
.left {
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
margin-bottom: $gap;
|
margin-bottom: $gap;
|
||||||
|
@ -21,3 +19,7 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: $gap * 2 $gap;
|
padding: $gap * 2 $gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
grid-row-end: span 2;
|
||||||
|
}
|
||||||
|
|
|
@ -51,7 +51,6 @@ import {
|
||||||
import { OAUTH_EVENT_TYPES, Unwrap } from '../types';
|
import { OAUTH_EVENT_TYPES, Unwrap } from '../types';
|
||||||
import { REHYDRATE, RehydrateAction } from 'redux-persist';
|
import { REHYDRATE, RehydrateAction } from 'redux-persist';
|
||||||
import { ERRORS } from '~/constants/errors';
|
import { ERRORS } from '~/constants/errors';
|
||||||
import { messagesSet } from '~/redux/messages/actions';
|
|
||||||
import { SagaIterator } from 'redux-saga';
|
import { SagaIterator } from 'redux-saga';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { getMOBXStore } from '~/store';
|
import { getMOBXStore } from '~/store';
|
||||||
|
@ -144,7 +143,6 @@ function* loadProfile({ username }: ReturnType<typeof authLoadProfile>): SagaIte
|
||||||
});
|
});
|
||||||
|
|
||||||
yield put(authSetProfile({ is_loading: false, user }));
|
yield put(authSetProfile({ is_loading: false, user }));
|
||||||
yield put(messagesSet({ messages: [] }));
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { IMessage } from '~/redux/types';
|
|
||||||
import { MESSAGES_ACTIONS } from '~/redux/messages/constants';
|
|
||||||
import { IMessagesState } from '~/redux/messages';
|
|
||||||
|
|
||||||
export const messagesGetMessages = (username: string) => ({
|
|
||||||
type: MESSAGES_ACTIONS.GET_MESSAGES,
|
|
||||||
username,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const messagesRefreshMessages = () => ({
|
|
||||||
type: MESSAGES_ACTIONS.REFRESH_MESSAGES,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const messagesLoadMoreMessages = (username: string) => ({
|
|
||||||
type: MESSAGES_ACTIONS.LOAD_MORE,
|
|
||||||
username,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const messagesSendMessage = (message: Partial<IMessage>, onSuccess) => ({
|
|
||||||
type: MESSAGES_ACTIONS.SEND_MESSAGE,
|
|
||||||
message,
|
|
||||||
onSuccess,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const messagesDeleteMessage = (id: IMessage['id'], is_locked: boolean) => ({
|
|
||||||
type: MESSAGES_ACTIONS.DELETE_MESSAGE,
|
|
||||||
id,
|
|
||||||
is_locked,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const messagesSet = (messages: Partial<IMessagesState>) => ({
|
|
||||||
type: MESSAGES_ACTIONS.SET_MESSAGES,
|
|
||||||
messages,
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
const p = 'MESSAGES.';
|
|
||||||
|
|
||||||
export const MESSAGES_ACTIONS = {
|
|
||||||
SET_MESSAGES: `${p}SET_MESSAGES`,
|
|
||||||
GET_MESSAGES: `${p}GET_MESSAGES`,
|
|
||||||
REFRESH_MESSAGES: `${p}REFRESH_MESSAGES`,
|
|
||||||
LOAD_MORE: `${p}LOAD_MORE`,
|
|
||||||
SEND_MESSAGE: `${p}SEND_MESSAGE`,
|
|
||||||
DELETE_MESSAGE: `${p}DELETE_MESSAGE`,
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { MESSAGES_ACTIONS } from '~/redux/messages/constants';
|
|
||||||
import { IMessagesState } from '~/redux/messages';
|
|
||||||
import { messagesSet } from '~/redux/messages/actions';
|
|
||||||
|
|
||||||
const setMessages = (
|
|
||||||
state: IMessagesState,
|
|
||||||
{ messages }: ReturnType<typeof messagesSet>
|
|
||||||
): IMessagesState => ({
|
|
||||||
...state,
|
|
||||||
...messages,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const MESSAGE_HANDLERS = {
|
|
||||||
[MESSAGES_ACTIONS.SET_MESSAGES]: setMessages,
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { createReducer } from '~/utils/reducer';
|
|
||||||
import { MESSAGE_HANDLERS } from '~/redux/messages/handlers';
|
|
||||||
import { IMessage } from '~/redux/types';
|
|
||||||
|
|
||||||
export interface IMessagesState {
|
|
||||||
is_loading_messages: boolean;
|
|
||||||
is_sending_messages: boolean;
|
|
||||||
messages: IMessage[];
|
|
||||||
error: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const INITIAL_STATE: IMessagesState = {
|
|
||||||
is_loading_messages: true,
|
|
||||||
is_sending_messages: false,
|
|
||||||
error: '',
|
|
||||||
messages: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createReducer(INITIAL_STATE, MESSAGE_HANDLERS);
|
|
|
@ -1,213 +0,0 @@
|
||||||
import { authSetUpdates } from '~/redux/auth/actions';
|
|
||||||
import { call, put, select, takeLatest } from 'redux-saga/effects';
|
|
||||||
import {
|
|
||||||
selectAuthProfile,
|
|
||||||
selectAuthProfileUsername,
|
|
||||||
selectAuthUpdates,
|
|
||||||
} from '~/redux/auth/selectors';
|
|
||||||
import { apiDeleteMessage, apiGetUserMessages, apiSendMessage } from '~/redux/messages/api';
|
|
||||||
import { ERRORS } from '~/constants/errors';
|
|
||||||
import { IMessageNotification, Unwrap } from '~/redux/types';
|
|
||||||
import {
|
|
||||||
messagesDeleteMessage,
|
|
||||||
messagesGetMessages,
|
|
||||||
messagesRefreshMessages,
|
|
||||||
messagesSendMessage,
|
|
||||||
messagesSet,
|
|
||||||
} from '~/redux/messages/actions';
|
|
||||||
import { MESSAGES_ACTIONS } from '~/redux/messages/constants';
|
|
||||||
import { selectMessages } from '~/redux/messages/selectors';
|
|
||||||
import { sortCreatedAtDesc } from '~/utils/date';
|
|
||||||
import { getErrorMessage } from '~/utils/errors/getErrorMessage';
|
|
||||||
|
|
||||||
function* getMessages({ username }: ReturnType<typeof messagesGetMessages>) {
|
|
||||||
try {
|
|
||||||
const { messages }: ReturnType<typeof selectMessages> = yield select(selectMessages);
|
|
||||||
|
|
||||||
yield put(
|
|
||||||
messagesSet({
|
|
||||||
is_loading_messages: true,
|
|
||||||
messages:
|
|
||||||
messages &&
|
|
||||||
messages.length > 0 &&
|
|
||||||
(messages[0].to.username === username || messages[0].from.username === username)
|
|
||||||
? messages
|
|
||||||
: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const data: Unwrap<typeof apiGetUserMessages> = yield call(apiGetUserMessages, {
|
|
||||||
username,
|
|
||||||
});
|
|
||||||
|
|
||||||
yield put(messagesSet({ is_loading_messages: false, messages: data.messages }));
|
|
||||||
|
|
||||||
const { notifications }: ReturnType<typeof selectAuthUpdates> = yield select(selectAuthUpdates);
|
|
||||||
|
|
||||||
// clear viewed message from notifcation list
|
|
||||||
const filtered = notifications.filter(
|
|
||||||
notification =>
|
|
||||||
notification.type !== 'message' ||
|
|
||||||
(notification as IMessageNotification)?.content?.from?.username !== username
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filtered.length !== notifications.length) {
|
|
||||||
yield put(authSetUpdates({ notifications: filtered }));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
messagesSet({
|
|
||||||
error: getErrorMessage(error) || ERRORS.EMPTY_RESPONSE,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
yield put(
|
|
||||||
messagesSet({
|
|
||||||
is_loading_messages: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* sendMessage({ message, onSuccess }: ReturnType<typeof messagesSendMessage>) {
|
|
||||||
try {
|
|
||||||
const username: ReturnType<typeof selectAuthProfileUsername> = yield select(
|
|
||||||
selectAuthProfileUsername
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!username) return;
|
|
||||||
|
|
||||||
yield put(messagesSet({ is_sending_messages: true, error: '' }));
|
|
||||||
|
|
||||||
const data: Unwrap<typeof apiSendMessage> = yield call(apiSendMessage, {
|
|
||||||
username,
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { user }: ReturnType<typeof selectAuthProfile> = yield select(selectAuthProfile);
|
|
||||||
|
|
||||||
if (user?.username !== username) {
|
|
||||||
return yield put(messagesSet({ is_sending_messages: false }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { messages }: ReturnType<typeof selectMessages> = yield select(selectMessages);
|
|
||||||
|
|
||||||
if (message.id && message.id > 0) {
|
|
||||||
// modified
|
|
||||||
yield put(
|
|
||||||
messagesSet({
|
|
||||||
is_sending_messages: false,
|
|
||||||
messages: messages.map(item => (item.id === message.id ? data.message : item)),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// created
|
|
||||||
yield put(
|
|
||||||
messagesSet({
|
|
||||||
is_sending_messages: false,
|
|
||||||
messages: [data.message, ...messages],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuccess();
|
|
||||||
} catch (error) {
|
|
||||||
messagesSet({
|
|
||||||
error: getErrorMessage(error) || ERRORS.EMPTY_RESPONSE,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
yield put(
|
|
||||||
messagesSet({
|
|
||||||
is_loading_messages: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* deleteMessage({ id, is_locked }: ReturnType<typeof messagesDeleteMessage>) {
|
|
||||||
try {
|
|
||||||
const username: ReturnType<typeof selectAuthProfileUsername> = yield select(
|
|
||||||
selectAuthProfileUsername
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!username) return;
|
|
||||||
|
|
||||||
yield put(messagesSet({ is_sending_messages: true, error: '' }));
|
|
||||||
|
|
||||||
const data: Unwrap<typeof apiDeleteMessage> = yield call(apiDeleteMessage, {
|
|
||||||
username,
|
|
||||||
id,
|
|
||||||
is_locked,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUsername: ReturnType<typeof selectAuthProfileUsername> = yield select(
|
|
||||||
selectAuthProfileUsername
|
|
||||||
);
|
|
||||||
|
|
||||||
if (currentUsername !== username) {
|
|
||||||
return yield put(messagesSet({ is_sending_messages: false }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { messages }: ReturnType<typeof selectMessages> = yield select(selectMessages);
|
|
||||||
|
|
||||||
yield put(
|
|
||||||
messagesSet({
|
|
||||||
is_sending_messages: false,
|
|
||||||
messages: messages.map(item => (item.id === id ? data.message : item)),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
messagesSet({
|
|
||||||
error: getErrorMessage(error) || ERRORS.EMPTY_RESPONSE,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
yield put(
|
|
||||||
messagesSet({
|
|
||||||
is_loading_messages: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* refreshMessages({}: ReturnType<typeof messagesRefreshMessages>) {
|
|
||||||
try {
|
|
||||||
const username: ReturnType<typeof selectAuthProfileUsername> = yield select(
|
|
||||||
selectAuthProfileUsername
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!username) return;
|
|
||||||
|
|
||||||
const { messages }: ReturnType<typeof selectMessages> = yield select(selectMessages);
|
|
||||||
|
|
||||||
yield put(messagesSet({ is_loading_messages: true }));
|
|
||||||
|
|
||||||
const after = messages.length > 0 ? messages[0].created_at : undefined;
|
|
||||||
|
|
||||||
const data: Unwrap<typeof apiGetUserMessages> = yield call(apiGetUserMessages, {
|
|
||||||
username,
|
|
||||||
after,
|
|
||||||
});
|
|
||||||
|
|
||||||
yield put(messagesSet({ is_loading_messages: false }));
|
|
||||||
|
|
||||||
if (!data.messages || !data.messages.length) return;
|
|
||||||
|
|
||||||
const newMessages = [...data.messages, ...messages].sort(sortCreatedAtDesc);
|
|
||||||
yield put(messagesSet({ messages: newMessages }));
|
|
||||||
} catch (error) {
|
|
||||||
messagesSet({
|
|
||||||
error: getErrorMessage(error) || ERRORS.EMPTY_RESPONSE,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
yield put(
|
|
||||||
messagesSet({
|
|
||||||
is_loading_messages: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function*() {
|
|
||||||
yield takeLatest(MESSAGES_ACTIONS.GET_MESSAGES, getMessages);
|
|
||||||
yield takeLatest(MESSAGES_ACTIONS.SEND_MESSAGE, sendMessage);
|
|
||||||
yield takeLatest(MESSAGES_ACTIONS.DELETE_MESSAGE, deleteMessage);
|
|
||||||
yield takeLatest(MESSAGES_ACTIONS.REFRESH_MESSAGES, refreshMessages);
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { IState } from '~/redux/store';
|
|
||||||
|
|
||||||
export const selectMessages = (state: IState) => state.messages;
|
|
|
@ -13,9 +13,6 @@ import { IAuthState } from '~/redux/auth/types';
|
||||||
|
|
||||||
import { authLogout, authOpenProfile, gotAuthPostMessage } from './auth/actions';
|
import { authLogout, authOpenProfile, gotAuthPostMessage } from './auth/actions';
|
||||||
|
|
||||||
import messages, { IMessagesState } from './messages';
|
|
||||||
import messagesSaga from './messages/sagas';
|
|
||||||
|
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
import { assocPath } from 'ramda';
|
import { assocPath } from 'ramda';
|
||||||
|
@ -26,16 +23,9 @@ const authPersistConfig: PersistConfig = {
|
||||||
storage,
|
storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
const playerPersistConfig: PersistConfig = {
|
|
||||||
key: 'player',
|
|
||||||
whitelist: ['youtubes'],
|
|
||||||
storage,
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IState {
|
export interface IState {
|
||||||
auth: IAuthState;
|
auth: IAuthState;
|
||||||
router: RouterState;
|
router: RouterState;
|
||||||
messages: IMessagesState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sagaMiddleware = createSagaMiddleware();
|
export const sagaMiddleware = createSagaMiddleware();
|
||||||
|
@ -52,7 +42,6 @@ export const store = createStore(
|
||||||
combineReducers<IState>({
|
combineReducers<IState>({
|
||||||
auth: persistReducer(authPersistConfig, auth),
|
auth: persistReducer(authPersistConfig, auth),
|
||||||
router: connectRouter(history),
|
router: connectRouter(history),
|
||||||
messages,
|
|
||||||
}),
|
}),
|
||||||
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
|
composeEnhancers(applyMiddleware(routerMiddleware(history), sagaMiddleware))
|
||||||
);
|
);
|
||||||
|
@ -62,7 +51,6 @@ export function configureStore(): {
|
||||||
persistor: Persistor;
|
persistor: Persistor;
|
||||||
} {
|
} {
|
||||||
sagaMiddleware.run(authSaga);
|
sagaMiddleware.run(authSaga);
|
||||||
sagaMiddleware.run(messagesSaga);
|
|
||||||
|
|
||||||
const persistor = persistStore(store);
|
const persistor = persistStore(store);
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ $text_sign: 22px;
|
||||||
$button_bg_color: $red_gradient;
|
$button_bg_color: $red_gradient;
|
||||||
|
|
||||||
$inset_bg: linear-gradient(135deg, darken($content_bg, 2%), $content_bg);
|
$inset_bg: linear-gradient(135deg, darken($content_bg, 2%), $content_bg);
|
||||||
$comment_bg: linear-gradient(135deg, $content_bg, lighten($content_bg, 2%));
|
$comment_bg: lighten($content_bg, 2%);
|
||||||
|
|
||||||
$panel_bg: transparent;
|
$panel_bg: transparent;
|
||||||
$node_bg: darken($content_bg, 2%);
|
$node_bg: darken($content_bg, 2%);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue