import React, { useState, useEffect, useRef } from "react"; import { Icon } from "@iconify/react"; import { Note } from "../../types/note"; import { parseMarkdown, makeTagsClickable, } from "../../utils/markdown"; import { parseSQLiteUtc, formatLocalDateTime } from "../../utils/dateFormat"; import { useMarkdown } from "../../hooks/useMarkdown"; interface PublicNoteItemProps { note: Note; onImageClick: (imageUrl: string) => void; } export const PublicNoteItem: React.FC = ({ note, onImageClick, }) => { useMarkdown(); // Инициализируем обработчики спойлеров и внешних ссылок const textNoteRef = useRef(null); const [isLongNote, setIsLongNote] = useState(false); const [isExpanded, setIsExpanded] = useState(false); // Форматируем дату для отображения const formatDate = () => { const createdDate = parseSQLiteUtc(note.created_at); return formatLocalDateTime(createdDate); }; // Форматируем содержимое заметки const formatContent = () => { let content = note.content; // Делаем теги кликабельными (для публичной страницы они не будут работать, но стиль сохраним) content = makeTagsClickable(content); // Парсим markdown с флагом read-only (чтобы чекбоксы были disabled) const htmlContent = parseMarkdown(content, true); return htmlContent; }; const toggleExpand = () => { setIsExpanded(!isExpanded); }; // Определяем длинные заметки для сворачивания useEffect(() => { const checkNoteLength = () => { if (!textNoteRef.current) return; const scrollHeight = textNoteRef.current.scrollHeight; // Считаем заметку длинной, если она больше 300px const isLong = scrollHeight > 300; setIsLongNote(isLong); }; const timer = setTimeout(checkNoteLength, 100); return () => clearTimeout(timer); }, [note.content]); // Получаем иконку файла по расширению const getFileIcon = (filename: string): string => { const ext = filename.split(".").pop()?.toLowerCase(); const iconMap: { [key: string]: string } = { pdf: "mdi:file-pdf-box", doc: "mdi:file-word-box", docx: "mdi:file-word-box", xls: "mdi:file-excel-box", xlsx: "mdi:file-excel-box", txt: "mdi:file-document-outline", zip: "mdi:folder-zip", rar: "mdi:folder-zip", "7z": "mdi:folder-zip", }; return iconMap[ext || ""] || "mdi:file"; }; // Форматируем размер файла const formatFileSize = (bytes: number): string => { if (bytes < 1024) return bytes + " B"; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; return (bytes / (1024 * 1024)).toFixed(1) + " MB"; }; return (
{formatDate()} {note.is_pinned === 1 && ( Закреплено )}
{ // Обработка клика по тегам (для публичной страницы не работает, но стиль сохраняем) const target = e.target as HTMLElement; if (target.classList.contains("tag-in-note")) { // Для публичной страницы теги не кликабельны e.preventDefault(); } }} /> {isLongNote && ( )} {note.images && note.images.length > 0 && (
{note.images.map((image) => { // Используем публичный endpoint для изображений const imageUrl = `/api/public/notes/${note.id}/images/${image.id}`; return (
{image.original_name} onImageClick(imageUrl)} />
); })}
)} {note.files && note.files.length > 0 && (
{note.files.map((file) => { // Используем публичный endpoint для файлов const fileUrl = `/api/public/notes/${note.id}/files/${file.id}`; return ( ); })}
)}
); };