Добавлен новый функционал в компонент 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 [isOpen, setIsOpen] = useState(false);
|
||||||
const [imageSrc, setImageSrc] = useState("");
|
const [imageSrc, setImageSrc] = useState("");
|
||||||
|
const [currentImgIndex, setCurrentImgIndex] = useState(0);
|
||||||
|
|
||||||
|
// Если передан imageUrl через props, используем props-режим
|
||||||
|
const isPropsMode = imageUrl !== undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isPropsMode) {
|
||||||
|
// Режим с props
|
||||||
|
if (imageUrl) {
|
||||||
|
setImageSrc(imageUrl);
|
||||||
|
setIsOpen(true);
|
||||||
|
setCurrentImgIndex(currentIndex || 0);
|
||||||
|
} else {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Режим с event listeners (обратная совместимость)
|
||||||
const handleImageClick = (e: Event) => {
|
const handleImageClick = (e: Event) => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
if (target.classList.contains("note-image")) {
|
if (target.classList.contains("note-image")) {
|
||||||
@ -22,22 +49,26 @@ export const ImageModal: React.FC = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("click", handleImageClick);
|
document.removeEventListener("click", handleImageClick);
|
||||||
};
|
};
|
||||||
}, []);
|
}
|
||||||
|
}, [isPropsMode, imageUrl, currentIndex]);
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
if (onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
if (e.key === "Escape" && isOpen) {
|
if (e.key === "Escape" && isOpen) {
|
||||||
setIsOpen(false);
|
handleClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", handleEscape);
|
document.addEventListener("keydown", handleEscape);
|
||||||
return () => document.removeEventListener("keydown", handleEscape);
|
return () => document.removeEventListener("keydown", handleEscape);
|
||||||
}, [isOpen]);
|
}, [isOpen, handleClose]);
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setIsOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||||
if (e.target === e.currentTarget) {
|
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;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
const showNavigation = isPropsMode && images && images.length > 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="imageModal"
|
id="imageModal"
|
||||||
@ -57,6 +108,52 @@ export const ImageModal: React.FC = () => {
|
|||||||
<span className="image-modal-close" onClick={handleClose}>
|
<span className="image-modal-close" onClick={handleClose}>
|
||||||
×
|
×
|
||||||
</span>
|
</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
|
<img
|
||||||
className="image-modal-content"
|
className="image-modal-content"
|
||||||
id="modalImage"
|
id="modalImage"
|
||||||
|
|||||||
@ -7,14 +7,10 @@ import { Note } from "../types/note";
|
|||||||
import { PublicNoteItem } from "../components/notes/PublicNoteItem";
|
import { PublicNoteItem } from "../components/notes/PublicNoteItem";
|
||||||
import { ImageModal } from "../components/common/ImageModal";
|
import { ImageModal } from "../components/common/ImageModal";
|
||||||
import { ThemeToggle } from "../components/common/ThemeToggle";
|
import { ThemeToggle } from "../components/common/ThemeToggle";
|
||||||
import { useNotification } from "../hooks/useNotification";
|
|
||||||
import { useTheme } from "../hooks/useTheme";
|
|
||||||
|
|
||||||
const PublicProfilePage: React.FC = () => {
|
const PublicProfilePage: React.FC = () => {
|
||||||
const { username } = useParams<{ username: string }>();
|
const { username } = useParams<{ username: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { showNotification } = useNotification();
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const [profile, setProfile] = useState<{ username: string; avatar?: string } | null>(null);
|
const [profile, setProfile] = useState<{ username: string; avatar?: string } | null>(null);
|
||||||
const [notes, setNotes] = useState<Note[]>([]);
|
const [notes, setNotes] = useState<Note[]>([]);
|
||||||
|
|||||||
@ -390,6 +390,8 @@ const SettingsPage: React.FC = () => {
|
|||||||
note_delete_permanent: "Окончательное удаление",
|
note_delete_permanent: "Окончательное удаление",
|
||||||
profile_update: "Обновление профиля",
|
profile_update: "Обновление профиля",
|
||||||
ai_improve: "Улучшение через AI",
|
ai_improve: "Улучшение через AI",
|
||||||
|
ai_generate_tags: "Генерация тегов через AI",
|
||||||
|
ai_merge: "Объединение заметок через AI",
|
||||||
};
|
};
|
||||||
return actionTypes[actionType] || actionType;
|
return actionTypes[actionType] || actionType;
|
||||||
};
|
};
|
||||||
@ -943,6 +945,8 @@ const SettingsPage: React.FC = () => {
|
|||||||
</option>
|
</option>
|
||||||
<option value="profile_update">Обновление профиля</option>
|
<option value="profile_update">Обновление профиля</option>
|
||||||
<option value="ai_improve">Улучшение через AI</option>
|
<option value="ai_improve">Улучшение через AI</option>
|
||||||
|
<option value="ai_generate_tags">Генерация тегов через AI</option>
|
||||||
|
<option value="ai_merge">Объединение заметок через AI</option>
|
||||||
</select>
|
</select>
|
||||||
<button className="btnSave" onClick={() => loadLogs(true)}>
|
<button className="btnSave" onClick={() => loadLogs(true)}>
|
||||||
<Icon icon="mdi:refresh" /> Обновить
|
<Icon icon="mdi:refresh" /> Обновить
|
||||||
|
|||||||
@ -4926,6 +4926,16 @@ textarea:focus {
|
|||||||
color: #0c5460;
|
color: #0c5460;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-action-ai_generate_tags {
|
||||||
|
background: #e7d5f5;
|
||||||
|
color: #6a1b9a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-action-ai_merge {
|
||||||
|
background: #fff4e6;
|
||||||
|
color: #b8860b;
|
||||||
|
}
|
||||||
|
|
||||||
/* Стили для скрытого текста (спойлеров) */
|
/* Стили для скрытого текста (спойлеров) */
|
||||||
.spoiler {
|
.spoiler {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
|
|||||||
@ -162,7 +162,6 @@ const createRenderer = (isReadOnly: boolean = false): any => {
|
|||||||
const renderer = new marked.Renderer();
|
const renderer = new marked.Renderer();
|
||||||
|
|
||||||
// Переопределяем метод link для обработки внешних ссылок
|
// Переопределяем метод link для обработки внешних ссылок
|
||||||
const originalLink = renderer.link.bind(renderer);
|
|
||||||
renderer.link = function(token: any) {
|
renderer.link = function(token: any) {
|
||||||
const href = token.href;
|
const href = token.href;
|
||||||
const title = token.title;
|
const title = token.title;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user