diff --git a/index.html b/index.html
index 4737075..f2ec968 100644
--- a/index.html
+++ b/index.html
@@ -2,9 +2,9 @@
-
+
- Культура двух Столиц
+ Культура Двух Столиц
diff --git a/package.json b/package.json
index c76c259..8ab1a33 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vite-react-typescript-starter",
"private": true,
- "version": "1.0.8",
+ "version": "1.1.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src/components/ArticleForm.tsx b/src/components/ArticleForm.tsx
index 756e598..d3ac252 100644
--- a/src/components/ArticleForm.tsx
+++ b/src/components/ArticleForm.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useRef } from 'react';
import { TipTapEditor } from './Editor/TipTapEditor';
import { CoverImageUpload } from './ImageUpload/CoverImageUpload';
import { ImageUploader } from './ImageUpload/ImageUploader';
@@ -71,6 +71,10 @@ export function ArticleForm({
const isAdmin = user?.permissions.isAdmin || false;
const showGallery = false;
+// Используем useRef для отслеживания состояния инициализации
+ const isInitializingRef = useRef(false);
+ const initialDataRef = useRef(null);
+
const [title, setTitle] = useState('');
const [excerpt, setExcerpt] = useState('');
const [categoryId, setCategoryId] = useState(availableCategoryIds[0] || 1);
@@ -82,23 +86,24 @@ export function ArticleForm({
const [formNewImageUrl, setFormNewImageUrl] = useState('');
const [error, setError] = useState(null);
const [hasChanges, setHasChanges] = useState(false);
- const [isInitialLoad, setIsInitialLoad] = useState(true);
+ const [isInitialized, setIsInitialized] = useState(false); // Заменяем isInitialLoad и formReady
const [isSubmitting, setIsSubmitting] = useState(false); // Добавляем флаг для отслеживания отправки
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); // Состояние для модального окна
const [newRole, setNewRole] = useState('');
const [newAuthorId, setNewAuthorId] = useState('');
const [showAddAuthorModal, setShowAddAuthorModal] = useState(false);
- const [formReady, setFormReady] = useState(false);
+
+ // Добавляем состояние для отслеживания загрузки галереи
+ //const [galleryLoaded, setGalleryLoaded] = useState(false);
+
+ // Изменяем логику отслеживания состояния загрузки
+ const [dataLoaded, setDataLoaded] = useState(false);
const { images: galleryImages, loading: galleryLoading, error: galleryError, addImage: addGalleryImage, updateImage: updateGalleryImage, deleteImage: deleteGalleryImage, reorderImages: reorderGalleryImages } = useGallery(editingId || '');
const [selectedAuthors, setSelectedAuthors] = useState([]);
- useEffect(() => {
- if (initialFormState) setFormReady(true);
- }, [initialFormState]);
-
// Добавляем обработку ошибок
useEffect(() => {
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
@@ -120,37 +125,95 @@ export function ArticleForm({
};
}, []);
+// Эффект для отслеживания загрузки всех данных
useEffect(() => {
- if (editingId) {
- setDisplayedImages(galleryImages);
- } else {
- setDisplayedImages([]);
+ if (!editingId) {
+ // Для новой статьи данные загружены сразу
+ setDataLoaded(true);
+ return;
}
- }, [editingId, galleryImages]);
- useEffect(() => {
- if (initialFormState) {
- setTitle(initialFormState.title);
- setExcerpt(initialFormState.excerpt);
- setCategoryId(initialFormState.categoryId);
- setCityId(initialFormState.cityId);
- setCoverImage(initialFormState.coverImage);
- setReadTime(initialFormState.readTime);
- setContent(initialFormState.content);
- setDisplayedImages(initialFormState.galleryImages || []);
- setSelectedAuthors(
- (initialFormState.authors || []).map(a => ({
- authorId: a.author.id, // 👈 добавить вручную
- role: a.role,
- author: a.author
- }))
- );
-// console.log('Содержимое статьи при загрузке:', initialFormState.content);
+ // Для редактирования ждем загрузки галереи
+ if (!galleryLoading) {
+ setDataLoaded(true);
}
- }, [initialFormState]);
+ }, [editingId, galleryLoading]);
+// Эффект для инициализации формы
useEffect(() => {
- if (!initialFormState || !formReady) return;
+ // Если начальные данные не пришли или данные еще не загружены - выходим
+ if (!initialFormState || !dataLoaded) return;
+
+ isInitializingRef.current = true;
+
+ setTitle(initialFormState.title);
+ setExcerpt(initialFormState.excerpt);
+ setCategoryId(initialFormState.categoryId);
+ setCityId(initialFormState.cityId);
+ setCoverImage(initialFormState.coverImage);
+ setReadTime(initialFormState.readTime);
+ setContent(initialFormState.content);
+
+ // Для редактирования используем загруженные данные галереи
+ // Для новой статьи - пустой массив
+ const galleryData = editingId ? galleryImages : [];
+
+ // Нормализуем данные галереи
+ const normalizedGalleryImages = galleryData.map(img => ({
+ id: img.id,
+ url: img.url,
+ caption: img.caption || '',
+ alt: img.alt || '',
+ width: img.width || 0,
+ height: img.height || 0,
+ size: img.size || 0,
+ format: img.format,
+ }));
+
+ setDisplayedImages(normalizedGalleryImages);
+
+ setSelectedAuthors(
+ (initialFormState.authors || []).map(a => ({
+ authorId: a.author.id,
+ role: a.role,
+ author: a.author
+ }))
+ );
+
+ // Сохраняем начальное состояние с нормализованной галереей
+ initialDataRef.current = {
+ ...initialFormState,
+ authors: (initialFormState.authors || []).map(a => ({
+ role: a.role,
+ author: a.author
+ })),
+ galleryImages: normalizedGalleryImages
+ };
+
+ setIsInitialized(true);
+ }, [initialFormState, dataLoaded, editingId, galleryImages]);
+
+// Эффект для отслеживания изменений формы
+ useEffect(() => {
+ if (!isInitialized || !initialDataRef.current) return;
+
+ // Нормализуем текущее состояние для сравнения
+ const normalizedAuthors = selectedAuthors.map(sa => ({
+ role: sa.role,
+ author: sa.author
+ }));
+
+ // Нормализуем текущую галерею для сравнения
+ const normalizedCurrentGallery = displayedImages.map(img => ({
+ id: img.id,
+ url: img.url,
+ caption: img.caption || '',
+ alt: img.alt || '',
+ width: img.width || 0,
+ height: img.height || 0,
+ size: img.size || 0,
+ format: img.format,
+ }));
const currentState: FormState = {
title,
@@ -160,36 +223,23 @@ export function ArticleForm({
coverImage,
readTime,
content,
- authors: selectedAuthors,
- galleryImages: displayedImages,
+ authors: normalizedAuthors,
+ galleryImages: normalizedCurrentGallery,
};
+ // Проверяем заполнение обязательных полей
const areRequiredFieldsFilled = title.trim() !== '' && excerpt.trim() !== '';
- const hasFormChanges = Object.keys(initialFormState).some(key => {
- if (!formReady) return false;
-
- if (key === 'galleryImages') {
- //if (!showGallery) return false; // 💡 игнорировать при выключенной галерее
- const isDifferent = JSON.stringify(currentState[key]) !== JSON.stringify(initialFormState[key]);
- if (isInitialLoad && isDifferent) return false;
- return isDifferent;
- }
- if (key === 'content') {
- return JSON.stringify(currentState[key]) !== JSON.stringify(initialFormState[key]);
- }
- const currentValue = typeof currentState[key as keyof FormState] === 'number' ? String(currentState[key as keyof FormState]) : currentState[key as keyof FormState];
- const initialValue = typeof initialFormState[key as keyof FormState] === 'number' ? String(initialFormState[key as keyof FormState]) : initialFormState[key as keyof FormState];
-
- return !isEqual(currentValue, initialValue);
- });
+ // Сравниваем текущее состояние с начальным
+ const hasFormChanges = !isEqual(currentState, initialDataRef.current);
+ // Устанавливаем флаг изменений
setHasChanges(hasFormChanges && areRequiredFieldsFilled);
-
- if (isInitialLoad) {
- setIsInitialLoad(false);
- }
- }, [title, excerpt, categoryId, cityId, coverImage, readTime, content, selectedAuthors, displayedImages, initialFormState, isInitialLoad, formReady, showGallery]);
+ }, [
+ title, excerpt, categoryId, cityId, coverImage,
+ readTime, content, selectedAuthors, displayedImages,
+ isInitialized
+ ]);
const filteredAuthors = authors.filter(
(a) =>
diff --git a/src/components/Editor/TipTapEditor.tsx b/src/components/Editor/TipTapEditor.tsx
index 7cfd407..245256e 100644
--- a/src/components/Editor/TipTapEditor.tsx
+++ b/src/components/Editor/TipTapEditor.tsx
@@ -84,13 +84,20 @@ export function TipTapEditor({ initialContent, onContentChange, articleId }: Tip
setSelectedImage(null);
}
- onContentChange(editor.getHTML());
+ // Добавляем проверку, чтобы избежать лишних обновлений
+ const newContent = editor.getHTML();
+ if (newContent !== initialContent) {
+ onContentChange(newContent);
+ }
},
});
useEffect(() => {
if (editor && editor.getHTML() !== initialContent) {
- editor.commands.setContent(initialContent);
+ // Используем requestAnimationFrame для отложенного обновления
+ requestAnimationFrame(() => {
+ editor.commands.setContent(initialContent);
+ });
}
}, [initialContent, editor]);
diff --git a/src/types/index.ts b/src/types/index.ts
index c09eb1e..88b7ad1 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -60,7 +60,6 @@ export interface GalleryImage {
height: number;
size: number;
format: string;
-
}
export interface Author {