Версия 1.1.0 Крупные исправления в логике редактора статьи. Корректный выход без редактирования, учет изменения галереи. Убраны лишние перересовки редактора TipTap.
This commit is contained in:
parent
07a596ad1d
commit
fcad77cf9d
@ -2,9 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Культура двух Столиц</title>
|
<title>Культура Двух Столиц</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vite-react-typescript-starter",
|
"name": "vite-react-typescript-starter",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.8",
|
"version": "1.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"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 { TipTapEditor } from './Editor/TipTapEditor';
|
||||||
import { CoverImageUpload } from './ImageUpload/CoverImageUpload';
|
import { CoverImageUpload } from './ImageUpload/CoverImageUpload';
|
||||||
import { ImageUploader } from './ImageUpload/ImageUploader';
|
import { ImageUploader } from './ImageUpload/ImageUploader';
|
||||||
@ -71,6 +71,10 @@ export function ArticleForm({
|
|||||||
const isAdmin = user?.permissions.isAdmin || false;
|
const isAdmin = user?.permissions.isAdmin || false;
|
||||||
const showGallery = false;
|
const showGallery = false;
|
||||||
|
|
||||||
|
// Используем useRef для отслеживания состояния инициализации
|
||||||
|
const isInitializingRef = useRef(false);
|
||||||
|
const initialDataRef = useRef<FormState | null>(null);
|
||||||
|
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [excerpt, setExcerpt] = useState('');
|
const [excerpt, setExcerpt] = useState('');
|
||||||
const [categoryId, setCategoryId] = useState(availableCategoryIds[0] || 1);
|
const [categoryId, setCategoryId] = useState(availableCategoryIds[0] || 1);
|
||||||
@ -82,23 +86,24 @@ export function ArticleForm({
|
|||||||
const [formNewImageUrl, setFormNewImageUrl] = useState('');
|
const [formNewImageUrl, setFormNewImageUrl] = useState('');
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [hasChanges, setHasChanges] = useState<boolean>(false);
|
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 [isSubmitting, setIsSubmitting] = useState<boolean>(false); // Добавляем флаг для отслеживания отправки
|
||||||
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); // Состояние для модального окна
|
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); // Состояние для модального окна
|
||||||
|
|
||||||
const [newRole, setNewRole] = useState('');
|
const [newRole, setNewRole] = useState('');
|
||||||
const [newAuthorId, setNewAuthorId] = useState('');
|
const [newAuthorId, setNewAuthorId] = useState('');
|
||||||
const [showAddAuthorModal, setShowAddAuthorModal] = useState(false);
|
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 { images: galleryImages, loading: galleryLoading, error: galleryError, addImage: addGalleryImage, updateImage: updateGalleryImage, deleteImage: deleteGalleryImage, reorderImages: reorderGalleryImages } = useGallery(editingId || '');
|
||||||
|
|
||||||
const [selectedAuthors, setSelectedAuthors] = useState<AuthorLink[]>([]);
|
const [selectedAuthors, setSelectedAuthors] = useState<AuthorLink[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (initialFormState) setFormReady(true);
|
|
||||||
}, [initialFormState]);
|
|
||||||
|
|
||||||
// Добавляем обработку ошибок
|
// Добавляем обработку ошибок
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
|
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
|
||||||
@ -120,37 +125,95 @@ export function ArticleForm({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Эффект для отслеживания загрузки всех данных
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editingId) {
|
if (!editingId) {
|
||||||
setDisplayedImages(galleryImages);
|
// Для новой статьи данные загружены сразу
|
||||||
} else {
|
setDataLoaded(true);
|
||||||
setDisplayedImages([]);
|
return;
|
||||||
}
|
}
|
||||||
}, [editingId, galleryImages]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Для редактирования ждем загрузки галереи
|
||||||
if (initialFormState) {
|
if (!galleryLoading) {
|
||||||
setTitle(initialFormState.title);
|
setDataLoaded(true);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}, [initialFormState]);
|
}, [editingId, galleryLoading]);
|
||||||
|
|
||||||
|
// Эффект для инициализации формы
|
||||||
useEffect(() => {
|
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 = {
|
const currentState: FormState = {
|
||||||
title,
|
title,
|
||||||
@ -160,36 +223,23 @@ export function ArticleForm({
|
|||||||
coverImage,
|
coverImage,
|
||||||
readTime,
|
readTime,
|
||||||
content,
|
content,
|
||||||
authors: selectedAuthors,
|
authors: normalizedAuthors,
|
||||||
galleryImages: displayedImages,
|
galleryImages: normalizedCurrentGallery,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Проверяем заполнение обязательных полей
|
||||||
const areRequiredFieldsFilled = title.trim() !== '' && excerpt.trim() !== '';
|
const areRequiredFieldsFilled = title.trim() !== '' && excerpt.trim() !== '';
|
||||||
|
|
||||||
const hasFormChanges = Object.keys(initialFormState).some(key => {
|
// Сравниваем текущее состояние с начальным
|
||||||
if (!formReady) return false;
|
const hasFormChanges = !isEqual(currentState, initialDataRef.current);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Устанавливаем флаг изменений
|
||||||
setHasChanges(hasFormChanges && areRequiredFieldsFilled);
|
setHasChanges(hasFormChanges && areRequiredFieldsFilled);
|
||||||
|
}, [
|
||||||
if (isInitialLoad) {
|
title, excerpt, categoryId, cityId, coverImage,
|
||||||
setIsInitialLoad(false);
|
readTime, content, selectedAuthors, displayedImages,
|
||||||
}
|
isInitialized
|
||||||
}, [title, excerpt, categoryId, cityId, coverImage, readTime, content, selectedAuthors, displayedImages, initialFormState, isInitialLoad, formReady, showGallery]);
|
]);
|
||||||
|
|
||||||
const filteredAuthors = authors.filter(
|
const filteredAuthors = authors.filter(
|
||||||
(a) =>
|
(a) =>
|
||||||
|
|||||||
@ -84,13 +84,20 @@ export function TipTapEditor({ initialContent, onContentChange, articleId }: Tip
|
|||||||
setSelectedImage(null);
|
setSelectedImage(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
onContentChange(editor.getHTML());
|
// Добавляем проверку, чтобы избежать лишних обновлений
|
||||||
|
const newContent = editor.getHTML();
|
||||||
|
if (newContent !== initialContent) {
|
||||||
|
onContentChange(newContent);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editor && editor.getHTML() !== initialContent) {
|
if (editor && editor.getHTML() !== initialContent) {
|
||||||
editor.commands.setContent(initialContent);
|
// Используем requestAnimationFrame для отложенного обновления
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
editor.commands.setContent(initialContent);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [initialContent, editor]);
|
}, [initialContent, editor]);
|
||||||
|
|
||||||
|
|||||||
@ -60,7 +60,6 @@ export interface GalleryImage {
|
|||||||
height: number;
|
height: number;
|
||||||
size: number;
|
size: number;
|
||||||
format: string;
|
format: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Author {
|
export interface Author {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user