diff --git a/src/components/button/style.css b/src/components/button/style.css
index 0efc8a896..3cde0bc51 100644
--- a/src/components/button/style.css
+++ b/src/components/button/style.css
@@ -34,6 +34,7 @@
.Button_style_text {
padding: 0;
border: none;
+ color: var(--primary);
&:hover {
color: var(--primary);
diff --git a/src/components/comment-form/index.js b/src/components/comment-form/index.js
new file mode 100644
index 000000000..a69f23f59
--- /dev/null
+++ b/src/components/comment-form/index.js
@@ -0,0 +1,41 @@
+import { memo, useState } from 'react';
+import { cn as bem } from '@bem-react/classname';
+import Button from '../button';
+import PropTypes from 'prop-types';
+import useTranslate from '../../hooks/use-translate';
+import './style.css';
+
+function CommentForm({ onSubmit, onCancel, title, formRef }) {
+ const cn = bem('CommentForm');
+ const { t } = useTranslate();
+ const [text, setText] = useState('');
+
+ const handleSubmit = e => {
+ e.preventDefault();
+ if (text.trim()) {
+ onSubmit(text);
+ setText('');
+ }
+ };
+
+ return (
+
+ );
+}
+
+CommentForm.propTypes = {
+ onSubmit: PropTypes.func.isRequired,
+ onCancel: PropTypes.func,
+ title: PropTypes.string,
+};
+
+export default memo(CommentForm);
diff --git a/src/components/comment-form/style.css b/src/components/comment-form/style.css
new file mode 100644
index 000000000..428cab6ea
--- /dev/null
+++ b/src/components/comment-form/style.css
@@ -0,0 +1,26 @@
+.CommentForm {
+ padding: 8px 0 24px 0;
+}
+
+.CommentForm-text {
+ width: 100%;
+ min-height: 88px;
+ padding: 8px 12px;
+ border-radius: 4px;
+ font-family: var(--font-family);
+ font-size: 14px;
+ border: 1px solid var(--filter-border);
+ resize: none;
+ outline: 0px none transparent;
+}
+
+.CommentForm-title {
+ padding-bottom: 16px;
+ font-weight: 700;
+}
+
+.CommentForm-buttons {
+ display: flex;
+ gap: 16px;
+ padding-top: 16px;
+}
diff --git a/src/components/comments-item/index.js b/src/components/comments-item/index.js
new file mode 100644
index 000000000..b4dcf9bc5
--- /dev/null
+++ b/src/components/comments-item/index.js
@@ -0,0 +1,51 @@
+import { memo } from 'react';
+import { cn as bem } from '@bem-react/classname';
+import PropTypes from 'prop-types';
+import Button from '../button';
+import DateFormat from '../../utils/date-format';
+import useTranslate from '../../hooks/use-translate';
+import './style.css';
+
+function CommentItem({ comment, onReplyClick, currentUserId, children, depth = 0 }) {
+ const cn = bem('CommentItem');
+ const maxDepth = 3;
+
+ // const indent = Math.min(depth, 1) * 40;
+ const indent = Math.min(depth, maxDepth) * 40;
+ const { t, lang } = useTranslate();
+ const isCurrentUser = currentUserId === comment.author?._id;
+
+ return (
+
+
+
+
+ {comment.author?.profile?.name}
+
+ {DateFormat(comment.dateCreate, lang)}
+
+
{comment.text}
+ {!comment.isDeleted && (
+
+
+ )}
+
+
{children}
+
+ );
+}
+
+CommentItem.propTypes = {
+ comment: PropTypes.object.isRequired,
+ onReplyClick: PropTypes.func,
+ isReplyFormVisible: PropTypes.bool,
+ children: PropTypes.node,
+ depth: PropTypes.number,
+};
+
+export default memo(CommentItem);
diff --git a/src/components/comments-item/style.css b/src/components/comments-item/style.css
new file mode 100644
index 000000000..6ea7e1a4d
--- /dev/null
+++ b/src/components/comments-item/style.css
@@ -0,0 +1,36 @@
+.CommentItem-wrap {
+ padding-bottom: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.CommentItem-header {
+ display: flex;
+ gap: 12px;
+ line-height: 18px;
+ font-size: 12px;
+}
+
+.CommentItem-author {
+ font-weight: 700;
+}
+.CommentItem-comment {
+ font-size: 14px;
+ line-height: 20px;
+ word-wrap: break-word;
+ white-space: pre-wrap;
+}
+
+.CommentItem-date {
+ color: #666666;
+}
+
+.CommentItem-button button {
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.CommentItem-author_me {
+ color: #4b5563;
+}
diff --git a/src/components/comments-layout/index.js b/src/components/comments-layout/index.js
new file mode 100644
index 000000000..2e97bbdcb
--- /dev/null
+++ b/src/components/comments-layout/index.js
@@ -0,0 +1,21 @@
+import { memo } from 'react';
+import PropTypes from 'prop-types';
+import { cn as bem } from '@bem-react/classname';
+import './style.css';
+
+function CommentsLayout({ head, children }) {
+ const cn = bem('CommentsLayout');
+
+ return (
+
+ );
+}
+
+CommentsLayout.propTypes = {
+ children: PropTypes.node,
+};
+
+export default memo(CommentsLayout);
diff --git a/src/components/comments-layout/style.css b/src/components/comments-layout/style.css
new file mode 100644
index 000000000..af00e2e14
--- /dev/null
+++ b/src/components/comments-layout/style.css
@@ -0,0 +1,6 @@
+.CommentsLayout-head {
+ font-family: var(--second-font-family);
+ font-size: 24px;
+ font-weight: bold;
+ padding: 16px 0 24px 0;
+}
diff --git a/src/components/comments-link/index.js b/src/components/comments-link/index.js
new file mode 100644
index 000000000..bb0ea4c36
--- /dev/null
+++ b/src/components/comments-link/index.js
@@ -0,0 +1,20 @@
+import { memo } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+import useTranslate from '../../hooks/use-translate';
+import './style.css';
+
+function CommentsLink() {
+ const location = useLocation();
+ const { t } = useTranslate();
+
+ return (
+
+
+ {t('comments.sighIn')}
+
+ {t('comments.beAble')}
+
+ );
+}
+
+export default memo(CommentsLink);
diff --git a/src/components/comments-link/style.css b/src/components/comments-link/style.css
new file mode 100644
index 000000000..6da57d86c
--- /dev/null
+++ b/src/components/comments-link/style.css
@@ -0,0 +1,3 @@
+.comments-link {
+ padding-bottom: 24px;
+}
diff --git a/src/components/comments-list/index.js b/src/components/comments-list/index.js
new file mode 100644
index 000000000..d6c162d4d
--- /dev/null
+++ b/src/components/comments-list/index.js
@@ -0,0 +1,62 @@
+import { memo, useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import CommentItem from '../../components/comments-item';
+import CommentForm from '../../components/comment-form';
+import CommentsLink from '../comments-link';
+import useTranslate from '../../hooks/use-translate';
+function CommentsList({
+ comment,
+ children,
+ replyFormId,
+ onReplyClick,
+ onSubmitReply,
+ onCancelReply,
+ isAuth,
+ depth = 0,
+ userId,
+}) {
+ const { t } = useTranslate();
+ const formRef = useRef(null);
+
+ useEffect(() => {
+ if (replyFormId === comment._id && formRef.current) {
+ formRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+ }, [replyFormId, comment._id]);
+
+ return (
+
+ {replyFormId === comment._id &&
+ (isAuth ? (
+ onSubmitReply(text, comment._id)}
+ onCancel={onCancelReply}
+ formRef={formRef}
+ />
+ ) : (
+
+ ))}
+ {children}
+
+ );
+}
+
+CommentsList.propTypes = {
+ comment: PropTypes.object.isRequired,
+ children: PropTypes.node,
+ replyFormId: PropTypes.string,
+ onReplyClick: PropTypes.func,
+ onSubmitReply: PropTypes.func,
+ onCancelReply: PropTypes.func,
+ isAuth: PropTypes.bool,
+ depth: PropTypes.number,
+};
+
+export default memo(CommentsList);
diff --git a/src/config.js b/src/config.js
index 67c72f734..1e6001480 100644
--- a/src/config.js
+++ b/src/config.js
@@ -18,6 +18,9 @@ const config = {
api: {
baseUrl: '',
},
+ i18n: {
+ lang: 'ru',
+ },
};
export default config;
diff --git a/src/containers/comments/index.js b/src/containers/comments/index.js
new file mode 100644
index 000000000..c7114f7e6
--- /dev/null
+++ b/src/containers/comments/index.js
@@ -0,0 +1,134 @@
+import { memo, useEffect, useState, useCallback, useMemo } from 'react';
+import { useDispatch, useSelector as useSelectorRedux } from 'react-redux';
+import CommentsLayout from '../../components/comments-layout';
+import CommentForm from '../../components/comment-form';
+import CommentsList from '../../components/comments-list';
+import listToTree from '../../utils/list-to-tree';
+import commentActions from '../../store-redux/comment/action';
+import CommentsLink from '../../components/comments-link';
+import Spinner from '../../components/spinner';
+import useSelector from '../../hooks/use-selector';
+import useTranslate from '../../hooks/use-translate';
+
+function Comments({ articleId, parentType = 'article' }) {
+ const { t } = useTranslate();
+ const dispatch = useDispatch();
+ const [replyFormId, setReplyFormId] = useState(null);
+
+ const items = useSelectorRedux(state => state.comment.data.items);
+ const waiting = useSelectorRedux(state => state.comment.waiting);
+ const raw = useMemo(() => items || [], [items]);
+ const comments = useMemo(() => listToTree(raw, '_id', parentType), [raw, parentType]);
+ const count = raw.length;
+ const maxDepth = 3;
+ // const { comments, waiting, count } = useSelectorRedux(state => {
+ // const raw = state.comment.data.items || [];
+ // return {
+ // comments: listToTree(raw),
+ // waiting: state.comment.waiting,
+ // count: raw.length,
+ // };
+ // });
+
+ const isAuth = useSelector(state => state.session.exists);
+ const userId = useSelector(state => state.session.user?._id);
+
+ useEffect(() => {
+ dispatch(commentActions.load(articleId));
+ }, [articleId]);
+
+ const handleReplyClick = useCallback(id => {
+ setReplyFormId(prev => (prev === id ? null : id)); //закрыть, если уже открыта
+ }, []);
+
+ const handleSubmitReply = useCallback(
+ (text, parentId) => {
+ dispatch(commentActions.addComment(text, parentId, 'comment'));
+ setReplyFormId(null); //закрыть форму, после отправить
+ },
+ [dispatch],
+ );
+
+ const handleSubmitNew = useCallback(
+ text => {
+ dispatch(commentActions.addComment(text, articleId, 'article'));
+ },
+ [dispatch, articleId],
+ );
+
+ const renderCommentsTree = (items, depth = 0) => {
+ const flat = [];
+
+ const traverse = (nodes, currentDepth) => {
+ for (const comment of nodes) {
+ const limitedDepth = Math.min(currentDepth, maxDepth);
+
+ flat.push(
+
setReplyFormId(null)}
+ isAuth={isAuth}
+ depth={limitedDepth}
+ userId={userId}
+ />,
+ );
+
+ // если depth меньше maxDepth, то рисуем детей
+ if (currentDepth < maxDepth && comment.children?.length) {
+ traverse(comment.children, currentDepth + 1);
+ }
+
+ // если depth >= maxDepth, то плоско, на этом же уровне
+ if (currentDepth >= maxDepth && comment.children?.length) {
+ for (const child of comment.children) {
+ flat.push(
+ setReplyFormId(null)}
+ isAuth={isAuth}
+ depth={maxDepth}
+ userId={userId}
+ />,
+ );
+
+ //отсальное плоско
+ if (child.children?.length) {
+ traverse(child.children, maxDepth);
+ }
+ }
+ }
+ }
+ };
+
+ traverse(items, depth);
+
+ return flat;
+ };
+ return (
+
+
+ {renderCommentsTree(comments)}
+ {/* новый коммент */}
+ {isAuth && replyFormId === null && (
+ setReplyFormId(null)}
+ />
+ )}
+
+ {!isAuth && }
+
+
+ );
+}
+
+export default memo(Comments);
diff --git a/src/containers/navigation/index.js b/src/containers/navigation/index.js
index 44a2f8a4f..b853b6d90 100644
--- a/src/containers/navigation/index.js
+++ b/src/containers/navigation/index.js
@@ -10,6 +10,8 @@ import modalsActions from '../../store-redux/modals/actions';
function Navigation() {
const store = useStore();
+ const { t, lang } = useTranslate();
+
const dispatch = useDispatch();
const select = useSelector(state => ({
@@ -34,10 +36,9 @@ function Navigation() {
};
// Функция для локализации текстов
- const { t } = useTranslate();
const options = {
- menu: useMemo(() => [{ key: 1, title: t('menu.main'), link: '/' }], [t]),
+ menu: useMemo(() => [{ key: 1, title: t('menu.main'), link: '/' }], [t, lang]),
};
return (
diff --git a/src/containers/top-head/index.js b/src/containers/top-head/index.js
index 179a51cfb..957ca92e1 100644
--- a/src/containers/top-head/index.js
+++ b/src/containers/top-head/index.js
@@ -31,7 +31,13 @@ function TopHead() {
return (
- {select.exists ? {select.user.profile.name} : ''}
+ {select.exists ? (
+
+ {select.user.profile.name}
+
+ ) : (
+ ''
+ )}
{select.exists ? (
) : (
diff --git a/src/global.css b/src/global.css
index e81695d21..15c3bccb3 100644
--- a/src/global.css
+++ b/src/global.css
@@ -1,18 +1,18 @@
@import url('https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700&family=Montserrat+Alternates:wght@400;500;600;700&display=swap');
:root {
- --background: #FFFFFF;
- --second-background: #EFEFEF;
- --primary: #6B4ACB;
- --primary-select: #6B4ACB1A;
- --main-text: #1A2028;
- --second-text: #FFFFFF;
- --overlay: #1A202866;
- --divider: #EFEFEF;
- --delete: #D43B3B;
- --odd-item: #6B4ACB08;
+ --background: #ffffff;
+ --second-background: #efefef;
+ --primary: #6b4acb;
+ --primary-select: #6b4acb1a;
+ --main-text: #1a2028;
+ --second-text: #ffffff;
+ --overlay: #1a202866;
+ --divider: #efefef;
+ --delete: #d43b3b;
+ --odd-item: #6b4acb08;
--close: #878787;
- --filter-border: #D3D3D3;
+ --filter-border: #d3d3d3;
--font-family: 'Golos Text';
--second-font-family: 'Montserrat Alternates';
@@ -43,7 +43,9 @@ button {
background-color: transparent;
}
-h1, h2, h4 {
+h1,
+h2,
+h4 {
margin: 0;
}
diff --git a/src/hooks/use-translate.js b/src/hooks/use-translate.js
index bdcbf1071..4dd8495b6 100644
--- a/src/hooks/use-translate.js
+++ b/src/hooks/use-translate.js
@@ -1,9 +1,24 @@
-import { useCallback, useContext } from 'react';
-import { I18nContext } from '../i18n/context';
+import { useEffect, useState } from 'react';
+import useServices from './use-services';
/**
* Хук возвращает функцию для локализации текстов, код языка и функцию его смены
*/
export default function useTranslate() {
- return useContext(I18nContext);
+ const i18n = useServices().i18n;
+
+ const [lang, setLangState] = useState(i18n.getLang());
+
+ useEffect(() => {
+ const unsubscribe = i18n.subscribe(newLang => {
+ setLangState(newLang);
+ });
+ return unsubscribe;
+ }, [i18n]);
+
+ return {
+ lang,
+ setLang: i18n.setLang.bind(i18n), //чтоб не потерять контекст
+ t: i18n.translate,
+ };
}
diff --git a/src/i18n/context.js b/src/i18n/context.js
deleted file mode 100644
index 3733d9bac..000000000
--- a/src/i18n/context.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { createContext, useMemo, useState } from 'react';
-import translate from './translate';
-
-/**
- * @type {React.Context<{}>}
- */
-export const I18nContext = createContext({});
-
-/**
- * Обертка над провайдером контекста, чтобы управлять изменениями в контексте
- * @param children
- * @return {JSX.Element}
- */
-export function I18nProvider({ children }) {
- const [lang, setLang] = useState('ru');
-
- const i18n = useMemo(
- () => ({
- // Код локали
- lang,
- // Функция для смены локали
- setLang,
- // Функция для локализации текстов с замыканием на код языка
- t: (text, number) => translate(lang, text, number),
- }),
- [lang],
- );
-
- return {children};
-}
diff --git a/src/i18n/index.js b/src/i18n/index.js
new file mode 100644
index 000000000..7fc4342cc
--- /dev/null
+++ b/src/i18n/index.js
@@ -0,0 +1,43 @@
+import translateFn from './translate';
+
+export default class I18nService {
+ constructor(services, config = {}) {
+ this.services = services;
+ this.config = config;
+ this.lang = config.lang || 'ru';
+ this.listeners = [];
+
+ this.services.api.setHeader('Accept-Language', this.lang);
+ }
+
+ getLang() {
+ return this.lang;
+ }
+
+ setLang(newLang) {
+ if (this.lang === newLang) return;
+
+ this.lang = newLang;
+ this.services.api.setHeader('Accept-Language', newLang);
+
+ for (const listener of this.listeners) {
+ listener(this.lang);
+ }
+ }
+
+ translate = (text, plural) => {
+ return translateFn(this.lang, text, plural);
+ };
+
+ /**
+ * Подписка на изменение языка
+ * @returns {Function} — функция отписки
+ */
+ subscribe(callback) {
+ this.listeners.push(callback);
+
+ return () => {
+ this.listeners = this.listeners.filter(func => func !== callback);
+ };
+ }
+}
diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json
index 0ebbcf8f2..7dcfe6979 100644
--- a/src/i18n/translations/en.json
+++ b/src/i18n/translations/en.json
@@ -14,6 +14,10 @@
"other": "articles"
},
"article.add": "Add",
+ "article.madeiIn": "Сountry of origin",
+ "article.category": "Category",
+ "article.year": "Year of release",
+ "article.price": "Price",
"filter.reset": "Reset",
"auth.title": "Sign In",
"auth.login": "Login",
@@ -22,5 +26,13 @@
"auth.login-placeholder": "Enter login",
"auth.password-placeholder": "Enter password",
"session.signIn": "Sign In",
- "session.signOut": "Sign Out"
+ "session.signOut": "Sign Out",
+ "comments.head": "Comments",
+ "comments.new-comment": "New comment",
+ "comments.new-reply": "New reply",
+ "comments.cancel": "Cancel",
+ "comments.send": "Send",
+ "comments.reply": "Reply",
+ "comments.sighIn": "Sign in ",
+ "comments.beAble": "to be able to comment"
}
diff --git a/src/i18n/translations/ru.json b/src/i18n/translations/ru.json
index a18fb845e..2a4e54091 100644
--- a/src/i18n/translations/ru.json
+++ b/src/i18n/translations/ru.json
@@ -16,6 +16,10 @@
"other": "товара"
},
"article.add": "Добавить",
+ "article.madeiIn": "Страна производитель",
+ "article.category": "Категория",
+ "article.year": "Год выпуска",
+ "article.price": "Цена",
"filter.reset": "Сбросить",
"auth.title": "Вход",
"auth.login": "Логин",
@@ -24,5 +28,13 @@
"auth.login-placeholder": "Введите логин",
"auth.password-placeholder": "Введите пароль",
"session.signIn": "Вход",
- "session.signOut": "Выход"
+ "session.signOut": "Выход",
+ "comments.head": "Комментарии",
+ "comments.new-comment": "Новый комментарий",
+ "comments.new-reply": "Новый ответ",
+ "comments.cancel": "Отмена",
+ "comments.send": "Отправить",
+ "comments.reply": "Ответить",
+ "comments.sighIn": "Войдите, ",
+ "comments.beAble": "чтобы иметь возможность комментировать"
}
diff --git a/src/index.js b/src/index.js
index f49db385a..9404d09eb 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,6 @@ import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { ServicesContext } from './context';
-import { I18nProvider } from './i18n/context';
import App from './app';
import Services from './services';
import config from './config';
@@ -16,11 +15,9 @@ const root = createRoot(document.getElementById('root'));
root.render(
-
-
-
-
-
+
+
+
,
);
diff --git a/src/services.js b/src/services.js
index a32b14d57..61ab8eff8 100644
--- a/src/services.js
+++ b/src/services.js
@@ -1,6 +1,7 @@
import APIService from './api';
import Store from './store';
import createStoreRedux from './store-redux';
+import I18nService from './i18n';
class Services {
constructor(config) {
@@ -38,6 +39,13 @@ class Services {
}
return this._redux;
}
+
+ get i18n() {
+ if (!this._i18n) {
+ this._i18n = new I18nService(this, this.config.i18n);
+ }
+ return this._i18n;
+ }
}
export default Services;
diff --git a/src/store-redux/comment/action.js b/src/store-redux/comment/action.js
new file mode 100644
index 000000000..7d244c459
--- /dev/null
+++ b/src/store-redux/comment/action.js
@@ -0,0 +1,41 @@
+export default {
+ /**
+ * Загрузка товара
+ * @param id
+ * @return {Function}
+ */
+ load: id => {
+ return async (dispatch, getState, services) => {
+ // Сброс текущего коммента и установка признака ожидания загрузки
+ dispatch({ type: 'comments/load-start' });
+
+ try {
+ const res = await services.api.request({
+ url: `/api/v1/comments?fields=items(_id,text,dateCreate,author(profile(name)),parent(_id,_type),isDeleted),count&limit=*&search[parent]=${id}`,
+ });
+ // коммент загружен успешно
+ dispatch({ type: 'comments/load-success', payload: { data: res.data.result } });
+ } catch (e) {
+ //Ошибка загрузки
+ dispatch({ type: 'comments/load-error' });
+ }
+ };
+ },
+
+ addComment: (text, id, type) => {
+ return async (dispatch, getState, services) => {
+ dispatch({ type: 'comments/addComment-start' });
+
+ try {
+ const res = await services.api.request({
+ url: `/api/v1/comments`,
+ method: 'POST',
+ body: JSON.stringify({ text, parent: { _id: id, _type: type } }),
+ });
+ dispatch({ type: 'comments/addComment-success', payload: { data: res.data.result } });
+ } catch (e) {
+ dispatch({ type: 'comments/addComment-error' });
+ }
+ };
+ },
+};
diff --git a/src/store-redux/comment/reducer.js b/src/store-redux/comment/reducer.js
new file mode 100644
index 000000000..d240a500d
--- /dev/null
+++ b/src/store-redux/comment/reducer.js
@@ -0,0 +1,38 @@
+// Начальное состояние
+export const initialState = {
+ data: {},
+ waiting: false, // признак ожидания загрузки
+};
+
+// Обработчик действий
+function reducer(state = initialState, action) {
+ switch (action.type) {
+ case 'comments/load-start':
+ return { ...state, data: {}, waiting: true };
+
+ case 'comments/load-success':
+ return { ...state, data: action.payload.data, waiting: false };
+
+ case 'comments/load-error':
+ return { ...state, data: {}, waiting: false };
+
+ case 'comments/addComment-start':
+ return { ...state, waiting: true };
+
+ case 'comments/addComment-success':
+ return {
+ ...state,
+ data: { ...state.data, items: [...state.data.items, action.payload.data] },
+ waiting: false,
+ };
+
+ case 'comments/addComment-error':
+ return { ...state, waiting: false };
+
+ default:
+ // Нет изменений
+ return state;
+ }
+}
+
+export default reducer;
diff --git a/src/store-redux/exports.js b/src/store-redux/exports.js
index 1a0a3d742..bbea750c9 100644
--- a/src/store-redux/exports.js
+++ b/src/store-redux/exports.js
@@ -1,2 +1,3 @@
export { default as article } from './article/reducer';
export { default as modals } from './modals/reducer';
+export { default as comment } from './comment/reducer';
diff --git a/src/utils/date-format.js b/src/utils/date-format.js
new file mode 100644
index 000000000..552d74328
--- /dev/null
+++ b/src/utils/date-format.js
@@ -0,0 +1,15 @@
+export default function DateFormat(toISOString, lang = 'ru') {
+ const select = {
+ minute: 'numeric',
+ hour: 'numeric',
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ };
+
+ const date = new Date(toISOString);
+ const locale = lang === 'en' ? 'en-US' : 'ru-RU';
+ const formatDate = date.toLocaleString(locale, select);
+
+ return lang === 'ru' ? formatDate.replace(' г.', '') : formatDate;
+}
diff --git a/src/utils/list-to-tree/index.js b/src/utils/list-to-tree/index.js
index fb4d20a66..3523b33f1 100644
--- a/src/utils/list-to-tree/index.js
+++ b/src/utils/list-to-tree/index.js
@@ -3,8 +3,9 @@
* @param list {Array} Список объектов с отношением на родителя
* @param [key] {String} Свойство с первичным ключом
* @returns {Array} Корневые узлы
+ * @param rootType {String}
*/
-export default function listToTree(list, key = '_id') {
+export default function listToTree(list, key = '_id', rootType = 'article') {
let trees = {};
let roots = {};
for (const item of list) {
@@ -17,9 +18,8 @@ export default function listToTree(list, key = '_id') {
} else {
trees[item[key]] = Object.assign(trees[item[key]], item);
}
-
// Если элемент имеет родителя, то добавляем его в подчиненные родителя
- if (item.parent?.[key]) {
+ if (item.parent?.[key] && item.parent?.['_type'] === 'comment') {
// Если родителя ещё нет в индексе, то индекс создаётся, ведь _id родителя известен
if (!trees[item.parent[key]]) {
trees[item.parent[key]] = { children: [] };