@@ -114,16 +114,13 @@ function Comments({}) {
{select.exists && activeReplyId === comment.id && (
)}
{!select.exists && activeReplyId === comment.id && (
@@ -146,13 +143,11 @@ function Comments({}) {
{select.exists && !activeReplyId && (
)}
diff --git a/src/containers/locale-select/index.js b/src/containers/locale-select/index.js
index a60df2a9a..b5dcbe29d 100644
--- a/src/containers/locale-select/index.js
+++ b/src/containers/locale-select/index.js
@@ -5,10 +5,10 @@ import useTranslate from '../../hooks/use-translate';
import Select from '../../components/select';
function LocaleSelect() {
- const { lang, setLang } = useTranslate();
+ const { locale, setLocale } = useTranslate();
const options = {
- lang: useMemo(
+ locale: useMemo(
() => [
{ value: 'ru', title: 'Русский' },
{ value: 'en', title: 'English' },
@@ -17,7 +17,7 @@ function LocaleSelect() {
),
};
- return
;
+ return
;
}
export default memo(LocaleSelect);
diff --git a/src/hooks/use-translate.js b/src/hooks/use-translate.js
index bdcbf1071..d1eada44a 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';
-
-/**
- * Хук возвращает функцию для локализации текстов, код языка и функцию его смены
- */
-export default function useTranslate() {
- return useContext(I18nContext);
+// /**
+// * Хук возвращает функцию для локализации текстов, код языка и функцию его смены
+// */
+
+import { useEffect, useState } from 'react';
+import useServices from './use-services';
+
+export default function useServiceTranslate() {
+ const services = useServices();
+ const i18n = services.i18n;
+
+ const [locale, setCurrentLocale] = useState(i18n.getLocale());
+
+ useEffect(() => {
+ const unsubscribe = i18n.subscribe(setCurrentLocale);
+ return () => unsubscribe();
+ }, [i18n]);
+
+ return {
+ locale,
+ setLocale: i18n.setLocale.bind(i18n),
+ t: i18n.translate.bind(i18n),
+ };
}
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..83106c7e6
--- /dev/null
+++ b/src/i18n/index.js
@@ -0,0 +1,55 @@
+class I18nService {
+ /**
+ * @param {Services} services Менеджер сервисов
+ * @param {Object} config Конфигурация: словарь и текущая локаль
+ */
+ constructor(services, config = {}) {
+ this.services = services;
+ this.locale = config.locale;
+ this.dictionary = config.dictionary || {};
+ this.listeners = new Set();
+ }
+
+ getLocale() {
+ return this.locale;
+ }
+
+ setLocale(locale) {
+ if (this.locale !== locale) {
+ this.locale = locale;
+ this.notify(locale);
+ }
+ }
+
+ subscribe(callback) {
+ this.listeners.add(callback);
+ return () => this.listeners.delete(callback);
+ }
+
+ notify(locale) {
+ this.listeners.forEach(cb => cb(locale));
+ }
+
+ /**
+ * Перевод строки с учётом множественного числа (если указано)
+ * @param {string} text Ключ перевода
+ * @param {number} [plural] Число для определения формы множественности
+ * @param {string} [lang=this.locale] Язык перевода
+ * @returns {string} Переведённая строка
+ */
+ translate(text, plural, lang = this.locale) {
+ const translations = this.dictionary[lang];
+ let result = translations && text in translations ? translations[text] : text;
+
+ if (typeof plural !== 'undefined') {
+ const pluralKey = new Intl.PluralRules(lang).select(plural);
+ if (result && typeof result === 'object' && pluralKey in result) {
+ result = result[pluralKey];
+ }
+ }
+
+ return result;
+ }
+}
+
+export default I18nService;
diff --git a/src/i18n/translate.js b/src/i18n/translate.js
deleted file mode 100644
index c4c60db93..000000000
--- a/src/i18n/translate.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import * as translations from './translations';
-
-/**
- * Перевод фразу по словарю
- * @param lang {String} Код языка
- * @param text {String} Текст для перевода
- * @param [plural] {Number} Число для плюрализации
- * @returns {String} Переведенный текст
- */
-export default function translate(lang, text, plural) {
- let result = translations[lang] && text in translations[lang] ? translations[lang][text] : text;
-
- if (typeof plural !== 'undefined') {
- const key = new Intl.PluralRules(lang).select(plural);
- if (key in result) {
- result = result[key];
- }
- }
-
- return result;
-}
diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json
index 84d2cbb00..9ca10cc47 100644
--- a/src/i18n/translations/en.json
+++ b/src/i18n/translations/en.json
@@ -5,7 +5,7 @@
"basket.open": "Open",
"basket.close": "Close",
"basket.inBasket": "In cart",
- "basket.empty": "empty",
+ "basket.empty": "Your cart is empty",
"basket.total": "Total",
"basket.unit": "pcs",
"basket.delete": "Delete",
@@ -14,6 +14,10 @@
"other": "articles"
},
"article.add": "Add",
+ "card.country": "Made in:",
+ "card.сategory": "Category:",
+ "card.year": "Year of manufacture:",
+ "card.price": "Price:",
"filter.reset": "Reset",
"auth.title": "Sign In",
"auth.login": "Login",
@@ -27,9 +31,13 @@
"comments.singIn": "Sign in",
"comments.singIn-desc": ", to be able to comment",
"comments.answer": "Answer",
- "comments.send": "Send",
+ "comments.send": "Submit",
"comments.cancel": "Cancel",
"comments.title": "New comment",
"comments.answer-title": "New answer",
- "comments.error": "Error, please try sending again"
+ "comments.error": "Submission failed, please try again",
+ "profile.profile": "Profile",
+ "profile.name": "Name:",
+ "profile.tel": "Contact Number:",
+ "profile.email": "Email:"
}
diff --git a/src/i18n/translations/index.js b/src/i18n/translations/index.js
deleted file mode 100644
index e215c553a..000000000
--- a/src/i18n/translations/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as en } from './en.json';
-export { default as ru } from './ru.json';
diff --git a/src/i18n/translations/ru.json b/src/i18n/translations/ru.json
index c01c78467..ac0c7b11a 100644
--- a/src/i18n/translations/ru.json
+++ b/src/i18n/translations/ru.json
@@ -16,6 +16,10 @@
"other": "товара"
},
"article.add": "Добавить",
+ "card.country": "Страна производитель:",
+ "card.сategory": "Категория:",
+ "card.year": "Год выпуска:",
+ "card.price": "Цена:",
"filter.reset": "Сбросить",
"auth.title": "Вход",
"auth.login": "Логин",
@@ -33,5 +37,9 @@
"comments.cancel": "Отмена",
"comments.title": "Новый комментарий",
"comments.answer-title": "Новый ответ",
- "comments.error": "Ошибка, попробуйте отправить повторно"
+ "comments.error": "Ошибка, попробуйте отправить повторно",
+ "profile.profile": "Профиль",
+ "profile.name": "Имя:",
+ "profile.tel": "Телефон:",
+ "profile.email": "Email:"
}
diff --git a/src/index.js b/src/index.js
index f49db385a..a52b32f1d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,7 @@ 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 +16,9 @@ const root = createRoot(document.getElementById('root'));
root.render(
-
-
-
-
-
+
+
+
,
);
diff --git a/src/services.js b/src/services.js
index a32b14d57..955772d28 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/index';
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;
From b5f30740129afdd599db69ad2a425460c41dcf6d Mon Sep 17 00:00:00 2001
From: Korotkov <149740711+Dgerimus@users.noreply.github.com>
Date: Tue, 22 Apr 2025 13:23:29 +0300
Subject: [PATCH 3/4] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/comments-form/index.js | 2 +-
src/components/field/index.js | 2 +-
src/components/field/style.css | 3 +
src/components/item-comment/index.js | 2 +-
src/components/item-comment/style.css | 35 +--
src/components/prompt-for-comments/style.css | 1 +
src/containers/comments/index.js | 240 +++++++++++--------
src/i18n/translations/en.json | 1 +
src/i18n/translations/ru.json | 3 +-
src/store-redux/comments/action.js | 14 +-
src/store-redux/comments/reducer.js | 6 +-
src/utils/find-insert-position.js | 22 ++
src/utils/find-last-child.js | 26 ++
13 files changed, 233 insertions(+), 124 deletions(-)
create mode 100644 src/utils/find-insert-position.js
create mode 100644 src/utils/find-last-child.js
diff --git a/src/components/comments-form/index.js b/src/components/comments-form/index.js
index 68ae93b71..bd3956657 100644
--- a/src/components/comments-form/index.js
+++ b/src/components/comments-form/index.js
@@ -22,7 +22,7 @@ function CommentsForm({
);
diff --git a/src/components/field/style.css b/src/components/field/style.css
index c4bec94ff..7d486d73c 100644
--- a/src/components/field/style.css
+++ b/src/components/field/style.css
@@ -3,3 +3,6 @@
font-size: 12px;
min-height: 16px;
}
+.Field-input {
+ margin-top: 8px;
+}
diff --git a/src/components/item-comment/index.js b/src/components/item-comment/index.js
index c822b45db..d0c5eb690 100644
--- a/src/components/item-comment/index.js
+++ b/src/components/item-comment/index.js
@@ -4,7 +4,7 @@ import { cn as bem } from '@bem-react/classname';
import './style.css';
import Button from '../button';
-function ItemComment({ author, date, text, titleBtn, onClick }) {
+function ItemComment({ author, date, text, titleBtn, onClick = () => {} }) {
const cn = bem('ItemComment');
return (
diff --git a/src/components/item-comment/style.css b/src/components/item-comment/style.css
index adaa30026..7ada0c253 100644
--- a/src/components/item-comment/style.css
+++ b/src/components/item-comment/style.css
@@ -1,25 +1,26 @@
.ItemComment {
margin-bottom: 16px;
+}
- &:last-child {
- margin-bottom: 24px;
- }
+.ItemComment:last-child {
+ margin-bottom: 16px;
+}
- &-header {
- font-size: 12px;
+.ItemComment-header {
+ font-size: 12px;
+}
- & strong {
- margin-right: 12px;
- }
+.ItemComment-header strong {
+ margin-right: 12px;
+}
- & span {
- color: var(--third-text);
- }
- }
+.ItemComment-header span {
+ color: var(--third-text);
+}
- &-text {
- margin: 6px 0;
- font-size: 14px;
- color: #000000;
- }
+.ItemComment-text {
+ margin: 6px 0;
+ font-size: 14px;
+ color: #000000;
+ word-wrap: break-word;
}
diff --git a/src/components/prompt-for-comments/style.css b/src/components/prompt-for-comments/style.css
index 6b8e8925e..b9bf51f23 100644
--- a/src/components/prompt-for-comments/style.css
+++ b/src/components/prompt-for-comments/style.css
@@ -6,6 +6,7 @@
&:hover {
color: var(--primary-select);
+ text-decoration: underline;
}
}
}
diff --git a/src/containers/comments/index.js b/src/containers/comments/index.js
index fbe2c3298..dcdec1e39 100644
--- a/src/containers/comments/index.js
+++ b/src/containers/comments/index.js
@@ -1,40 +1,37 @@
-import { memo, useCallback, useMemo, useState } from 'react';
-import useTranslate from '../../hooks/use-translate';
+import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useLocation, useParams } from 'react-router-dom';
+import { useDispatch, useSelector as useReduxSelector } from 'react-redux';
+import shallowEqual from 'shallowequal';
+import useTranslate from '../../hooks/use-translate';
+import useInit from '../../hooks/use-init';
+import useSelector from '../../hooks/use-selector';
import ItemComment from '../../components/item-comment';
import CommentsContainer from '../../components/comments-container';
import commentsActions from '../../store-redux/comments/action';
-import useSelector from '../../hooks/use-selector';
-import { useDispatch, useSelector as UseSelectRedux } from 'react-redux';
-import useInit from '../../hooks/use-init';
-import shallowEqual from 'shallowequal';
-import listToTree from '../../utils/list-to-tree';
-import treeToList from '../../utils/tree-to-list';
import Spinner from '../../components/spinner';
import CommentsForm from '../../components/comments-form';
-import PromptForComments from '../../components/prompt-for-comments';
+import CommentsPrompt from '../../components/prompt-for-comments';
import formatDate from '../../utils/format-date';
+import listToTree from '../../utils/list-to-tree';
+import treeToList from '../../utils/tree-to-list';
+import findLastChild from '../../utils/find-last-child';
+import findInsertPosition from '../../utils/find-insert-position';
-function Comments({}) {
+const Comments = () => {
const { t } = useTranslate();
const dispatch = useDispatch();
const params = useParams();
const location = useLocation();
- const back = location ? location?.pathname : '/';
+ const back = location?.pathname || '/';
+ const formRef = useRef(null);
+
const [newComment, setNewComment] = useState({
- parent: {
- _id: '',
- _type: '',
- },
+ parent: { _id: '', _type: '' },
text: '',
});
const [activeReplyId, setActiveReplyId] = useState(null);
- useInit(() => {
- dispatch(commentsActions.load(params.id));
- }, [params.id]);
-
- const selectRedux = UseSelectRedux(
+ const selectRedux = useReduxSelector(
state => ({
commentsList: state.comments.data,
waiting: state.comments.waiting,
@@ -42,7 +39,26 @@ function Comments({}) {
article: state.article.data,
}),
shallowEqual,
- ); // Нужно указать функцию для сравнения свойства объекта, так как хуком вернули объект
+ );
+
+ const select = useSelector(state => ({
+ exists: state.session.exists,
+ user: state.session.user,
+ }));
+
+ useInit(() => {
+ dispatch(commentsActions.load(params.id));
+ }, [params.id]);
+
+ useEffect(() => {
+ if (activeReplyId && formRef.current) {
+ formRef.current.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ });
+ }
+ }, [activeReplyId]);
+
const parent = useMemo(
() => ({
_id: activeReplyId || params.id,
@@ -51,95 +67,138 @@ function Comments({}) {
[activeReplyId, params.id, selectRedux.article._type],
);
+ const commentListTree = useMemo(
+ () => listToTree(selectRedux.commentsList),
+ [selectRedux.commentsList],
+ );
+
+ const comments = useMemo(
+ () =>
+ treeToList(commentListTree, (item, level) => ({
+ id: item._id,
+ level,
+ padding: Math.min(level, 8) * 40,
+ author: item.author?.profile?.name,
+ date: item.dateCreate,
+ text: item.text,
+ })),
+ [commentListTree],
+ );
+
+ const lastChild = useMemo(
+ () => findLastChild(commentListTree, activeReplyId),
+ [commentListTree, activeReplyId],
+ );
+
+ const insertPost = useMemo(
+ () => findInsertPosition(activeReplyId, lastChild, comments),
+ [activeReplyId, lastChild, comments],
+ );
+
const callbacks = {
onChange: useCallback(
e => {
- setNewComment({
+ setNewComment(prev => ({
+ ...prev,
parent,
text: e.target.value,
- });
+ }));
},
- [setNewComment, activeReplyId],
+ [parent],
),
onSubmit: useCallback(
- async e => {
+ e => {
e.preventDefault();
- if (newComment.text) {
- await dispatch(commentsActions.create(newComment));
- dispatch(commentsActions.load(params.id));
- setNewComment({
- parent,
- text: '',
- });
+ const isEmpty = newComment.text.trim().length > 0;
+
+ if (isEmpty) {
+ dispatch(commentsActions.create(newComment, select.user.profile.name));
+ setNewComment({ parent, text: '' });
setActiveReplyId(null);
}
},
- [newComment],
+ [newComment, select.user?.profile?.name, dispatch, parent],
),
- onCancel: useCallback(e => setActiveReplyId(null), [setActiveReplyId]),
- };
- const select = useSelector(state => ({
- exists: state.session.exists,
- }));
- const options = {
- comments: useMemo(
- () => [
- ...treeToList(listToTree(selectRedux.commentsList), (item, level) => ({
- id: item._id,
- padding: 40 * level,
- author: item.author?.profile.name,
- date: item.dateCreate,
- text: item.text,
- })),
- ],
- [selectRedux.commentsList],
+ onCancel: useCallback(() => {
+ setNewComment(prev => ({ ...prev, text: '' }));
+ setActiveReplyId(null);
+ }, []),
+
+ onAnswer: useCallback(
+ comment => {
+ setNewComment(prev => ({
+ ...prev,
+ text: `${t('comments.answer-text')}${comment.author}`,
+ }));
+ setActiveReplyId(comment.id);
+ },
+ [t],
),
};
- return (
-
-
- {options.comments.map(comment => (
-
-
setActiveReplyId(comment.id)}
+ const renderComment = (comment, index) => {
+ const isCurrentUserComment = select.user.profile?.name === comment.author;
+ const commentStyle = {
+ color: isCurrentUserComment ? '#4B5563' : '',
+ paddingLeft: comment.padding,
+ marginTop: index === 0 ? '24px' : 0,
+ };
+
+ return (
+
+
+ callbacks.onAnswer(comment)}
+ />
+
+
+ {select.exists && lastChild?._id === comment.id && (
+
+
+
+ )}
- {select.exists && activeReplyId === comment.id && (
-
- )}
- {!select.exists && activeReplyId === comment.id && (
-
- )}
+ {!select.exists && activeReplyId === comment.id && (
+
+
- ))}
-
+ )}
+
+ );
+ };
+
+ return (
+
+ {comments.map(renderComment)}
+
{!select.exists && !activeReplyId && (
-
)}
+
{select.exists && !activeReplyId && (
);
-}
-
-// Comments.propTypes = {
-// article: PropTypes.shape({
-// _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
-// description: PropTypes.string,
-// madeIn: PropTypes.object,
-// category: PropTypes.object,
-// edition: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
-// price: PropTypes.number,
-// }).isRequired,
-// onAdd: PropTypes.func,
-// t: PropTypes.func,
-// };
+};
export default memo(Comments);
diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json
index 9ca10cc47..0da018fda 100644
--- a/src/i18n/translations/en.json
+++ b/src/i18n/translations/en.json
@@ -31,6 +31,7 @@
"comments.singIn": "Sign in",
"comments.singIn-desc": ", to be able to comment",
"comments.answer": "Answer",
+ "comments.answer-text": "My answer for ",
"comments.send": "Submit",
"comments.cancel": "Cancel",
"comments.title": "New comment",
diff --git a/src/i18n/translations/ru.json b/src/i18n/translations/ru.json
index ac0c7b11a..77b08c2ba 100644
--- a/src/i18n/translations/ru.json
+++ b/src/i18n/translations/ru.json
@@ -33,7 +33,8 @@
"comments.singIn": "Войдите",
"comments.singIn-desc": ", чтобы иметь возможность комментировать",
"comments.answer": "Ответить",
- "comments.send": "Отпаравить",
+ "comments.answer-text": "Мой ответ для ",
+ "comments.send": "Отправить",
"comments.cancel": "Отмена",
"comments.title": "Новый комментарий",
"comments.answer-title": "Новый ответ",
diff --git a/src/store-redux/comments/action.js b/src/store-redux/comments/action.js
index a1d401a83..237e5ffca 100644
--- a/src/store-redux/comments/action.js
+++ b/src/store-redux/comments/action.js
@@ -21,7 +21,7 @@ export default {
}
};
},
- create: comment => {
+ create: (comment, userName) => {
return async (dispatch, getState, services) => {
// Сброс комментариев и установка признака ожидания загрузки
dispatch({ type: 'comments/create-start' });
@@ -33,11 +33,19 @@ export default {
body: JSON.stringify(comment),
});
// комментарии загружены успешно
- dispatch({ type: 'comments/create-success', payload: { data: res.data.result.items } });
+ dispatch({
+ type: 'comments/create-success',
+ payload: {
+ data: [
+ ...getState().comments.data,
+ { ...res.data.result, author: { profile: { name: userName } } },
+ ],
+ },
+ });
} catch (e) {
//Ошибка загрузки
dispatch({ type: 'comments/create-error' });
}
};
},
-};
\ No newline at end of file
+};
diff --git a/src/store-redux/comments/reducer.js b/src/store-redux/comments/reducer.js
index df1aa63f2..1af543de2 100644
--- a/src/store-redux/comments/reducer.js
+++ b/src/store-redux/comments/reducer.js
@@ -16,17 +16,17 @@ function reducer(state = initialState, action) {
}
case 'comments/load-error':
- return { ...state, data: [], waiting: false }; //@todo текст ошибки сохранять?
+ return { ...state, data: [], waiting: false };
case 'comments/create-start':
return { ...state, waiting: true, success: true };
case 'comments/create-success': {
- return { ...state, success: true, waiting: false };
+ return { ...state, success: true, waiting: false, data: action.payload.data };
}
case 'comments/create-error':
- return { ...state, success: false, waiting: false }; //@todo текст ошибки сохранять?
+ return { ...state, success: false, waiting: false };
default:
// Нет изменений
diff --git a/src/utils/find-insert-position.js b/src/utils/find-insert-position.js
new file mode 100644
index 000000000..1bd9a2e2d
--- /dev/null
+++ b/src/utils/find-insert-position.js
@@ -0,0 +1,22 @@
+export default function findInsertPosition(targetId, lastChild, commentsList) {
+ // Если есть последний дочерний комментарий, вставляем после него
+ if (lastChild) {
+ return {
+ insertAfterId: lastChild._id,
+ level: lastChild.level,
+ };
+ }
+
+ // Находим родительский комментарий в списке
+ const parentComment = commentsList.find(comment => comment.id === targetId);
+
+ // Определяем уровень вложенности для нового комментария
+ const nestingLevel = parentComment
+ ? parentComment.level + 1 // Уровень на 1 больше родительского
+ : 1; // Корневой уровень, если родитель не найден
+
+ return {
+ insertAfterId: targetId, // Вставляем после целевого комментария
+ level: nestingLevel, // Уровень вложенности нового комментария
+ };
+}
diff --git a/src/utils/find-last-child.js b/src/utils/find-last-child.js
new file mode 100644
index 000000000..5901baceb
--- /dev/null
+++ b/src/utils/find-last-child.js
@@ -0,0 +1,26 @@
+export default function findLastChild(comments, targetId, parentLevel = 0) {
+ for (const comment of comments) {
+ if (comment._id === targetId) {
+ if (comment.children && comment.children.length > 0) {
+ const lastChild = findLastChild(
+ comment.children,
+ comment.children[comment.children.length - 1]._id,
+ parentLevel + 1,
+ );
+ return {
+ ...lastChild,
+ level: parentLevel + 1,
+ };
+ }
+ return {
+ ...comment,
+ level: parentLevel + 1,
+ };
+ }
+ if (comment.children) {
+ const found = findLastChild(comment.children, targetId, parentLevel + 1);
+ if (found) return found;
+ }
+ }
+ return null;
+}
From 76b0aeb1b91f4e320cea750ac04844bd93924b8b Mon Sep 17 00:00:00 2001
From: Korotkov <149740711+Dgerimus@users.noreply.github.com>
Date: Wed, 23 Apr 2025 14:52:03 +0300
Subject: [PATCH 4/4] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8,=20?=
=?UTF-8?q?=D0=BF=D0=BE=D1=84=D0=B8=D0=BA=D1=88=D0=B5=D0=BD=20=D0=B1=D0=B0?=
=?UTF-8?q?=D0=B3=20=D1=81=20useMemo?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/containers/comments/index.js | 151 +++++++++++++++++++-----------
src/utils/find-insert-position.js | 15 +--
src/utils/find-last-child.js | 9 +-
src/utils/tree-to-list/index.js | 11 ++-
4 files changed, 114 insertions(+), 72 deletions(-)
diff --git a/src/containers/comments/index.js b/src/containers/comments/index.js
index dcdec1e39..7b56078d7 100644
--- a/src/containers/comments/index.js
+++ b/src/containers/comments/index.js
@@ -14,8 +14,48 @@ import CommentsPrompt from '../../components/prompt-for-comments';
import formatDate from '../../utils/format-date';
import listToTree from '../../utils/list-to-tree';
import treeToList from '../../utils/tree-to-list';
-import findLastChild from '../../utils/find-last-child';
-import findInsertPosition from '../../utils/find-insert-position';
+
+// Функция для поиска последнего дочернего комментария
+function findLastChild(comments, targetId, parentLevel = 0) {
+ for (const comment of comments) {
+ if (comment._id === targetId) {
+ if (comment.children && comment.children.length > 0) {
+ const lastChild = findLastChild(
+ comment.children,
+ comment.children[comment.children.length - 1]._id,
+ parentLevel + 1,
+ );
+ return (
+ lastChild || {
+ ...comment.children[comment.children.length - 1],
+ level: parentLevel + 1,
+ }
+ );
+ }
+ return null;
+ }
+ if (comment.children) {
+ const found = findLastChild(comment.children, targetId, parentLevel + 1);
+ if (found) return found;
+ }
+ }
+ return null;
+}
+
+// Функция для определения позиции вставки
+function findInsertPosition(targetId, lastChild, commentsList) {
+ if (lastChild) {
+ return {
+ insertAfterId: lastChild._id,
+ level: lastChild.level + 1,
+ };
+ }
+
+ return {
+ insertAfterId: targetId,
+ level: 1, // Базовый уровень вложенности для ответа
+ };
+}
const Comments = () => {
const { t } = useTranslate();
@@ -26,7 +66,6 @@ const Comments = () => {
const formRef = useRef(null);
const [newComment, setNewComment] = useState({
- parent: { _id: '', _type: '' },
text: '',
});
const [activeReplyId, setActiveReplyId] = useState(null);
@@ -59,14 +98,6 @@ const Comments = () => {
}
}, [activeReplyId]);
- const parent = useMemo(
- () => ({
- _id: activeReplyId || params.id,
- _type: activeReplyId ? 'comment' : selectRedux.article._type,
- }),
- [activeReplyId, params.id, selectRedux.article._type],
- );
-
const commentListTree = useMemo(
() => listToTree(selectRedux.commentsList),
[selectRedux.commentsList],
@@ -75,8 +106,9 @@ const Comments = () => {
const comments = useMemo(
() =>
treeToList(commentListTree, (item, level) => ({
+ ...item,
id: item._id,
- level,
+ level: level,
padding: Math.min(level, 8) * 40,
author: item.author?.profile?.name,
date: item.dateCreate,
@@ -86,26 +118,22 @@ const Comments = () => {
);
const lastChild = useMemo(
- () => findLastChild(commentListTree, activeReplyId),
+ () => (activeReplyId ? findLastChild(commentListTree, activeReplyId) : null),
[commentListTree, activeReplyId],
);
- const insertPost = useMemo(
- () => findInsertPosition(activeReplyId, lastChild, comments),
- [activeReplyId, lastChild, comments],
+ const insertPosition = useMemo(
+ () => (activeReplyId ? findInsertPosition(activeReplyId, lastChild, comments) : null),
+ [lastChild, activeReplyId, comments],
);
const callbacks = {
- onChange: useCallback(
- e => {
- setNewComment(prev => ({
- ...prev,
- parent,
- text: e.target.value,
- }));
- },
- [parent],
- ),
+ onChange: useCallback(e => {
+ setNewComment(prev => ({
+ ...prev,
+ text: e.target.value,
+ }));
+ }, []),
onSubmit: useCallback(
e => {
@@ -113,12 +141,24 @@ const Comments = () => {
const isEmpty = newComment.text.trim().length > 0;
if (isEmpty) {
- dispatch(commentsActions.create(newComment, select.user.profile.name));
- setNewComment({ parent, text: '' });
+ const parent = {
+ _id: activeReplyId || params.id,
+ _type: activeReplyId ? 'comment' : selectRedux.article._type,
+ };
+
+ dispatch(commentsActions.create({ ...newComment, parent }, select.user.profile.name));
+ setNewComment({ text: '' });
setActiveReplyId(null);
}
},
- [newComment, select.user?.profile?.name, dispatch, parent],
+ [
+ newComment,
+ activeReplyId,
+ params.id,
+ selectRedux.article._type,
+ select.user?.profile?.name,
+ dispatch,
+ ],
),
onCancel: useCallback(() => {
@@ -141,8 +181,8 @@ const Comments = () => {
const renderComment = (comment, index) => {
const isCurrentUserComment = select.user.profile?.name === comment.author;
const commentStyle = {
- color: isCurrentUserComment ? '#4B5563' : '',
- paddingLeft: comment.padding,
+ color: isCurrentUserComment ? '#4B5563' : 'inherit',
+ paddingLeft: `${comment.padding}px`,
marginTop: index === 0 ? '24px' : 0,
};
@@ -159,28 +199,33 @@ const Comments = () => {
/>
- {select.exists && lastChild?._id === comment.id && (
-
-
-
- )}
-
- {!select.exists && activeReplyId === comment.id && (
-
-
+ {/* Форма вставляется после последнего комментария в цепочке */}
+ {insertPosition && insertPosition.insertAfterId === comment.id && (
+
+ {select.exists ? (
+
+ ) : (
+
+ )}
)}
diff --git a/src/utils/find-insert-position.js b/src/utils/find-insert-position.js
index 1bd9a2e2d..4efb0fe56 100644
--- a/src/utils/find-insert-position.js
+++ b/src/utils/find-insert-position.js
@@ -1,22 +1,15 @@
export default function findInsertPosition(targetId, lastChild, commentsList) {
- // Если есть последний дочерний комментарий, вставляем после него
if (lastChild) {
return {
- insertAfterId: lastChild._id,
- level: lastChild.level,
+ insertAfterId: lastChild.id,
+ level: lastChild.level + 1,
};
}
- // Находим родительский комментарий в списке
const parentComment = commentsList.find(comment => comment.id === targetId);
- // Определяем уровень вложенности для нового комментария
- const nestingLevel = parentComment
- ? parentComment.level + 1 // Уровень на 1 больше родительского
- : 1; // Корневой уровень, если родитель не найден
-
return {
- insertAfterId: targetId, // Вставляем после целевого комментария
- level: nestingLevel, // Уровень вложенности нового комментария
+ insertAfterId: targetId,
+ level: parentComment ? parentComment.level + 1 : 0,
};
}
diff --git a/src/utils/find-last-child.js b/src/utils/find-last-child.js
index 5901baceb..90d502513 100644
--- a/src/utils/find-last-child.js
+++ b/src/utils/find-last-child.js
@@ -5,16 +5,13 @@ export default function findLastChild(comments, targetId, parentLevel = 0) {
const lastChild = findLastChild(
comment.children,
comment.children[comment.children.length - 1]._id,
- parentLevel + 1,
+ parentLevel + 1, // Увеличиваем уровень для детей
);
- return {
- ...lastChild,
- level: parentLevel + 1,
- };
+ return lastChild; // Возвращаем последнего ребёнка без перезаписи уровня
}
return {
...comment,
- level: parentLevel + 1,
+ level: parentLevel, // Уровень должен быть parentLevel, а не +1 (чтобы не "опережать" расчёты)
};
}
if (comment.children) {
diff --git a/src/utils/tree-to-list/index.js b/src/utils/tree-to-list/index.js
index 3a79c0b06..26c3c55fd 100644
--- a/src/utils/tree-to-list/index.js
+++ b/src/utils/tree-to-list/index.js
@@ -7,9 +7,16 @@
* @returns {Array} Корневые узлы
*/
export default function treeToList(tree, callback, level = 0, result = []) {
+ if (!Array.isArray(tree)) return result;
+
for (const item of tree) {
- result.push(callback ? callback(item, level) : item);
- if (item.children?.length) treeToList(item.children, callback, level + 1, result);
+ const newItem = callback ? callback(item, level) : { ...item, level, id: item._id };
+ result.push(newItem);
+
+ if (item.children?.length) {
+ treeToList(item.children, callback, level + 1, result);
+ }
}
+
return result;
}