From a37f5cd2d010303be07806b94ce435fc06aab776 Mon Sep 17 00:00:00 2001 From: Fovway Date: Sat, 8 Nov 2025 23:58:48 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20ImageModal?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=BA=D0=B8=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=B8=D1=85=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9=20=D1=81=20=D0=B2=D0=BE=D0=B7?= =?UTF-8?q?=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D1=8C=D1=8E=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BC=D0=B5?= =?UTF-8?q?=D0=B6=D0=B4=D1=83=20=D0=BD=D0=B8=D0=BC=D0=B8.=20=D0=A0=D0=B5?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=87=D0=B8=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=BF=D1=81=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0.=20=D0=A2=D0=B0=D0=BA=D0=B6=D0=B5=20=D1=83?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=BE=20=D1=83=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BE=D0=BA=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3=D0=B0=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/ImageModal.tsx | 143 ++++++++++++++++++++++----- src/pages/PublicProfilePage.tsx | 4 - src/pages/SettingsPage.tsx | 4 + src/styles/style.css | 10 ++ src/utils/markdown.ts | 1 - 5 files changed, 134 insertions(+), 28 deletions(-) diff --git a/src/components/common/ImageModal.tsx b/src/components/common/ImageModal.tsx index 8fa2875..ef5e8b9 100644 --- a/src/components/common/ImageModal.tsx +++ b/src/components/common/ImageModal.tsx @@ -1,43 +1,74 @@ -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 = ({ + 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(() => { - const handleImageClick = (e: Event) => { - const target = e.target as HTMLElement; - if (target.classList.contains("note-image")) { - const src = - target.getAttribute("src") || target.getAttribute("data-src"); - if (src) { - setImageSrc(src); - setIsOpen(true); - } + 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")) { + const src = + target.getAttribute("src") || target.getAttribute("data-src"); + if (src) { + setImageSrc(src); + setIsOpen(true); + } + } + }; - document.addEventListener("click", handleImageClick); + document.addEventListener("click", handleImageClick); - return () => { - document.removeEventListener("click", handleImageClick); - }; - }, []); + 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 (
{ × + {showNavigation && ( + <> + + + + )} { 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([]); diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index b0bfefc..b0f295e 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -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 = () => { + +