Версия 1.1.0 Крупные исправления в логике редактора статьи. Корректный выход без редактирования, учет изменения галереи. Убраны лишние перересовки редактора TipTap.
This commit is contained in:
parent
07a596ad1d
commit
fcad77cf9d
@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Культура двух Столиц</title>
|
||||
<title>Культура Двух Столиц</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vite-react-typescript-starter",
|
||||
"private": true,
|
||||
"version": "1.0.8",
|
||||
"version": "1.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@ -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<FormState | null>(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<string | null>(null);
|
||||
const [hasChanges, setHasChanges] = useState<boolean>(false);
|
||||
const [isInitialLoad, setIsInitialLoad] = useState<boolean>(true);
|
||||
const [isInitialized, setIsInitialized] = useState(false); // Заменяем isInitialLoad и formReady
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(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<AuthorLink[]>([]);
|
||||
|
||||
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) =>
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
@ -60,7 +60,6 @@ export interface GalleryImage {
|
||||
height: number;
|
||||
size: number;
|
||||
format: string;
|
||||
|
||||
}
|
||||
|
||||
export interface Author {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user