From b8fb67115eba884e2c98de06ccb4e71fadb6c9d5 Mon Sep 17 00:00:00 2001 From: a2nr Date: Sun, 26 Apr 2026 15:47:11 +0700 Subject: [PATCH] feat: implement LaTeX support with KaTeX and python-markdown-math 3 - Integration of KaTeX in frontend (SvelteKit) with a custom renderMath` action. - Added `python-markdown-math` extension in backend (Flask). to handle math blocks. - Configured Vite to handle KaTeX during SSR and browser rendering. - Added a LaTeX test lesson and updated home navigation. - Supported both inline ($) and block ($$) math rendering, including multi-line support. --- frontend/package.json | 2 + frontend/src/app.html | 1 + frontend/src/lib/actions/renderMath.ts | 59 +++++++++++++++++++ frontend/src/routes/+page.svelte | 3 +- .../src/routes/lesson/[slug]/+page.svelte | 7 ++- frontend/vite.config.ts | 3 + requirements.txt | 3 +- services/lesson_service.py | 4 +- 8 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 frontend/src/lib/actions/renderMath.ts diff --git a/frontend/package.json b/frontend/package.json index d5449b6..5f5c660 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@sveltejs/adapter-node": "^5.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@types/katex": "^0.16.7", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "^5.0.0", @@ -23,6 +24,7 @@ "@codemirror/theme-one-dark": "^6.1.0", "codemirror": "^6.0.1", "highlight.js": "^11.11.0", + "katex": "^0.16.9", "marked": "^15.0.0" } } diff --git a/frontend/src/app.html b/frontend/src/app.html index cb664d7..a1c50e5 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -6,6 +6,7 @@ + %sveltekit.head% diff --git a/frontend/src/lib/actions/renderMath.ts b/frontend/src/lib/actions/renderMath.ts new file mode 100644 index 0000000..24e93d9 --- /dev/null +++ b/frontend/src/lib/actions/renderMath.ts @@ -0,0 +1,59 @@ +import { browser } from '$app/environment'; +import katex from 'katex'; + +export async function autoRenderMath(node: HTMLElement) { + if (!browser) return; + + try { + const renderMathInElement = (await import('katex/dist/contrib/auto-render')).default; + await import('katex/dist/katex.min.css'); + + // 1. First, handle the standard delimiters + renderMathInElement(node, { + delimiters: [ + { left: '$$', right: '$$', display: true }, + { left: '$', right: '$', display: false }, + { left: '\\(', right: '\\)', display: false }, + { left: '\\[', right: '\\]', display: true } + ], + throwOnError: false + }); + + // 2. Then, handle @@ -10,7 +11,7 @@ {#if data.homeContent} -
+
{@html data.homeContent}
{/if} diff --git a/frontend/src/routes/lesson/[slug]/+page.svelte b/frontend/src/routes/lesson/[slug]/+page.svelte index 38a3ff9..7f90861 100644 --- a/frontend/src/routes/lesson/[slug]/+page.svelte +++ b/frontend/src/routes/lesson/[slug]/+page.svelte @@ -16,6 +16,7 @@ import { createFloatingPanel } from '$actions/floatingPanel.svelte'; import { highlightAllCode } from '$actions/highlightCode'; import { renderCircuitEmbeds } from '$actions/renderCircuitEmbeds'; + import { renderMath, autoRenderMath } from '$lib/actions/renderMath'; import { tick, untrack } from 'svelte'; import type { LessonContent } from '$types/lesson'; @@ -279,10 +280,12 @@ if (contentEl) { highlightAllCode(contentEl); renderCircuitEmbeds(contentEl); + autoRenderMath(contentEl); } if (tabsEl) { highlightAllCode(tabsEl); renderCircuitEmbeds(tabsEl); + autoRenderMath(tabsEl); } }); } @@ -821,7 +824,7 @@
-
e.preventDefault()} @@ -869,7 +872,7 @@ /> -
+
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 27eadf5..7873598 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -3,6 +3,9 @@ import { defineConfig } from 'vite'; export default defineConfig({ plugins: [sveltekit()], + ssr: { + noExternal: ['katex'] + }, server: { proxy: { '/api': { diff --git a/requirements.txt b/requirements.txt index 79c7dec..20d1f03 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ gunicorn==21.2.0 flask-cors==4.0.0 python-dotenv==1.0.0 requests==2.31.0 -flask-limiter==3.5.0 \ No newline at end of file +flask-limiter==3.5.0 +python-markdown-math==0.8 \ No newline at end of file diff --git a/services/lesson_service.py b/services/lesson_service.py index b492552..a8db89b 100644 --- a/services/lesson_service.py +++ b/services/lesson_service.py @@ -195,7 +195,7 @@ def get_ordered_lessons_with_learning_objectives(progress=None): # Markdown rendering # --------------------------------------------------------------------------- -MD_EXTENSIONS = ['fenced_code', 'tables', 'nl2br', 'toc'] +MD_EXTENSIONS = ['fenced_code', 'tables', 'nl2br', 'toc', 'mdx_math'] def _process_circuit_embeds(text): @@ -398,4 +398,4 @@ def render_home_content(): parts = home_content.split('---Available_Lessons---') main_content = parts[0] if parts else home_content - return md.markdown(main_content, extensions=['fenced_code', 'tables']) + return md.markdown(main_content, extensions=['fenced_code', 'tables', 'mdx_math'])