Добавлен новый функционал в компонент ImageModal для поддержки отображения нескольких изображений с возможностью навигации между ними. Реализованы обработчики для переключения изображений и обновлены пропсы компонента. Также улучшено управление состоянием модального окна и добавлены стили для навигационных кнопок.

This commit is contained in:
Fovway 2025-11-08 23:58:48 +07:00
parent 91c6f46fb4
commit a37f5cd2d0
5 changed files with 134 additions and 28 deletions

View File

@ -1,10 +1,37 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";
export const ImageModal: React.FC = () => {
interface ImageModalProps {
imageUrl?: string;
images?: string[];
currentIndex?: number;
onClose?: () => void;
}
export const ImageModal: React.FC<ImageModalProps> = ({
imageUrl,
images,
currentIndex = 0,
onClose,
}) => {
const [isOpen, setIsOpen] = useState(false);
const [imageSrc, setImageSrc] = useState("");
const [currentImgIndex, setCurrentImgIndex] = useState(0);
// Если передан imageUrl через props, используем props-режим
const isPropsMode = imageUrl !== undefined;
useEffect(() => {
if (isPropsMode) {
// Режим с props
if (imageUrl) {
setImageSrc(imageUrl);
setIsOpen(true);
setCurrentImgIndex(currentIndex || 0);
} else {
setIsOpen(false);
}
} else {
// Режим с event listeners (обратная совместимость)
const handleImageClick = (e: Event) => {
const target = e.target as HTMLElement;
if (target.classList.contains("note-image")) {
@ -22,22 +49,26 @@ export const ImageModal: React.FC = () => {
return () => {
document.removeEventListener("click", handleImageClick);
};
}, []);
}
}, [isPropsMode, imageUrl, currentIndex]);
const handleClose = useCallback(() => {
setIsOpen(false);
if (onClose) {
onClose();
}
}, [onClose]);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape" && isOpen) {
setIsOpen(false);
handleClose();
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [isOpen]);
const handleClose = () => {
setIsOpen(false);
};
}, [isOpen, handleClose]);
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
@ -45,8 +76,28 @@ export const ImageModal: React.FC = () => {
}
};
const handleNext = (e: React.MouseEvent) => {
e.stopPropagation();
if (images && images.length > 0) {
const nextIndex = (currentImgIndex + 1) % images.length;
setCurrentImgIndex(nextIndex);
setImageSrc(images[nextIndex]);
}
};
const handlePrev = (e: React.MouseEvent) => {
e.stopPropagation();
if (images && images.length > 0) {
const prevIndex = (currentImgIndex - 1 + images.length) % images.length;
setCurrentImgIndex(prevIndex);
setImageSrc(images[prevIndex]);
}
};
if (!isOpen) return null;
const showNavigation = isPropsMode && images && images.length > 1;
return (
<div
id="imageModal"
@ -57,6 +108,52 @@ export const ImageModal: React.FC = () => {
<span className="image-modal-close" onClick={handleClose}>
&times;
</span>
{showNavigation && (
<>
<button
className="image-modal-nav image-modal-nav-prev"
onClick={handlePrev}
style={{
position: "absolute",
left: "20px",
top: "50%",
transform: "translateY(-50%)",
background: "rgba(0, 0, 0, 0.5)",
color: "white",
border: "none",
borderRadius: "50%",
width: "50px",
height: "50px",
cursor: "pointer",
fontSize: "24px",
zIndex: 1001,
}}
>
</button>
<button
className="image-modal-nav image-modal-nav-next"
onClick={handleNext}
style={{
position: "absolute",
right: "20px",
top: "50%",
transform: "translateY(-50%)",
background: "rgba(0, 0, 0, 0.5)",
color: "white",
border: "none",
borderRadius: "50%",
width: "50px",
height: "50px",
cursor: "pointer",
fontSize: "24px",
zIndex: 1001,
}}
>
</button>
</>
)}
<img
className="image-modal-content"
id="modalImage"

View File

@ -7,14 +7,10 @@ import { Note } from "../types/note";
import { PublicNoteItem } from "../components/notes/PublicNoteItem";
import { ImageModal } from "../components/common/ImageModal";
import { ThemeToggle } from "../components/common/ThemeToggle";
import { useNotification } from "../hooks/useNotification";
import { useTheme } from "../hooks/useTheme";
const PublicProfilePage: React.FC = () => {
const { username } = useParams<{ username: string }>();
const navigate = useNavigate();
const { showNotification } = useNotification();
const { theme } = useTheme();
const [profile, setProfile] = useState<{ username: string; avatar?: string } | null>(null);
const [notes, setNotes] = useState<Note[]>([]);

View File

@ -390,6 +390,8 @@ const SettingsPage: React.FC = () => {
note_delete_permanent: "Окончательное удаление",
profile_update: "Обновление профиля",
ai_improve: "Улучшение через AI",
ai_generate_tags: "Генерация тегов через AI",
ai_merge: "Объединение заметок через AI",
};
return actionTypes[actionType] || actionType;
};
@ -943,6 +945,8 @@ const SettingsPage: React.FC = () => {
</option>
<option value="profile_update">Обновление профиля</option>
<option value="ai_improve">Улучшение через AI</option>
<option value="ai_generate_tags">Генерация тегов через AI</option>
<option value="ai_merge">Объединение заметок через AI</option>
</select>
<button className="btnSave" onClick={() => loadLogs(true)}>
<Icon icon="mdi:refresh" /> Обновить

View File

@ -4926,6 +4926,16 @@ textarea:focus {
color: #0c5460;
}
.log-action-ai_generate_tags {
background: #e7d5f5;
color: #6a1b9a;
}
.log-action-ai_merge {
background: #fff4e6;
color: #b8860b;
}
/* Стили для скрытого текста (спойлеров) */
.spoiler {
background: linear-gradient(

View File

@ -162,7 +162,6 @@ const createRenderer = (isReadOnly: boolean = false): any => {
const renderer = new marked.Renderer();
// Переопределяем метод link для обработки внешних ссылок
const originalLink = renderer.link.bind(renderer);
renderer.link = function(token: any) {
const href = token.href;
const title = token.title;