import type { Editor } from "@tiptap/react"; import Lightbox, { type Slide } from "yet-another-react-lightbox"; import { getFileUrl } from "@/lib/config.ts"; import "yet-another-react-lightbox/styles.css"; import "yet-another-react-lightbox/plugins/captions.css"; import Captions from "yet-another-react-lightbox/plugins/captions"; import Download from "yet-another-react-lightbox/plugins/download"; import Fullscreen from "yet-another-react-lightbox/plugins/fullscreen"; import Video from "yet-another-react-lightbox/plugins/video"; import Zoom from "yet-another-react-lightbox/plugins/zoom"; import { useEffect, useMemo, useState } from "react"; import i18n from "@/i18n.ts"; import { useTranslation } from "react-i18next"; type LightboxViewProps = { editor: Editor; open: boolean; src: string; type: "image" | "video"; onClose: () => void; }; function getVideoMimeType(src: string) { const extension = src.split(/[?#]/, 1)[0].split(".").pop()?.toLowerCase(); switch (extension) { case "webm": return "video/webm"; case "ogv": return "video/ogg"; case "mov": return "video/quicktime"; case "m4v": return "video/x-m4v"; default: return "video/mp4"; } } function getFilename(src: string) { const filename = src.split(/[?#]/, 1)[0].split("/").pop(); if (!filename) return i18n.t("Media"); try { return decodeURIComponent(filename); } catch { return filename; } } function getMedia(rawSrc: string, type?: string, alt?: string): Slide { const src = getFileUrl(rawSrc); const filename = getFilename(rawSrc); const caption = alt || filename; if (type === "video") { return { type: "video", sources: [{ src, type: getVideoMimeType(rawSrc) }], title: caption, download: { url: src, filename }, }; } else { return { type: "image", src, alt: alt || undefined, title: caption, download: { url: src, filename }, }; } } function getPageMedia(editor: Editor): Slide[] { const media: Slide[] = []; editor.state.doc.descendants((node) => { if (node.type.name !== "image" && node.type.name !== "video") return; const rawSrc = typeof node.attrs.src === "string" ? node.attrs.src : ""; if (!rawSrc) return; media.push(getMedia(rawSrc, node.type.name, node.attrs.alt)); }); return media; } export default function LightboxView({ editor, open, src, type, onClose, }: LightboxViewProps) { const { i18n: i18nInstance } = useTranslation(); const selectedSlide = useMemo( () => getMedia(src, type), [src, type, i18nInstance.language] ); const [pageSlides, setPageSlides] = useState([]); const [loadedMediaKey, setLoadedMediaKey] = useState(null); useEffect(() => { if (!open) return; setLoadedMediaKey(null); const frame = requestAnimationFrame(() => { const slides = getPageMedia(editor); setPageSlides(slides); setLoadedMediaKey(`${type}:${src}`); }); return () => cancelAnimationFrame(frame); }, [editor, open, type, src]); const slides = loadedMediaKey === `${type}:${src}` ? pageSlides : [selectedSlide]; const index = useMemo(() => { if (!(pageSlides.length > 0)) { return 0; } const idx = slides.findIndex((slide) => type === "video" ? "sources" in slide && slide.sources.some((s) => s.src === src) : "src" in slide && slide.src === src ); return idx >= 0 ? idx : 0; }, [slides, src, type]); return ( ); }