mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(reply modal): add sci tex preview button (#1170)
* feat(sci): add 4chan-style TeX support with MathJax on /sci/ - [math]/[eqn] tags typeset with MathJax 3 (lazy chunk, only on /sci/ with math present) - 4chan-identical config: Safe mode, left-aligned eqn, neutered \color/\newcommand macros - TeX button in reply modal title bar opens live TeX Preview modal - /sci/ post form rules bullets for [math]/[eqn] usage and right-click source - MathJax context menu on right-click (Show Math As > TeX Commands) - woff fonts served from node_modules in dev and emitted into build * feat(sci): finish TeX support: configmacros fix, preview preload, translations, tests - add configmacros package so the 4chan macro neutering (\color, \newcommand, ...) applies - preload MathJax when the TeX Preview opens, like 4chan - stable closeModal callback for the preview modal - pre-bundle mathjax components in vite optimizeDeps to avoid dev mid-session reload - translate the 6 new TeX keys into all 35 languages - markdown math segment component tests + math-tags unit tests * feat(reply modal): add sci tex preview button * fix(tex-preview): clear MathJax bookkeeping and pending typeset on close Addresses Cursor Bugbot: the preview output was typeset via typesetMathElement but never passed to clearMathElement on unmount, so repeated open/close cycles kept detached nodes in MathJax's math list. Also cancels the pending debounce timer. * fix(reply-modal): reset TeX preview state when the reply modal closes Addresses CodeRabbit: showTexPreview persisted across close/reopen like the bbcode preview flags, so the TeX preview would auto-open on the next reply.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { HAS_MATH_TAG_REGEX, isMathDirectoryCode, splitMathSegments } from '../math-tags';
|
||||
|
||||
describe('math-tags', () => {
|
||||
it('enables math tags only for /sci/', () => {
|
||||
expect(isMathDirectoryCode('sci')).toBe(true);
|
||||
expect(isMathDirectoryCode('SCI')).toBe(true);
|
||||
expect(isMathDirectoryCode('g')).toBe(false);
|
||||
expect(isMathDirectoryCode('b')).toBe(false);
|
||||
expect(isMathDirectoryCode(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('detects closed math and eqn tags', () => {
|
||||
expect(HAS_MATH_TAG_REGEX.test('[math]x[/math]')).toBe(true);
|
||||
expect(HAS_MATH_TAG_REGEX.test('[eqn]\\int x dx[/eqn]')).toBe(true);
|
||||
expect(HAS_MATH_TAG_REGEX.test('[math]unclosed')).toBe(false);
|
||||
expect(HAS_MATH_TAG_REGEX.test('[math]mismatch[/eqn]')).toBe(false);
|
||||
expect(HAS_MATH_TAG_REGEX.test('no tags at all')).toBe(false);
|
||||
});
|
||||
|
||||
it('splits text and math segments with offsets, keeping the delimiters', () => {
|
||||
expect(splitMathSegments('a [math]x_1[/math] b')).toEqual([
|
||||
{ type: 'text', value: 'a ', start: 0 },
|
||||
{ type: 'math', value: '[math]x_1[/math]', start: 2 },
|
||||
{ type: 'text', value: ' b', start: 18 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps multi-line eqn content in one math segment', () => {
|
||||
const raw = 'before\n[eqn]\\begin{pmatrix}a & b\\\\c & d\\end{pmatrix}[/eqn]\nafter';
|
||||
const segments = splitMathSegments(raw);
|
||||
expect(segments.map((segment) => segment.type)).toEqual(['text', 'math', 'text']);
|
||||
expect(segments[1].value).toContain('pmatrix');
|
||||
expect(segments[1].value).toContain('\\\\');
|
||||
});
|
||||
|
||||
it('leaves unclosed or mismatched tags as plain text', () => {
|
||||
expect(splitMathSegments('[math]x')).toEqual([{ type: 'text', value: '[math]x', start: 0 }]);
|
||||
expect(splitMathSegments('[math]x[/eqn]')).toEqual([{ type: 'text', value: '[math]x[/eqn]', start: 0 }]);
|
||||
});
|
||||
|
||||
it('handles back-to-back and repeated math segments', () => {
|
||||
expect(splitMathSegments('[math]a[/math][eqn]b[/eqn]')).toEqual([
|
||||
{ type: 'math', value: '[math]a[/math]', start: 0 },
|
||||
{ type: 'math', value: '[eqn]b[/eqn]', start: 14 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not extract math inside spoilers so spoiler parsing keeps working', () => {
|
||||
expect(splitMathSegments('[spoiler]a [math]x[/math][/spoiler]')).toEqual([
|
||||
{ type: 'text', value: '[spoiler]a [math]x[/math][/spoiler]', start: 0 },
|
||||
]);
|
||||
const mixed = splitMathSegments('[spoiler][math]a[/math][/spoiler] [math]b[/math]');
|
||||
expect(mixed.map((segment) => segment.type)).toEqual(['text', 'math']);
|
||||
expect(mixed[1].value).toBe('[math]b[/math]');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user