import { LevelPart } from "@/interfaces/exam"; import { useEffect, useRef, useState } from "react"; interface Props { part: LevelPart, contextWord: string | undefined, setContextWordLine: React.Dispatch> } const TextComponent: React.FC = ({ part, contextWord, setContextWordLine }) => { const textRef = useRef(null); const [lineNumbers, setLineNumbers] = useState([]); const [lineHeight, setLineHeight] = useState(0); const calculateLineNumbers = () => { if (textRef.current) { const computedStyle = window.getComputedStyle(textRef.current); const lineHeightValue = parseFloat(computedStyle.lineHeight); const containerWidth = textRef.current.clientWidth; setLineHeight(lineHeightValue); const offscreenElement = document.createElement('div'); offscreenElement.style.position = 'absolute'; offscreenElement.style.top = '-9999px'; offscreenElement.style.left = '-9999px'; offscreenElement.style.whiteSpace = 'pre-wrap'; offscreenElement.style.width = `${containerWidth}px`; offscreenElement.style.font = computedStyle.font; offscreenElement.style.lineHeight = computedStyle.lineHeight; offscreenElement.style.wordWrap = 'break-word'; offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign; const textContent = textRef.current.textContent || ''; const lines = textContent.split(/\n/).map(line => line.split(/(\s+)/).map(word => { const span = document.createElement('span'); span.textContent = word; return span; }) ); // Append all spans to offscreenElement lines.forEach(line => { line.forEach(span => offscreenElement.appendChild(span)); offscreenElement.appendChild(document.createElement('br')); }); document.body.appendChild(offscreenElement); const processedLines: string[][] = [[]]; let currentLine = 1; let currentLineTop: number | undefined; let contextWordLine: number | null = null; const firstChild = offscreenElement.firstChild as HTMLElement; if (firstChild) { currentLineTop = firstChild.getBoundingClientRect().top; } const spans = offscreenElement.querySelectorAll('span'); spans.forEach(span => { const rect = span.getBoundingClientRect(); const top = rect.top; if (currentLineTop !== undefined && top > currentLineTop) { currentLine++; currentLineTop = top; processedLines.push([]); } processedLines[processedLines.length - 1].push(span.textContent?.trim() || ''); if (contextWord && contextWordLine === null && span.textContent?.includes(contextWord)) { contextWordLine = currentLine; } }); setLineNumbers(processedLines.map((_, index) => index + 1)); if (contextWordLine) { setContextWordLine(contextWordLine); } document.body.removeChild(offscreenElement); } }; useEffect(() => { calculateLineNumbers(); const resizeObserver = new ResizeObserver(() => { calculateLineNumbers(); }); if (textRef.current) { // eslint-disable-next-line react-hooks/exhaustive-deps resizeObserver.observe(textRef.current); } return () => { if (textRef.current) { // eslint-disable-next-line react-hooks/exhaustive-deps resizeObserver.unobserve(textRef.current); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [part.context, contextWord]); /*if (typeof part.showContextLines === "undefined") { return (
{!!part.context && part.context .split(/\n|(\\n)/g) .filter((x) => x && x.length > 0 && x !== "\\n") .map((line, index) => (

{line}

))}
); }*/ return (
{lineNumbers.map(num => (
{num}
))}
); } export default TextComponent;