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 {