mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 21:06:42 +07:00
splitting comment text by blocks
This commit is contained in:
parent
d74c9ee739
commit
93afa626db
8 changed files with 206 additions and 158 deletions
20
src/components/comment/CommentTextBlock/index.tsx
Normal file
20
src/components/comment/CommentTextBlock/index.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import React, { FC } from 'react';
|
||||
import { ICommentBlock } from '~/constants/comment';
|
||||
import styles from './styles.scss';
|
||||
|
||||
interface IProps {
|
||||
block: ICommentBlock;
|
||||
}
|
||||
|
||||
const CommentTextBlock: FC<IProps> = ({ block }) => {
|
||||
return (
|
||||
<div
|
||||
className={styles.text}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `<p>${block.content}</p>`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { CommentTextBlock };
|
14
src/components/comment/CommentTextBlock/styles.scss
Normal file
14
src/components/comment/CommentTextBlock/styles.scss
Normal file
|
@ -0,0 +1,14 @@
|
|||
.text {
|
||||
padding: $gap;
|
||||
font-weight: 300;
|
||||
font: $font_16_medium;
|
||||
line-height: 20px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
color: #cccccc;
|
||||
word-break: break-word;
|
||||
|
||||
b {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC, useMemo, memo } from 'react';
|
||||
import React, { FC, useMemo, memo, createElement } from 'react';
|
||||
import { IComment, IFile } from '~/redux/types';
|
||||
import path from 'ramda/es/path';
|
||||
import { formatCommentText, getURL, getPrettyDate } from '~/utils/dom';
|
||||
|
@ -11,6 +11,7 @@ import reduce from 'ramda/es/reduce';
|
|||
import { AudioPlayer } from '~/components/media/AudioPlayer';
|
||||
import classnames from 'classnames';
|
||||
import { PRESETS } from '~/constants/urls';
|
||||
import { COMMENT_BLOCK_RENDERERS } from '~/constants/comment';
|
||||
|
||||
interface IProps {
|
||||
comment: IComment;
|
||||
|
@ -31,12 +32,11 @@ const CommentContent: FC<IProps> = memo(({ comment }) => {
|
|||
<>
|
||||
{comment.text && (
|
||||
<div className={styles.block}>
|
||||
<Group
|
||||
className={styles.text}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: formatCommentText(path(['user', 'username'], comment), comment.text),
|
||||
}}
|
||||
/>
|
||||
{formatCommentText(path(['user', 'username'], comment), comment.text).map(
|
||||
(block, key) =>
|
||||
COMMENT_BLOCK_RENDERERS[block.type] &&
|
||||
createElement(COMMENT_BLOCK_RENDERERS[block.type], { block, key })
|
||||
)}
|
||||
|
||||
<div className={styles.date}>{getPrettyDate(comment.created_at)}</div>
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
position: relative;
|
||||
padding-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
|
||||
&:first-child {
|
||||
border-top-right-radius: $radius;
|
||||
|
@ -36,21 +37,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: $gap;
|
||||
font-weight: 300;
|
||||
font: $font_16_medium;
|
||||
line-height: 20px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
color: #cccccc;
|
||||
word-break: break-word;
|
||||
|
||||
b {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
|
|
@ -1,56 +1,51 @@
|
|||
import React, { FC, useState, useEffect, useCallback } from "react";
|
||||
import styles from "./styles.scss";
|
||||
import { connect } from "react-redux";
|
||||
import classNames from "classnames";
|
||||
import { selectAuthUser, selectAuthProfile } from "~/redux/auth/selectors";
|
||||
import { Textarea } from "~/components/input/Textarea";
|
||||
import { Button } from "~/components/input/Button";
|
||||
import { Group } from "~/components/containers/Group";
|
||||
import { Filler } from "~/components/containers/Filler";
|
||||
import { TextInput } from "~/components/input/TextInput";
|
||||
import { InputText } from "~/components/input/InputText";
|
||||
import reject from "ramda/es/reject";
|
||||
import * as AUTH_ACTIONS from "~/redux/auth/actions";
|
||||
import { ERROR_LITERAL } from "~/constants/errors";
|
||||
import React, { FC, useState, useEffect, useCallback } from 'react';
|
||||
import styles from './styles.scss';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { selectAuthUser, selectAuthProfile } from '~/redux/auth/selectors';
|
||||
import { Textarea } from '~/components/input/Textarea';
|
||||
import { Button } from '~/components/input/Button';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { TextInput } from '~/components/input/TextInput';
|
||||
import { InputText } from '~/components/input/InputText';
|
||||
import reject from 'ramda/es/reject';
|
||||
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||
import { ERROR_LITERAL } from '~/constants/errors';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: selectAuthUser(state),
|
||||
profile: selectAuthProfile(state)
|
||||
profile: selectAuthProfile(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
authPatchUser: AUTH_ACTIONS.authPatchUser,
|
||||
authSetProfile: AUTH_ACTIONS.authSetProfile
|
||||
authSetProfile: AUTH_ACTIONS.authSetProfile,
|
||||
};
|
||||
|
||||
type IProps = ReturnType<typeof mapStateToProps> &
|
||||
typeof mapDispatchToProps & {};
|
||||
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||
|
||||
const ProfileSettingsUnconnected: FC<IProps> = ({
|
||||
user,
|
||||
profile: { patch_errors },
|
||||
authPatchUser,
|
||||
authSetProfile
|
||||
authSetProfile,
|
||||
}) => {
|
||||
const [password, setPassword] = useState("");
|
||||
const [new_password, setNewPassword] = useState("");
|
||||
const [password, setPassword] = useState('');
|
||||
const [new_password, setNewPassword] = useState('');
|
||||
|
||||
const [data, setData] = useState(user);
|
||||
|
||||
const setDescription = useCallback(
|
||||
description => setData({ ...data, description }),
|
||||
[data, setData]
|
||||
);
|
||||
|
||||
const setEmail = useCallback(email => setData({ ...data, email }), [
|
||||
const setDescription = useCallback(description => setData({ ...data, description }), [
|
||||
data,
|
||||
setData
|
||||
setData,
|
||||
]);
|
||||
|
||||
const setUsername = useCallback(username => setData({ ...data, username }), [
|
||||
data,
|
||||
setData
|
||||
]);
|
||||
const setEmail = useCallback(email => setData({ ...data, email }), [data, setData]);
|
||||
|
||||
const setUsername = useCallback(username => setData({ ...data, username }), [data, setData]);
|
||||
|
||||
const setFullname = useCallback(fullname => setData({ ...data, fullname }), [data, setData]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
event => {
|
||||
|
@ -58,10 +53,11 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
|||
|
||||
const fields = reject(el => !el)({
|
||||
email: data.email !== user.email && data.email,
|
||||
fullname: data.fullname !== user.fullname && data.fullname,
|
||||
username: data.username !== user.username && data.username,
|
||||
password: password.length > 0 && password,
|
||||
new_password: new_password.length > 0 && new_password,
|
||||
description: data.description !== user.description && data.description
|
||||
description: data.description !== user.description && data.description,
|
||||
});
|
||||
|
||||
if (Object.values(fields).length === 0) return;
|
||||
|
@ -78,15 +74,18 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
|||
return (
|
||||
<form className={styles.wrap} onSubmit={onSubmit}>
|
||||
<Group>
|
||||
<Textarea
|
||||
value={data.description}
|
||||
handler={setDescription}
|
||||
title="Описание"
|
||||
<InputText
|
||||
value={data.fullname}
|
||||
handler={setFullname}
|
||||
title="Полное имя"
|
||||
error={patch_errors.fullname && ERROR_LITERAL[patch_errors.fullname]}
|
||||
/>
|
||||
|
||||
<Textarea value={data.description} handler={setDescription} title="Описание" />
|
||||
|
||||
<div className={styles.small}>
|
||||
Описание будет видно на странице профиля. Здесь работают те же правила
|
||||
оформления, что и в комментариях.
|
||||
Описание будет видно на странице профиля. Здесь работают те же правила оформления, что и в
|
||||
комментариях.
|
||||
</div>
|
||||
|
||||
<Group className={styles.pad}>
|
||||
|
@ -94,9 +93,7 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
|||
value={data.username}
|
||||
handler={setUsername}
|
||||
title="Логин"
|
||||
error={
|
||||
patch_errors.username && ERROR_LITERAL[patch_errors.username]
|
||||
}
|
||||
error={patch_errors.username && ERROR_LITERAL[patch_errors.username]}
|
||||
/>
|
||||
|
||||
<InputText
|
||||
|
@ -111,10 +108,7 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
|||
handler={setNewPassword}
|
||||
title="Новый пароль"
|
||||
type="password"
|
||||
error={
|
||||
patch_errors.new_password &&
|
||||
ERROR_LITERAL[patch_errors.new_password]
|
||||
}
|
||||
error={patch_errors.new_password && ERROR_LITERAL[patch_errors.new_password]}
|
||||
/>
|
||||
|
||||
<div />
|
||||
|
@ -124,9 +118,7 @@ const ProfileSettingsUnconnected: FC<IProps> = ({
|
|||
handler={setPassword}
|
||||
title="Старый пароль"
|
||||
type="password"
|
||||
error={
|
||||
patch_errors.password && ERROR_LITERAL[patch_errors.password]
|
||||
}
|
||||
error={patch_errors.password && ERROR_LITERAL[patch_errors.password]}
|
||||
/>
|
||||
|
||||
<div className={styles.small}>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue