Добавлен новый функционал в компонент ImageModal для поддержки отображения нескольких изображений с возможностью навигации между ними. Реализованы обработчики для переключения изображений и обновлены пропсы компонента. Также улучшено управление состоянием модального окна и добавлены стили для навигационных кнопок.
This commit is contained in:
parent
91c6f46fb4
commit
a37f5cd2d0
@ -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}>
|
||||
×
|
||||
</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"
|
||||
|
||||
@ -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[]>([]);
|
||||
|
||||
@ -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" /> Обновить
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user