mirror of
https://github.com/HananoshikaYomaru/openfreebooks.git
synced 2026-11-13 21:15:54 +00:00
Refactor math rendering: Replace direct KaTeX imports with a centralized module for rendering math expressions across widgets. Update CONTRIBUTING.md with new guidelines for using KaTeX in HTML and widgets. Enhance TypeScript configuration for path resolution of the KaTeX module. Update curriculum status for linear equations and improve math rendering logic in the frontend.
This commit is contained in:
parent
7636bb4a86
commit
ede9a7aa63
28 changed files with 2289 additions and 111 deletions
62
frontend/src/lib/katex.ts
Normal file
62
frontend/src/lib/katex.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import katex from "katex";
|
||||
import renderMathInElement from "katex/contrib/auto-render";
|
||||
import "katex/dist/katex.min.css";
|
||||
|
||||
export type RenderLatexOptions = {
|
||||
displayMode?: boolean;
|
||||
};
|
||||
|
||||
const DELIMITERS = [
|
||||
{ left: "$$", right: "$$", display: true },
|
||||
{ left: "\\(", right: "\\)", display: false },
|
||||
{ left: "\\[", right: "\\]", display: true },
|
||||
];
|
||||
|
||||
const AUTO_RENDER_OPTIONS = {
|
||||
delimiters: DELIMITERS,
|
||||
throwOnError: false,
|
||||
strict: "ignore" as const,
|
||||
};
|
||||
|
||||
/** Prose and question blocks that may contain \\( … \\) or \\[ … \\] delimiters. */
|
||||
const AUTO_RENDER_TARGETS =
|
||||
".book-prose, .book-callout, .book-formula, .book-question__prompt, .book-question__solution, .math-widget__caption";
|
||||
|
||||
/**
|
||||
* Render a LaTeX string to HTML for use with Solid `innerHTML` (widgets, dynamic labels).
|
||||
*/
|
||||
export function renderLatex(latex: string, options: RenderLatexOptions = {}) {
|
||||
return katex.renderToString(latex, {
|
||||
throwOnError: false,
|
||||
strict: "ignore",
|
||||
displayMode: options.displayMode ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-render delimiter-based math inside a single element (idempotent per element).
|
||||
*/
|
||||
export function renderMathInContainer(el: HTMLElement) {
|
||||
if (el.dataset.katexRendered === "true") return;
|
||||
renderMathInElement(el, AUTO_RENDER_OPTIONS);
|
||||
el.dataset.katexRendered = "true";
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan chapter / book content for math delimiters. Skips `.math-widget` trees (widgets use renderLatex).
|
||||
*/
|
||||
export function renderBookMath(root: ParentNode = document) {
|
||||
const article =
|
||||
root instanceof Document
|
||||
? root.querySelector<HTMLElement>("[data-pagefind-body]")
|
||||
: root instanceof HTMLElement && root.matches("[data-pagefind-body]")
|
||||
? root
|
||||
: root.querySelector<HTMLElement>("[data-pagefind-body]");
|
||||
|
||||
const scope: ParentNode = article ?? root;
|
||||
|
||||
scope.querySelectorAll<HTMLElement>(AUTO_RENDER_TARGETS).forEach((el) => {
|
||||
if (el.closest(".math-widget")) return;
|
||||
renderMathInContainer(el);
|
||||
});
|
||||
}
|
||||
|
|
@ -1,24 +1,3 @@
|
|||
import renderMathInElement from "katex/contrib/auto-render";
|
||||
import "katex/dist/katex.min.css";
|
||||
|
||||
const DELIMITERS = [
|
||||
{ left: "$$", right: "$$", display: true },
|
||||
{ left: "\\(", right: "\\)", display: false },
|
||||
{ left: "\\[", right: "\\]", display: true },
|
||||
];
|
||||
|
||||
const RENDER_OPTIONS = {
|
||||
delimiters: DELIMITERS,
|
||||
throwOnError: false,
|
||||
strict: "ignore" as const,
|
||||
};
|
||||
|
||||
export function renderMathInContainer(el: HTMLElement) {
|
||||
renderMathInElement(el, RENDER_OPTIONS);
|
||||
}
|
||||
|
||||
export function renderBookMath() {
|
||||
document
|
||||
.querySelectorAll<HTMLElement>(".book-prose, .book-callout, .math-widget__caption")
|
||||
.forEach(renderMathInContainer);
|
||||
}
|
||||
/** @deprecated Import from `./katex` instead. Kept for existing paths. */
|
||||
export { renderBookMath, renderLatex, renderMathInContainer } from "./katex";
|
||||
export type { RenderLatexOptions } from "./katex";
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ function mountFooterYear() {
|
|||
}
|
||||
}
|
||||
|
||||
function scheduleChapterMath() {
|
||||
void import("./lib/render-math").then(({ renderBookMath }) => {
|
||||
function scheduleBookMath() {
|
||||
void import("./lib/katex").then(({ renderBookMath }) => {
|
||||
requestAnimationFrame(() => renderBookMath());
|
||||
});
|
||||
}
|
||||
|
|
@ -50,11 +50,7 @@ function mountChapterWidgets() {
|
|||
const mounts = document.querySelectorAll<HTMLElement>("[data-widget]");
|
||||
if (!article || !chapterKey) return;
|
||||
|
||||
// KaTeX must run on widget-less chapters too (renderBookMath was only chained after widgets).
|
||||
if (mounts.length === 0) {
|
||||
scheduleChapterMath();
|
||||
return;
|
||||
}
|
||||
if (mounts.length === 0) return;
|
||||
|
||||
void import("./generated/chapter-widgets").then(({ chapterWidgetLoaders }) => {
|
||||
const loadPromises: Promise<void>[] = [];
|
||||
|
|
@ -76,11 +72,18 @@ function mountChapterWidgets() {
|
|||
}
|
||||
|
||||
void Promise.all(loadPromises).then(() => {
|
||||
scheduleChapterMath();
|
||||
scheduleBookMath();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Prose math runs immediately; widgets re-run after mount for any new captions. */
|
||||
function initBookMath() {
|
||||
if (document.querySelector("[data-pagefind-body]")) {
|
||||
scheduleBookMath();
|
||||
}
|
||||
}
|
||||
|
||||
initTheme();
|
||||
|
||||
const config = readConfig();
|
||||
|
|
@ -131,6 +134,7 @@ if (chapterNavRoot) {
|
|||
});
|
||||
}
|
||||
|
||||
initBookMath();
|
||||
mountChapterWidgets();
|
||||
|
||||
const catalogRoot = document.getElementById("catalog-app");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue