Skip to content

Commit e805063

Browse files
committed
fix: inline markdown-it-katex, fix #134
1 parent e277e9e commit e805063

File tree

9 files changed

+224
-22
lines changed

9 files changed

+224
-22
lines changed

demo/test/slides.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,14 @@ Current Page: {{$slidev.nav.currentPage}}
5858
<div>
5959
Hi
6060
</div>
61+
62+
---
63+
64+
# Page 5
65+
66+
$$
67+
\begin{aligned}
68+
\frac{D \boldsymbol{v}}{D t}=&-\frac{1}{\rho} \operatorname{grad} p+\frac{\mu}{\rho} \Delta \boldsymbol{v}+\frac{\lambda+\mu}{\rho} \operatorname{grad} \Theta+\frac{\Theta}{\rho} \operatorname{grad}(\lambda+\mu) \\
69+
&+\frac{1}{\rho} \operatorname{grad}(\boldsymbol{v} \cdot \operatorname{grad} \mu)+\frac{1}{\rho} \operatorname{rot}(\boldsymbol{v} \times \operatorname{grad} \mu)-\frac{1}{\rho} \boldsymbol{v} \Delta \mu+\boldsymbol{g}
70+
\end{aligned}
71+
$$

packages/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"file-saver": "^2.0.5",
2626
"js-base64": "^3.6.0",
2727
"js-yaml": "^4.1.0",
28+
"katex": "^0.13.11",
2829
"mermaid": "8.5.0",
2930
"monaco-editor": "^0.24.0",
3031
"nanoid": "^3.1.23",

packages/slidev/node/common.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ export async function getIndexHtml({ clientRoot, themeRoots, data, userRoot }: R
2525
body += `\n${(index.match(/<body>([\s\S]*?)<\/body>/im)?.[1] || '').trim()}`
2626
}
2727

28-
if (data.features.katex)
29-
head += '\n<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css">'
30-
3128
if (data.features.tweet)
3229
body += '\n<script src="https://platform.twitter.com/widgets.js"></script>'
3330

packages/slidev/node/plugins/loaders.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ export function createSlidesLoader(
330330
}
331331
}
332332

333+
if (data.features.katex)
334+
imports.push('import "katex/dist/katex.min.css"')
335+
333336
return imports.join('\n')
334337
}
335338

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Ported from https://github.com/waylonflinn/markdown-it-katex
2+
3+
/* Process inline math */
4+
/*
5+
Like markdown-it-simplemath, this is a stripped down, simplified version of:
6+
https://github.com/runarberg/markdown-it-math
7+
8+
It differs in that it takes (a subset of) LaTeX as input and relies on KaTeX
9+
for rendering output.
10+
*/
11+
12+
import katex, { KatexOptions } from 'katex'
13+
14+
// Test if potential opening or closing delimieter
15+
// Assumes that there is a "$" at state.src[pos]
16+
function isValidDelim(state: any, pos: number) {
17+
const max = state.posMax
18+
let can_open = true
19+
let can_close = true
20+
21+
const prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1
22+
const nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1
23+
24+
// Check non-whitespace conditions for opening and closing, and
25+
// check that closing delimeter isn't followed by a number
26+
if (prevChar === 0x20/* " " */ || prevChar === 0x09
27+
||/* \t */ (nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */))
28+
can_close = false
29+
30+
if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */)
31+
can_open = false
32+
33+
return {
34+
can_open,
35+
can_close,
36+
}
37+
}
38+
39+
function math_inline(state: any, silent: boolean) {
40+
let match, token, res, pos
41+
42+
if (state.src[state.pos] !== '$') return false
43+
44+
res = isValidDelim(state, state.pos)
45+
if (!res.can_open) {
46+
if (!silent) state.pending += '$'
47+
state.pos += 1
48+
return true
49+
}
50+
51+
// First check for and bypass all properly escaped delimieters
52+
// This loop will assume that the first leading backtick can not
53+
// be the first character in state.src, which is known since
54+
// we have found an opening delimieter already.
55+
const start = state.pos + 1
56+
match = start
57+
// eslint-disable-next-line no-cond-assign
58+
while ((match = state.src.indexOf('$', match)) !== -1) {
59+
// Found potential $, look for escapes, pos will point to
60+
// first non escape when complete
61+
pos = match - 1
62+
while (state.src[pos] === '\\') pos -= 1
63+
64+
// Even number of escapes, potential closing delimiter found
65+
if (((match - pos) % 2) === 1) break
66+
match += 1
67+
}
68+
69+
// No closing delimter found. Consume $ and continue.
70+
if (match === -1) {
71+
if (!silent) state.pending += '$'
72+
state.pos = start
73+
return true
74+
}
75+
76+
// Check if we have empty content, ie: $$. Do not parse.
77+
if (match - start === 0) {
78+
if (!silent) state.pending += '$$'
79+
state.pos = start + 1
80+
return true
81+
}
82+
83+
// Check for valid closing delimiter
84+
res = isValidDelim(state, match)
85+
if (!res.can_close) {
86+
if (!silent) state.pending += '$'
87+
state.pos = start
88+
return true
89+
}
90+
91+
if (!silent) {
92+
token = state.push('math_inline', 'math', 0)
93+
token.markup = '$'
94+
token.content = state.src.slice(start, match)
95+
}
96+
97+
state.pos = match + 1
98+
return true
99+
}
100+
101+
function math_block(state: any, start: number, end: number, silent: boolean) {
102+
let firstLine; let lastLine; let next; let lastPos
103+
let found = false
104+
let pos = state.bMarks[start] + state.tShift[start]
105+
let max = state.eMarks[start]
106+
107+
if (pos + 2 > max) return false
108+
if (state.src.slice(pos, pos + 2) !== '$$') return false
109+
110+
pos += 2
111+
firstLine = state.src.slice(pos, max)
112+
113+
if (silent) return true
114+
if (firstLine.trim().slice(-2) === '$$') {
115+
// Single line expression
116+
firstLine = firstLine.trim().slice(0, -2)
117+
found = true
118+
}
119+
120+
for (next = start; !found;) {
121+
next++
122+
123+
if (next >= end) break
124+
125+
pos = state.bMarks[next] + state.tShift[next]
126+
max = state.eMarks[next]
127+
128+
if (pos < max && state.tShift[next] < state.blkIndent) {
129+
// non-empty line with negative indent should stop the list:
130+
break
131+
}
132+
133+
if (state.src.slice(pos, max).trim().slice(-2) === '$$') {
134+
lastPos = state.src.slice(0, max).lastIndexOf('$$')
135+
lastLine = state.src.slice(pos, lastPos)
136+
found = true
137+
}
138+
}
139+
140+
state.line = next + 1
141+
142+
const token = state.push('math_block', 'math', 0)
143+
token.block = true
144+
token.content = (firstLine && firstLine.trim() ? `${firstLine}\n` : '')
145+
+ state.getLines(start + 1, next, state.tShift[start], true)
146+
+ (lastLine && lastLine.trim() ? lastLine : '')
147+
token.map = [start, state.line]
148+
token.markup = '$$'
149+
return true
150+
}
151+
152+
export default function math_plugin(md: any, options: KatexOptions) {
153+
// Default options
154+
155+
options = options || {}
156+
157+
// set KaTeX as the renderer for markdown-it-simplemath
158+
const katexInline = function(latex: string) {
159+
options.displayMode = false
160+
try {
161+
return katex.renderToString(latex, options)
162+
}
163+
catch (error) {
164+
if (options.throwOnError)
165+
// eslint-disable-next-line no-console
166+
console.warn(error)
167+
return latex
168+
}
169+
}
170+
171+
const inlineRenderer = function(tokens: any, idx: number) {
172+
return katexInline(tokens[idx].content)
173+
}
174+
175+
const katexBlock = function(latex: string) {
176+
options.displayMode = true
177+
try {
178+
return `<p>${katex.renderToString(latex, options)}</p>`
179+
}
180+
catch (error) {
181+
if (options.throwOnError)
182+
// eslint-disable-next-line no-console
183+
console.warn(error)
184+
return latex
185+
}
186+
}
187+
188+
const blockRenderer = function(tokens: any, idx: number) {
189+
return `${katexBlock(tokens[idx].content)}\n`
190+
}
191+
192+
md.inline.ruler.after('escape', 'math_inline', math_inline)
193+
md.block.ruler.after('blockquote', 'math_block', math_block, {
194+
alt: ['paragraph', 'reference', 'blockquote', 'list'],
195+
})
196+
md.renderer.rules.math_inline = inlineRenderer
197+
md.renderer.rules.math_block = blockRenderer
198+
}

packages/slidev/node/plugins/markdown.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ import base64 from 'js-base64'
66
import { slash } from '@antfu/utils'
77
// @ts-expect-error
88
import mila from 'markdown-it-link-attributes'
9-
// @ts-expect-error
10-
import Katex from 'markdown-it-katex'
119
import type { KatexOptions } from 'katex'
1210
import { ResolvedSlidevOptions, SlidevPluginOptions } from '../options'
11+
import Katex from './markdown-it-katex'
1312
import { loadSetups } from './setupNode'
1413
import Prism from './markdown-it-prism'
1514
import Shiki, { resolveShikiOptions } from './markdown-it-shiki'

packages/slidev/node/plugins/preset.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const customElements = new Set([
3333
'msqrt',
3434
'mtr',
3535
'semantics',
36+
'mstyle',
37+
'mtext',
3638
])
3739

3840
export async function ViteSlidevPlugin(

packages/slidev/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@
6262
"fs-extra": "^10.0.0",
6363
"jiti": "^1.9.2",
6464
"js-base64": "^3.6.0",
65+
"katex": "^0.13.11",
6566
"kolorist": "^1.4.1",
6667
"markdown-it": "^12.0.6",
67-
"markdown-it-katex": "^2.0.3",
6868
"markdown-it-link-attributes": "^3.0.0",
6969
"monaco-editor": "^0.24.0",
7070
"nanoid": "^3.1.23",

pnpm-lock.yaml

Lines changed: 7 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)