import { useEffect, useState } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import remarkFrontmatter from 'remark-frontmatter'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize from 'rehype-sanitize'; import rehypeHighlight from 'rehype-highlight'; import { Loader2, AlertCircle, FileText } from 'lucide-react'; import { imageRegistry, hasImageId, findImageById } from '@/data/image-registry'; import { sanitizeImageSrc, sanitizeImageAlt, sanitizeText, sanitizeURL } from '@/utils/sanitize'; import 'highlight.js/styles/github-dark.css'; interface MarkdownViewerProps { /** * Ruta del archivo .md a cargar * Ejemplo: "/manual/BLOQUE_0_FUNDAMENTOS/BLOQUE_00_0_FUNDAMENTOS_EMERGENCIAS.md" * O ruta relativa desde public/: "manual/BLOQUE_0_FUNDAMENTOS/archivo.md" */ filePath: string; /** * Clases CSS adicionales para el contenedor */ className?: string; /** * Mostrar estado de carga */ showLoading?: boolean; /** * Mostrar mensaje de error personalizado */ onError?: (error: Error) => void; } /** * Componente MarkdownViewer que carga y renderiza archivos .md dinámicamente * * Características: * - Carga dinámica de archivos .md desde public/ * - Renderizado con react-markdown y plugins avanzados * - Estilos consistentes con el diseño de la app * - Estados de carga y error * - Syntax highlighting para código * - Sanitización de HTML para seguridad */ const MarkdownViewer = ({ filePath, className = '', showLoading = true, onError, }: MarkdownViewerProps) => { const [content, setContent] = useState(''); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { // Normalizar ruta: asegurar que empiece con / const normalizedPath = filePath.startsWith('/') ? filePath : `/${filePath}`; setLoading(true); setError(null); fetch(normalizedPath) .then((res) => { if (!res.ok) { throw new Error(`Error ${res.status}: ${res.statusText}`); } return res.text(); }) .then((text) => { setContent(text); setLoading(false); }) .catch((err) => { const errorMessage = `No se pudo cargar el archivo: ${err.message}`; setError(errorMessage); setLoading(false); if (onError) { onError(err instanceof Error ? err : new Error(errorMessage)); } }); }, [filePath, onError]); // Estado de carga if (loading && showLoading) { return (

Cargando contenido...

); } // Estado de error if (error) { return (

Error al cargar archivo

{error}

Ruta: {filePath}

); } // Contenido vacío if (!content) { return (

No hay contenido para mostrar

); } // Renderizar Markdown return (
(

), h2: ({ node, ...props }) => (

), h3: ({ node, ...props }) => (

), h4: ({ node, ...props }) => (

), h5: ({ node, ...props }) => (

), h6: ({ node, ...props }) => (
), // Paragraphs p: ({ node, ...props }) => (

), // Lists ul: ({ node, ...props }) => (