Files
challenge-pl/src/components/personal/LearningMaterialViewer.tsx

271 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useMemo } from 'react'
import {
Box,
Button,
HStack,
Text,
VStack,
} from '@chakra-ui/react'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
interface LearningMaterialViewerProps {
content: string
linesPerPage?: number
}
export const LearningMaterialViewer = ({
content,
linesPerPage = 30
}: LearningMaterialViewerProps) => {
const [currentPage, setCurrentPage] = useState(0)
// Разделяем контент на страницы по linesPerPage строк
const pages = useMemo(() => {
const lines = content.split('\n')
const pagesArray: string[] = []
for (let i = 0; i < lines.length; i += linesPerPage) {
const pageLines = lines.slice(i, i + linesPerPage)
pagesArray.push(pageLines.join('\n'))
}
return pagesArray
}, [content, linesPerPage])
const totalPages = pages.length
if (totalPages === 0) {
return null
}
const goToPrevious = () => {
setCurrentPage(prev => Math.max(0, prev - 1))
}
const goToNext = () => {
setCurrentPage(prev => Math.min(totalPages - 1, prev + 1))
}
return (
<Box
borderWidth="1px"
borderRadius="md"
borderColor="blue.200"
p={4}
bg="blue.50"
shadow="sm"
>
<VStack align="stretch" gap={3}>
<HStack justify="space-between" align="center">
<Text fontSize="lg" fontWeight="bold" color="blue.800">
Дополнительные материалы
</Text>
{totalPages > 1 && (
<Text fontSize="sm" color="blue.600">
Страница {currentPage + 1} из {totalPages}
</Text>
)}
</HStack>
<Box
color="gray.700"
fontSize="sm"
lineHeight="1.7"
css={{
// Заголовки
'& h1': {
fontSize: '1.75em',
fontWeight: '700',
marginTop: '1.2em',
marginBottom: '0.6em',
color: '#2D3748',
borderBottom: '2px solid #E2E8F0',
paddingBottom: '0.3em'
},
'& h2': {
fontSize: '1.5em',
fontWeight: '600',
marginTop: '1em',
marginBottom: '0.5em',
color: '#2D3748'
},
'& h3': {
fontSize: '1.25em',
fontWeight: '600',
marginTop: '0.8em',
marginBottom: '0.4em',
color: '#2D3748'
},
'& h4': {
fontSize: '1.1em',
fontWeight: '600',
marginTop: '0.6em',
marginBottom: '0.3em',
color: '#4A5568'
},
// Параграфы
'& p': {
marginTop: '0.75em',
marginBottom: '0.75em',
lineHeight: '1.8'
},
// Списки
'& ul, & ol': {
marginLeft: '1.5em',
marginTop: '0.75em',
marginBottom: '0.75em',
paddingLeft: '0.5em'
},
'& li': {
marginTop: '0.4em',
marginBottom: '0.4em',
lineHeight: '1.7'
},
'& li > p': {
marginTop: '0.25em',
marginBottom: '0.25em'
},
// Инлайн-код
'& code': {
backgroundColor: '#EDF2F7',
color: '#C53030',
padding: '0.15em 0.4em',
borderRadius: '4px',
fontSize: '0.9em',
fontFamily: 'Monaco, Consolas, "Courier New", monospace',
fontWeight: '500'
},
// Блоки кода
'& pre': {
backgroundColor: '#1A202C',
color: '#E2E8F0',
padding: '1em 1.2em',
borderRadius: '8px',
overflowX: 'auto',
marginTop: '1em',
marginBottom: '1em',
border: '1px solid #2D3748',
fontSize: '0.9em',
lineHeight: '1.6'
},
'& pre code': {
backgroundColor: 'transparent',
color: '#E2E8F0',
padding: '0',
fontFamily: 'Monaco, Consolas, "Courier New", monospace'
},
// Цитаты
'& blockquote': {
borderLeft: '4px solid #4299E1',
paddingLeft: '1em',
paddingTop: '0.5em',
paddingBottom: '0.5em',
marginLeft: '0',
marginTop: '1em',
marginBottom: '1em',
fontStyle: 'italic',
color: '#4A5568',
backgroundColor: '#EBF8FF',
borderRadius: '0 4px 4px 0'
},
'& blockquote p': {
marginTop: '0.25em',
marginBottom: '0.25em'
},
// Ссылки
'& a': {
color: '#3182CE',
textDecoration: 'underline',
fontWeight: '500',
transition: 'color 0.2s',
'&:hover': {
color: '#2C5282'
}
},
// Горизонтальная линия
'& hr': {
border: 'none',
borderTop: '2px solid #E2E8F0',
marginTop: '1.5em',
marginBottom: '1.5em'
},
// Таблицы
'& table': {
borderCollapse: 'collapse',
width: '100%',
marginTop: '1em',
marginBottom: '1em',
fontSize: '0.95em'
},
'& table thead': {
backgroundColor: '#F7FAFC'
},
'& table th': {
border: '1px solid #E2E8F0',
padding: '0.75em 1em',
textAlign: 'left',
fontWeight: '600',
color: '#2D3748'
},
'& table td': {
border: '1px solid #E2E8F0',
padding: '0.75em 1em',
textAlign: 'left'
},
'& table tr:nth-of-type(even)': {
backgroundColor: '#F7FAFC'
},
// Выделение (strong, em)
'& strong': {
fontWeight: '600',
color: '#2D3748'
},
'& em': {
fontStyle: 'italic'
},
// Изображения
'& img': {
maxWidth: '100%',
height: 'auto',
borderRadius: '8px',
marginTop: '1em',
marginBottom: '1em'
}
}}
>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{pages[currentPage]}
</ReactMarkdown>
</Box>
{totalPages > 1 && (
<HStack justify="center" gap={2}>
<Button
size="sm"
variant="outline"
colorScheme="blue"
onClick={goToPrevious}
// @ts-expect-error Chakra UI v2 uses isDisabled
isDisabled={currentPage === 0}
leftIcon={<Text></Text>}
>
Предыдущая
</Button>
<Button
size="sm"
variant="outline"
colorScheme="blue"
onClick={goToNext}
// @ts-expect-error Chakra UI v2 uses isDisabled
isDisabled={currentPage === totalPages - 1}
rightIcon={<Text></Text>}
>
Следующая
</Button>
</HStack>
)}
</VStack>
</Box>
)
}