MathText display: formula in the middle of a Persian sentence gets its own centered line (quiz and lessons)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,15 @@ const EDGE_END = /[\s،؛:!؟«»"'.=]+$/;
|
|||||||
// سمتِ خودشان میکشیدند، پس فهرست باز برعکس میشد.
|
// سمتِ خودشان میکشیدند، پس فهرست باز برعکس میشد.
|
||||||
const LTR = { unicodeBidi: 'isolate-override', direction: 'ltr' } as const;
|
const LTR = { unicodeBidi: 'isolate-override', direction: 'ltr' } as const;
|
||||||
|
|
||||||
export function MathText({ text: raw }: { text: string }) {
|
// فرمولِ «واقعی» (نه عددِ تنها): نمادِ رابطه یا عمل دارد و دستِکم چهار نویسه است.
|
||||||
|
const FORMULA = /[=<>≤≥≠∈∉⊂⊆∪∩+×÷^√∑∫]|[0-9۰-۹A-Za-z)][\s]*[−\-/][\s]*[0-9۰-۹A-Za-z(]/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `display`: فرمولِ وسطِ جملهی فارسی در خطِ جدا و وسطچین مینشیند (سوالها و درسها)، تا
|
||||||
|
* چشم وسطِ فرمول به خطِ بعد نیفتد. عددِ تنها و عبارتِ کوتاهِ بدونِ نمادِ عمل داخلِ جمله میماند.
|
||||||
|
* آزموده نشده: صفحهی واقعیِ اپ در Chrome و Safari؛ فقط منطقِ تشخیص با نمونههای بانک سنجیده شد.
|
||||||
|
*/
|
||||||
|
export function MathText({ text: raw, display = false }: { text: string; display?: boolean }) {
|
||||||
const text = plainPersian(plainMath(raw));
|
const text = plainPersian(plainMath(raw));
|
||||||
const out: React.ReactNode[] = [];
|
const out: React.ReactNode[] = [];
|
||||||
const depth = bracketDepth(text);
|
const depth = bracketDepth(text);
|
||||||
@@ -56,7 +64,13 @@ export function MathText({ text: raw }: { text: string }) {
|
|||||||
run = edge.run;
|
run = edge.run;
|
||||||
if (!run || !HAS_MATH.test(run)) continue;
|
if (!run || !HAS_MATH.test(run)) continue;
|
||||||
out.push(text.slice(last, start + head.length));
|
out.push(text.slice(last, start + head.length));
|
||||||
|
const before = text.slice(0, start + head.length), after = text.slice(start + head.length + run.length);
|
||||||
|
const inSentence = new RegExp(`[${LETTER}]`).test(before) || new RegExp(`[${LETTER}]`).test(after);
|
||||||
|
if (display && inSentence && run.trim().length >= 4 && FORMULA.test(run) && depth[start + head.length] === 0) {
|
||||||
|
out.push(<span key={i++} style={{ ...LTR, display: 'block', textAlign: 'center', margin: '4px 0', fontWeight: 800 }}>{run}</span>);
|
||||||
|
} else {
|
||||||
out.push(<span key={i++} style={LTR}>{run}</span>);
|
out.push(<span key={i++} style={LTR}>{run}</span>);
|
||||||
|
}
|
||||||
last = start + head.length + run.length;
|
last = start + head.length + run.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,17 +28,17 @@ export function parseNumbered(text: string): { lead: string; items: string[] } |
|
|||||||
|
|
||||||
export function StepText({ text, color, className = '' }: { text: string; color: string; className?: string }) {
|
export function StepText({ text, color, className = '' }: { text: string; color: string; className?: string }) {
|
||||||
const list = parseNumbered(text);
|
const list = parseNumbered(text);
|
||||||
if (!list) return <p className={`whitespace-pre-line ${className}`}><MathText text={text} /></p>;
|
if (!list) return <p className={`whitespace-pre-line ${className}`}><MathText text={text} display /></p>;
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{list.lead && <p className="mb-2 whitespace-pre-line"><MathText text={list.lead} /></p>}
|
{list.lead && <p className="mb-2 whitespace-pre-line"><MathText text={list.lead} display /></p>}
|
||||||
<ol className="flex flex-col gap-2.5">
|
<ol className="flex flex-col gap-2.5">
|
||||||
{list.items.map((item, i) => (
|
{list.items.map((item, i) => (
|
||||||
<li key={i} className="flex gap-2.5">
|
<li key={i} className="flex gap-2.5">
|
||||||
<span className="mt-0.5 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full text-xs font-black text-white" style={{ backgroundColor: color }}>
|
<span className="mt-0.5 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full text-xs font-black text-white" style={{ backgroundColor: color }}>
|
||||||
{toFa(i + 1)}
|
{toFa(i + 1)}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 whitespace-pre-line"><MathText text={item} /></span>
|
<span className="flex-1 whitespace-pre-line"><MathText text={item} display /></span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function LessonScreen({ lessonId, nodeId }: { lessonId: number; nodeId?:
|
|||||||
<span className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full text-xs font-black text-white" style={{ backgroundColor: color }}>
|
<span className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full text-xs font-black text-white" style={{ backgroundColor: color }}>
|
||||||
{toFa(i + 1)}
|
{toFa(i + 1)}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 whitespace-pre-line"><MathText text={step} />{figs('step', i)}</span>
|
<span className="flex-1 whitespace-pre-line"><MathText text={step} display />{figs('step', i)}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
|
|||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<span className="text-xs font-medium text-muted">سوال {toFa(index + 1)}</span>
|
<span className="text-xs font-medium text-muted">سوال {toFa(index + 1)}</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="whitespace-pre-line text-base font-bold leading-8 text-ink"><MathText text={question.prompt} /></h2>
|
<h2 className="whitespace-pre-line text-base font-bold leading-8 text-ink"><MathText text={question.prompt} display /></h2>
|
||||||
{question.figure ? <div className="mt-3"><Diagram spec={question.figure} /></div> : null}
|
{question.figure ? <div className="mt-3"><Diagram spec={question.figure} /></div> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user