@humanspeak/svelte-markdown 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -33
- package/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/katex/KatexRenderer.svelte +40 -0
- package/dist/extensions/katex/KatexRenderer.svelte.d.ts +31 -0
- package/dist/extensions/katex/index.d.ts +3 -0
- package/dist/extensions/katex/index.js +2 -0
- package/dist/extensions/katex/markedKatex.d.ts +70 -0
- package/dist/extensions/katex/markedKatex.js +159 -0
- package/dist/types.d.ts +28 -1
- package/package.json +26 -23
package/README.md
CHANGED
|
@@ -356,22 +356,34 @@ Both approaches work for any tag name. Snippet overrides take precedence over co
|
|
|
356
356
|
|
|
357
357
|
## Marked Extensions
|
|
358
358
|
|
|
359
|
-
Use
|
|
359
|
+
Use [marked extensions](https://marked.js.org/using_advanced#extensions) via the `extensions` prop. SvelteMarkdown ships first-class extensions for KaTeX, Mermaid, GitHub-style alerts, and footnotes from the `@humanspeak/svelte-markdown/extensions` subpath — no third-party packages required. Third-party extensions still work too; the component handles registering tokenizers internally and you just provide renderers for the custom token types.
|
|
360
360
|
|
|
361
361
|
### KaTeX Math Rendering
|
|
362
362
|
|
|
363
|
+
The package includes built-in `markedKatex` and `KatexRenderer` helpers. Install `katex` as an optional peer dependency and load its CSS:
|
|
364
|
+
|
|
363
365
|
```bash
|
|
364
|
-
npm install
|
|
366
|
+
npm install katex
|
|
365
367
|
```
|
|
366
368
|
|
|
369
|
+
**Default delimiter set** (mirrors KaTeX's own [`auto-render`](https://katex.org/docs/autorender.html) defaults):
|
|
370
|
+
|
|
371
|
+
| Delimiter pair | Level | `displayMode` |
|
|
372
|
+
| -------------------------------------------------------------- | ------ | ------------- |
|
|
373
|
+
| `\(...\)` | inline | `false` |
|
|
374
|
+
| `\[...\]` (own-line) | block | `true` |
|
|
375
|
+
| `$$...$$` (own-line) | block | `true` |
|
|
376
|
+
| `\begin{equation}...\end{equation}` and other AMS environments | block | `true` |
|
|
377
|
+
|
|
378
|
+
Single-dollar inline (`$x^2$`) is **off** by default — KaTeX itself excludes it from auto-render to avoid currency-string clashes like `$5,000`. Pass `{ singleDollarInline: true }` to enable it; it uses a whitespace-bounded rule so currency strings still won't match.
|
|
379
|
+
|
|
367
380
|
**Component renderer approach:**
|
|
368
381
|
|
|
369
382
|
```svelte
|
|
370
383
|
<script lang="ts">
|
|
371
384
|
import SvelteMarkdown from '@humanspeak/svelte-markdown'
|
|
372
385
|
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
|
|
373
|
-
import markedKatex from '
|
|
374
|
-
import KatexRenderer from './KatexRenderer.svelte'
|
|
386
|
+
import { markedKatex, KatexRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
375
387
|
|
|
376
388
|
interface KatexRenderers extends Renderers {
|
|
377
389
|
inlineKatex: RendererComponent
|
|
@@ -385,56 +397,45 @@ npm install marked-katex-extension katex
|
|
|
385
397
|
</script>
|
|
386
398
|
|
|
387
399
|
<svelte:head>
|
|
388
|
-
<link
|
|
400
|
+
<link
|
|
401
|
+
rel="stylesheet"
|
|
402
|
+
href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css"
|
|
403
|
+
crossorigin="anonymous"
|
|
404
|
+
/>
|
|
389
405
|
</svelte:head>
|
|
390
406
|
|
|
391
407
|
<SvelteMarkdown
|
|
392
|
-
source=
|
|
393
|
-
extensions={[markedKatex(
|
|
408
|
+
source={`Euler's identity: \\(e^{i\\pi} + 1 = 0\\)`}
|
|
409
|
+
extensions={[markedKatex()]}
|
|
394
410
|
{renderers}
|
|
395
411
|
/>
|
|
396
412
|
```
|
|
397
413
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
```svelte
|
|
401
|
-
<script lang="ts">
|
|
402
|
-
import katex from 'katex'
|
|
403
|
-
|
|
404
|
-
interface Props {
|
|
405
|
-
text: string
|
|
406
|
-
displayMode?: boolean
|
|
407
|
-
}
|
|
408
|
-
const { text, displayMode = false }: Props = $props()
|
|
409
|
-
|
|
410
|
-
const html = $derived(katex.renderToString(text, { throwOnError: false, displayMode }))
|
|
411
|
-
</script>
|
|
412
|
-
|
|
413
|
-
{@html html}
|
|
414
|
-
```
|
|
414
|
+
`KatexRenderer` hardcodes `throwOnError: false` so a single malformed expression renders as a tinted error span instead of throwing — if you need stricter behavior, supply your own component for the `inlineKatex` / `blockKatex` keys.
|
|
415
415
|
|
|
416
416
|
**Snippet override approach** (no separate component file needed):
|
|
417
417
|
|
|
418
418
|
```svelte
|
|
419
419
|
<script lang="ts">
|
|
420
420
|
import SvelteMarkdown from '@humanspeak/svelte-markdown'
|
|
421
|
+
import { markedKatex } from '@humanspeak/svelte-markdown/extensions'
|
|
421
422
|
import katex from 'katex'
|
|
422
|
-
import markedKatex from 'marked-katex-extension'
|
|
423
423
|
</script>
|
|
424
424
|
|
|
425
425
|
<svelte:head>
|
|
426
|
-
<link
|
|
426
|
+
<link
|
|
427
|
+
rel="stylesheet"
|
|
428
|
+
href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css"
|
|
429
|
+
crossorigin="anonymous"
|
|
430
|
+
/>
|
|
427
431
|
</svelte:head>
|
|
428
432
|
|
|
429
|
-
<SvelteMarkdown
|
|
430
|
-
source="Euler's identity: $e^{{i\pi}} + 1 = 0$"
|
|
431
|
-
extensions={[markedKatex({ throwOnError: false })]}
|
|
432
|
-
>
|
|
433
|
+
<SvelteMarkdown source={`Euler's identity: \\(e^{i\\pi} + 1 = 0\\)`} extensions={[markedKatex()]}>
|
|
433
434
|
{#snippet inlineKatex(props)}
|
|
434
|
-
{@html katex.renderToString(props.text, { displayMode: false })}
|
|
435
|
+
{@html katex.renderToString(props.text, { throwOnError: false, displayMode: false })}
|
|
435
436
|
{/snippet}
|
|
436
437
|
{#snippet blockKatex(props)}
|
|
437
|
-
{@html katex.renderToString(props.text, { displayMode: true })}
|
|
438
|
+
{@html katex.renderToString(props.text, { throwOnError: false, displayMode: true })}
|
|
438
439
|
{/snippet}
|
|
439
440
|
</SvelteMarkdown>
|
|
440
441
|
```
|
|
@@ -575,7 +576,7 @@ Marked extensions define custom token types with a `name` property (e.g., `inlin
|
|
|
575
576
|
To find the token type names for any extension, check its source or documentation for the `name` field in its `extensions` array:
|
|
576
577
|
|
|
577
578
|
```js
|
|
578
|
-
// Example:
|
|
579
|
+
// Example: markedKatex (built-in) registers tokens named "inlineKatex" and "blockKatex"
|
|
579
580
|
// → use renderers={{ inlineKatex: ..., blockKatex: ... }}
|
|
580
581
|
// → or {#snippet inlineKatex(props)} and {#snippet blockKatex(props)}
|
|
581
582
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* import { markedMermaid, MermaidRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
8
8
|
* import { markedAlert, AlertRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
9
9
|
* import { markedFootnote, FootnoteRef, FootnoteSection } from '@humanspeak/svelte-markdown/extensions'
|
|
10
|
+
* import { markedKatex, KatexRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
10
11
|
* ```
|
|
11
12
|
*
|
|
12
13
|
* @module @humanspeak/svelte-markdown/extensions
|
|
@@ -14,4 +15,6 @@
|
|
|
14
15
|
export { AlertRenderer, markedAlert } from './alert/index.js';
|
|
15
16
|
export type { AlertType } from './alert/index.js';
|
|
16
17
|
export { FootnoteRef, FootnoteSection, markedFootnote } from './footnote/index.js';
|
|
18
|
+
export { BLOCK_KATEX_TOKEN, INLINE_KATEX_TOKEN, KatexRenderer, markedKatex } from './katex/index.js';
|
|
19
|
+
export type { MarkedKatexOptions } from './katex/index.js';
|
|
17
20
|
export { MermaidRenderer, markedMermaid } from './mermaid/index.js';
|
package/dist/extensions/index.js
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* import { markedMermaid, MermaidRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
8
8
|
* import { markedAlert, AlertRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
9
9
|
* import { markedFootnote, FootnoteRef, FootnoteSection } from '@humanspeak/svelte-markdown/extensions'
|
|
10
|
+
* import { markedKatex, KatexRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
10
11
|
* ```
|
|
11
12
|
*
|
|
12
13
|
* @module @humanspeak/svelte-markdown/extensions
|
|
13
14
|
*/
|
|
14
15
|
export { AlertRenderer, markedAlert } from './alert/index.js';
|
|
15
16
|
export { FootnoteRef, FootnoteSection, markedFootnote } from './footnote/index.js';
|
|
17
|
+
export { BLOCK_KATEX_TOKEN, INLINE_KATEX_TOKEN, KatexRenderer, markedKatex } from './katex/index.js';
|
|
16
18
|
export { MermaidRenderer, markedMermaid } from './mermaid/index.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Renders an `inlineKatex` or `blockKatex` token (produced by {@link markedKatex})
|
|
4
|
+
to KaTeX HTML.
|
|
5
|
+
|
|
6
|
+
Pair with the matching extension via the `renderers` prop:
|
|
7
|
+
|
|
8
|
+
```svelte
|
|
9
|
+
<SvelteMarkdown
|
|
10
|
+
source={markdown}
|
|
11
|
+
extensions={[markedKatex()]}
|
|
12
|
+
renderers={{ inlineKatex: KatexRenderer, blockKatex: KatexRenderer }}
|
|
13
|
+
/>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires `katex` to be installed (it's an optional peer dependency of
|
|
17
|
+
`@humanspeak/svelte-markdown`) and `katex/dist/katex.css` to be loaded — either
|
|
18
|
+
imported directly or pulled in via the CDN tag KaTeX recommends.
|
|
19
|
+
|
|
20
|
+
Hardcodes `throwOnError: false` so a single malformed expression renders as a
|
|
21
|
+
red KaTeX error span instead of throwing. If you need stricter behavior, pass
|
|
22
|
+
your own component for `inlineKatex` / `blockKatex` instead.
|
|
23
|
+
-->
|
|
24
|
+
<script lang="ts">
|
|
25
|
+
import katex from 'katex'
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
/** TeX/LaTeX source to render. Token's inner content for delimiter pairs, or the full `\begin{env}...\end{env}` string for AMS environments. */
|
|
29
|
+
text: string
|
|
30
|
+
/** When `true`, render in display style (block); otherwise inline. */
|
|
31
|
+
displayMode?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { text, displayMode = false }: Props = $props()
|
|
35
|
+
|
|
36
|
+
const html = $derived(katex.renderToString(text, { throwOnError: false, displayMode }))
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<!-- trunk-ignore(eslint/svelte/no-at-html-tags) -->
|
|
40
|
+
{@html html}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** TeX/LaTeX source to render. Token's inner content for delimiter pairs, or the full `\begin{env}...\end{env}` string for AMS environments. */
|
|
3
|
+
text: string;
|
|
4
|
+
/** When `true`, render in display style (block); otherwise inline. */
|
|
5
|
+
displayMode?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Renders an `inlineKatex` or `blockKatex` token (produced by {@link markedKatex})
|
|
9
|
+
* to KaTeX HTML.
|
|
10
|
+
*
|
|
11
|
+
* Pair with the matching extension via the `renderers` prop:
|
|
12
|
+
*
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <SvelteMarkdown
|
|
15
|
+
* source={markdown}
|
|
16
|
+
* extensions={[markedKatex()]}
|
|
17
|
+
* renderers={{ inlineKatex: KatexRenderer, blockKatex: KatexRenderer }}
|
|
18
|
+
* />
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Requires `katex` to be installed (it's an optional peer dependency of
|
|
22
|
+
* `@humanspeak/svelte-markdown`) and `katex/dist/katex.css` to be loaded — either
|
|
23
|
+
* imported directly or pulled in via the CDN tag KaTeX recommends.
|
|
24
|
+
*
|
|
25
|
+
* Hardcodes `throwOnError: false` so a single malformed expression renders as a
|
|
26
|
+
* red KaTeX error span instead of throwing. If you need stricter behavior, pass
|
|
27
|
+
* your own component for `inlineKatex` / `blockKatex` instead.
|
|
28
|
+
*/
|
|
29
|
+
declare const KatexRenderer: import("svelte").Component<Props, {}, "">;
|
|
30
|
+
type KatexRenderer = ReturnType<typeof KatexRenderer>;
|
|
31
|
+
export default KatexRenderer;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { MarkedExtension } from 'marked';
|
|
2
|
+
/** Token type emitted for inline math (`\(...\)`, opt-in `$...$`). */
|
|
3
|
+
export declare const INLINE_KATEX_TOKEN = "inlineKatex";
|
|
4
|
+
/** Token type emitted for block math (`\[...\]`, `$$...$$`, AMS environments). */
|
|
5
|
+
export declare const BLOCK_KATEX_TOKEN = "blockKatex";
|
|
6
|
+
/**
|
|
7
|
+
* Options for the {@link markedKatex} factory.
|
|
8
|
+
*/
|
|
9
|
+
export interface MarkedKatexOptions {
|
|
10
|
+
/**
|
|
11
|
+
* When `true`, also tokenize `$...$` (inline) and `$$...$$` (inline) as
|
|
12
|
+
* math, using the standard whitespace-boundary rule that prevents
|
|
13
|
+
* currency strings like `$5,000` from being parsed as a math expression.
|
|
14
|
+
*
|
|
15
|
+
* Off by default. KaTeX's own `auto-render` extension excludes `$...$`
|
|
16
|
+
* from its defaults with the comment "LaTeX uses $…$, but it ruins the
|
|
17
|
+
* display of normal `$` in text" — we follow the same opinion.
|
|
18
|
+
*
|
|
19
|
+
* Block-level `$$...$$` (own-line delimiters) is always enabled, and is
|
|
20
|
+
* unaffected by this option.
|
|
21
|
+
*
|
|
22
|
+
* @defaultValue `false`
|
|
23
|
+
*/
|
|
24
|
+
singleDollarInline?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates a marked extension that tokenizes KaTeX math expressions into
|
|
28
|
+
* custom `inlineKatex` and `blockKatex` tokens.
|
|
29
|
+
*
|
|
30
|
+
* Default delimiter set (mirrors KaTeX's own `auto-render` defaults):
|
|
31
|
+
*
|
|
32
|
+
* | Delimiter pair | Level | `displayMode` |
|
|
33
|
+
* |---|---|---|
|
|
34
|
+
* | `\(...\)` | inline | `false` |
|
|
35
|
+
* | `\[...\]` (own-line) | block | `true` |
|
|
36
|
+
* | `$$...$$` (own-line) | block | `true` |
|
|
37
|
+
* | `\begin{equation}...\end{equation}` and other AMS envs | block | `true` |
|
|
38
|
+
*
|
|
39
|
+
* Supported AMS environments: `equation`, `align`, `alignat`, `gather`, `CD`,
|
|
40
|
+
* plus their starred variants (e.g. `equation*`).
|
|
41
|
+
*
|
|
42
|
+
* `$...$` inline is **off** by default — KaTeX itself opts out of single-
|
|
43
|
+
* dollar inline because of currency-string clashes (`$5,000` etc.). Pass
|
|
44
|
+
* `{ singleDollarInline: true }` to enable it; when enabled it uses the
|
|
45
|
+
* whitespace-boundary rule from upstream `marked-katex-extension` so
|
|
46
|
+
* currency strings still won't match.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```svelte
|
|
50
|
+
* <script lang="ts">
|
|
51
|
+
* import SvelteMarkdown from '@humanspeak/svelte-markdown'
|
|
52
|
+
* import { markedKatex, KatexRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
53
|
+
*
|
|
54
|
+
* const renderers = { inlineKatex: KatexRenderer, blockKatex: KatexRenderer }
|
|
55
|
+
* </script>
|
|
56
|
+
*
|
|
57
|
+
* <SvelteMarkdown
|
|
58
|
+
* source={markdown}
|
|
59
|
+
* extensions={[markedKatex()]}
|
|
60
|
+
* {renderers}
|
|
61
|
+
* />
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* Pair with `KatexRenderer` from the same subpath, or supply your own
|
|
65
|
+
* component that accepts `{ text: string; displayMode?: boolean }`.
|
|
66
|
+
*
|
|
67
|
+
* @param options - {@link MarkedKatexOptions}
|
|
68
|
+
* @returns A `MarkedExtension` containing one block-level and one inline tokenizer
|
|
69
|
+
*/
|
|
70
|
+
export declare function markedKatex(options?: MarkedKatexOptions): MarkedExtension;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/** Token type emitted for inline math (`\(...\)`, opt-in `$...$`). */
|
|
2
|
+
export const INLINE_KATEX_TOKEN = 'inlineKatex';
|
|
3
|
+
/** Token type emitted for block math (`\[...\]`, `$$...$$`, AMS environments). */
|
|
4
|
+
export const BLOCK_KATEX_TOKEN = 'blockKatex';
|
|
5
|
+
const AMS_ENVIRONMENTS = ['equation', 'align', 'alignat', 'gather', 'CD'];
|
|
6
|
+
// `\\*` in the JS string becomes `\*` in the regex source, which matches a
|
|
7
|
+
// literal `*` (the AMS un-numbered variant). Without the escape, `equation*`
|
|
8
|
+
// in a regex would mean "equatio + zero-or-more n", which silently fails to
|
|
9
|
+
// match `\begin{equation*}`.
|
|
10
|
+
const AMS_NAMES = AMS_ENVIRONMENTS.flatMap((n) => [n, `${n}\\*`]).join('|');
|
|
11
|
+
const blockBracketRule = /^\\\[[ \t]*\n([\s\S]+?)\n[ \t]*\\\](?:\n|$)/;
|
|
12
|
+
const blockDollarRule = /^\$\$[ \t]*\n([\s\S]+?)\n[ \t]*\$\$(?:\n|$)/;
|
|
13
|
+
const blockAmsRule = new RegExp(`^\\\\begin\\{(${AMS_NAMES})\\}[\\s\\S]+?\\\\end\\{\\1\\}(?:\\n|$)?`);
|
|
14
|
+
const inlineParenRule = /^\\\(([\s\S]+?)\\\)/;
|
|
15
|
+
// Mirrors the "standard" rule from upstream marked-katex-extension: requires a
|
|
16
|
+
// whitespace, end-of-string, or punctuation boundary after the closing `$` so
|
|
17
|
+
// strings like `$5,000` do not match.
|
|
18
|
+
const inlineDollarRule = /^\$(?!\$)((?:\\.|[^\\\n$])+?)\$(?=[\s?!.,:?!。,:]|$)/;
|
|
19
|
+
const earliestIndex = (src, needles) => {
|
|
20
|
+
let best = -1;
|
|
21
|
+
for (const needle of needles) {
|
|
22
|
+
const i = src.indexOf(needle);
|
|
23
|
+
if (i !== -1 && (best === -1 || i < best))
|
|
24
|
+
best = i;
|
|
25
|
+
}
|
|
26
|
+
return best === -1 ? undefined : best;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Creates a marked extension that tokenizes KaTeX math expressions into
|
|
30
|
+
* custom `inlineKatex` and `blockKatex` tokens.
|
|
31
|
+
*
|
|
32
|
+
* Default delimiter set (mirrors KaTeX's own `auto-render` defaults):
|
|
33
|
+
*
|
|
34
|
+
* | Delimiter pair | Level | `displayMode` |
|
|
35
|
+
* |---|---|---|
|
|
36
|
+
* | `\(...\)` | inline | `false` |
|
|
37
|
+
* | `\[...\]` (own-line) | block | `true` |
|
|
38
|
+
* | `$$...$$` (own-line) | block | `true` |
|
|
39
|
+
* | `\begin{equation}...\end{equation}` and other AMS envs | block | `true` |
|
|
40
|
+
*
|
|
41
|
+
* Supported AMS environments: `equation`, `align`, `alignat`, `gather`, `CD`,
|
|
42
|
+
* plus their starred variants (e.g. `equation*`).
|
|
43
|
+
*
|
|
44
|
+
* `$...$` inline is **off** by default — KaTeX itself opts out of single-
|
|
45
|
+
* dollar inline because of currency-string clashes (`$5,000` etc.). Pass
|
|
46
|
+
* `{ singleDollarInline: true }` to enable it; when enabled it uses the
|
|
47
|
+
* whitespace-boundary rule from upstream `marked-katex-extension` so
|
|
48
|
+
* currency strings still won't match.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```svelte
|
|
52
|
+
* <script lang="ts">
|
|
53
|
+
* import SvelteMarkdown from '@humanspeak/svelte-markdown'
|
|
54
|
+
* import { markedKatex, KatexRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
55
|
+
*
|
|
56
|
+
* const renderers = { inlineKatex: KatexRenderer, blockKatex: KatexRenderer }
|
|
57
|
+
* </script>
|
|
58
|
+
*
|
|
59
|
+
* <SvelteMarkdown
|
|
60
|
+
* source={markdown}
|
|
61
|
+
* extensions={[markedKatex()]}
|
|
62
|
+
* {renderers}
|
|
63
|
+
* />
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* Pair with `KatexRenderer` from the same subpath, or supply your own
|
|
67
|
+
* component that accepts `{ text: string; displayMode?: boolean }`.
|
|
68
|
+
*
|
|
69
|
+
* @param options - {@link MarkedKatexOptions}
|
|
70
|
+
* @returns A `MarkedExtension` containing one block-level and one inline tokenizer
|
|
71
|
+
*/
|
|
72
|
+
export function markedKatex(options = {}) {
|
|
73
|
+
const { singleDollarInline = false } = options;
|
|
74
|
+
// The token-cache hash serializes functions via `fn.toString()`, which
|
|
75
|
+
// can't see option values that live in a closure (our tokenizers'
|
|
76
|
+
// source code is identical regardless of `singleDollarInline`). This
|
|
77
|
+
// marker makes the option visible to JSON.stringify so two
|
|
78
|
+
// `markedKatex({ ... })` calls with different options produce
|
|
79
|
+
// different cache keys — without it, toggling the option at runtime
|
|
80
|
+
// returns stale tokens. Cast because `MarkedExtension` doesn't permit
|
|
81
|
+
// arbitrary fields, but Marked.use() shallow-spreads our object into
|
|
82
|
+
// `defaults`, so the marker survives.
|
|
83
|
+
const ext = {
|
|
84
|
+
_humanspeakKatexConfig: JSON.stringify({ singleDollarInline }),
|
|
85
|
+
extensions: [
|
|
86
|
+
{
|
|
87
|
+
name: BLOCK_KATEX_TOKEN,
|
|
88
|
+
level: 'block',
|
|
89
|
+
start(src) {
|
|
90
|
+
return earliestIndex(src, ['\\[', '$$', '\\begin{']);
|
|
91
|
+
},
|
|
92
|
+
tokenizer(src) {
|
|
93
|
+
const bracket = src.match(blockBracketRule);
|
|
94
|
+
if (bracket) {
|
|
95
|
+
return {
|
|
96
|
+
type: BLOCK_KATEX_TOKEN,
|
|
97
|
+
raw: bracket[0],
|
|
98
|
+
text: bracket[1].trim(),
|
|
99
|
+
displayMode: true
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const dollar = src.match(blockDollarRule);
|
|
103
|
+
if (dollar) {
|
|
104
|
+
return {
|
|
105
|
+
type: BLOCK_KATEX_TOKEN,
|
|
106
|
+
raw: dollar[0],
|
|
107
|
+
text: dollar[1].trim(),
|
|
108
|
+
displayMode: true
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const ams = src.match(blockAmsRule);
|
|
112
|
+
if (ams) {
|
|
113
|
+
// KaTeX parses `\begin{...}...\end{...}` natively, so
|
|
114
|
+
// pass the entire matched string through as `text`.
|
|
115
|
+
return {
|
|
116
|
+
type: BLOCK_KATEX_TOKEN,
|
|
117
|
+
raw: ams[0],
|
|
118
|
+
text: ams[0].replace(/\n$/, '').trim(),
|
|
119
|
+
displayMode: true
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: INLINE_KATEX_TOKEN,
|
|
126
|
+
level: 'inline',
|
|
127
|
+
start(src) {
|
|
128
|
+
const needles = ['\\('];
|
|
129
|
+
if (singleDollarInline)
|
|
130
|
+
needles.push('$');
|
|
131
|
+
return earliestIndex(src, needles);
|
|
132
|
+
},
|
|
133
|
+
tokenizer(src) {
|
|
134
|
+
const paren = src.match(inlineParenRule);
|
|
135
|
+
if (paren) {
|
|
136
|
+
return {
|
|
137
|
+
type: INLINE_KATEX_TOKEN,
|
|
138
|
+
raw: paren[0],
|
|
139
|
+
text: paren[1].trim(),
|
|
140
|
+
displayMode: false
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (singleDollarInline) {
|
|
144
|
+
const dollar = src.match(inlineDollarRule);
|
|
145
|
+
if (dollar) {
|
|
146
|
+
return {
|
|
147
|
+
type: INLINE_KATEX_TOKEN,
|
|
148
|
+
raw: dollar[0],
|
|
149
|
+
text: dollar[1].trim(),
|
|
150
|
+
displayMode: false
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
};
|
|
158
|
+
return ext;
|
|
159
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ import type { MarkedOptions, Renderers } from './utils/markdown-parser.js';
|
|
|
23
23
|
import type { HtmlKey } from './utils/rendererKeys.js';
|
|
24
24
|
import type { SanitizeAttributesFn, SanitizeUrlFn } from './utils/sanitize.js';
|
|
25
25
|
export interface ParagraphSnippetProps {
|
|
26
|
+
raw?: string;
|
|
27
|
+
text?: string;
|
|
26
28
|
children?: Snippet;
|
|
27
29
|
}
|
|
28
30
|
export interface HeadingSnippetProps {
|
|
@@ -36,29 +38,41 @@ export interface HeadingSnippetProps {
|
|
|
36
38
|
export interface LinkSnippetProps {
|
|
37
39
|
href?: string;
|
|
38
40
|
title?: string;
|
|
41
|
+
raw?: string;
|
|
42
|
+
text?: string;
|
|
39
43
|
children?: Snippet;
|
|
40
44
|
}
|
|
41
45
|
export interface ImageSnippetProps {
|
|
42
46
|
href?: string;
|
|
43
47
|
title?: string;
|
|
44
48
|
text?: string;
|
|
49
|
+
raw?: string;
|
|
45
50
|
}
|
|
46
51
|
export interface CodeSnippetProps {
|
|
47
52
|
lang: string;
|
|
48
53
|
text: string;
|
|
54
|
+
codeBlockStyle?: 'indented';
|
|
49
55
|
}
|
|
50
56
|
export interface CodespanSnippetProps {
|
|
51
57
|
raw: string;
|
|
58
|
+
text?: string;
|
|
52
59
|
}
|
|
53
60
|
export interface BlockquoteSnippetProps {
|
|
61
|
+
raw?: string;
|
|
62
|
+
text?: string;
|
|
54
63
|
children?: Snippet;
|
|
55
64
|
}
|
|
56
65
|
export interface ListSnippetProps {
|
|
57
66
|
ordered?: boolean;
|
|
58
67
|
start?: number;
|
|
68
|
+
loose?: boolean;
|
|
59
69
|
children?: Snippet;
|
|
60
70
|
}
|
|
61
71
|
export interface ListItemSnippetProps {
|
|
72
|
+
text?: string;
|
|
73
|
+
task?: boolean;
|
|
74
|
+
checked?: boolean;
|
|
75
|
+
loose?: boolean;
|
|
62
76
|
children?: Snippet;
|
|
63
77
|
listItemIndex?: number;
|
|
64
78
|
}
|
|
@@ -80,17 +94,25 @@ export interface TableCellSnippetProps {
|
|
|
80
94
|
children?: Snippet;
|
|
81
95
|
}
|
|
82
96
|
export interface EmSnippetProps {
|
|
97
|
+
raw?: string;
|
|
98
|
+
text?: string;
|
|
83
99
|
children?: Snippet;
|
|
84
100
|
}
|
|
85
101
|
export interface StrongSnippetProps {
|
|
102
|
+
raw?: string;
|
|
103
|
+
text?: string;
|
|
86
104
|
children?: Snippet;
|
|
87
105
|
}
|
|
88
106
|
export interface DelSnippetProps {
|
|
107
|
+
raw?: string;
|
|
108
|
+
text?: string;
|
|
89
109
|
children?: Snippet;
|
|
90
110
|
}
|
|
91
111
|
export type HrSnippetProps = Record<string, never>;
|
|
92
112
|
export type BrSnippetProps = Record<string, never>;
|
|
93
113
|
export interface TextSnippetProps {
|
|
114
|
+
raw?: string;
|
|
115
|
+
text?: string;
|
|
94
116
|
children?: Snippet;
|
|
95
117
|
}
|
|
96
118
|
export interface RawTextSnippetProps {
|
|
@@ -98,6 +120,7 @@ export interface RawTextSnippetProps {
|
|
|
98
120
|
}
|
|
99
121
|
export interface EscapeSnippetProps {
|
|
100
122
|
text: string;
|
|
123
|
+
raw?: string;
|
|
101
124
|
}
|
|
102
125
|
export type SnippetOverrides = {
|
|
103
126
|
paragraph?: Snippet<[ParagraphSnippetProps]>;
|
|
@@ -181,9 +204,13 @@ export type SvelteMarkdownProps<T extends Renderers = Renderers> = {
|
|
|
181
204
|
*
|
|
182
205
|
* @example
|
|
183
206
|
* ```svelte
|
|
207
|
+
* <script lang="ts">
|
|
208
|
+
* import { markedKatex, KatexRenderer } from '@humanspeak/svelte-markdown/extensions'
|
|
209
|
+
* </script>
|
|
210
|
+
*
|
|
184
211
|
* <SvelteMarkdown
|
|
185
212
|
* source={markdown}
|
|
186
|
-
* extensions={[markedKatex(
|
|
213
|
+
* extensions={[markedKatex()]}
|
|
187
214
|
* renderers={{ inlineKatex: KatexRenderer, blockKatex: KatexRenderer }}
|
|
188
215
|
* />
|
|
189
216
|
* ```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-markdown",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Fast, customizable markdown renderer for Svelte with built-in caching, TypeScript support, and Svelte 5 runes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -70,60 +70,63 @@
|
|
|
70
70
|
"@humanspeak/memory-cache": "^1.0.6",
|
|
71
71
|
"github-slugger": "^2.0.0",
|
|
72
72
|
"htmlparser2": "^12.0.0",
|
|
73
|
-
"marked": "^18.0.
|
|
73
|
+
"marked": "^18.0.3"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@eslint/compat": "^2.0.
|
|
76
|
+
"@eslint/compat": "^2.0.5",
|
|
77
77
|
"@eslint/js": "^10.0.1",
|
|
78
|
-
"@playwright/cli": "^0.1.
|
|
78
|
+
"@playwright/cli": "^0.1.11",
|
|
79
79
|
"@playwright/test": "^1.59.1",
|
|
80
80
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
81
|
-
"@sveltejs/kit": "^2.
|
|
81
|
+
"@sveltejs/kit": "^2.59.0",
|
|
82
82
|
"@sveltejs/package": "^2.5.7",
|
|
83
83
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
84
84
|
"@testing-library/jest-dom": "^6.9.1",
|
|
85
85
|
"@testing-library/svelte": "^5.3.1",
|
|
86
86
|
"@testing-library/user-event": "^14.6.1",
|
|
87
87
|
"@types/katex": "^0.16.8",
|
|
88
|
-
"@types/node": "^25.
|
|
89
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
90
|
-
"@typescript-eslint/parser": "^8.
|
|
91
|
-
"@vitest/coverage-v8": "^4.1.
|
|
92
|
-
"eslint": "^10.
|
|
88
|
+
"@types/node": "^25.6.0",
|
|
89
|
+
"@typescript-eslint/eslint-plugin": "^8.59.2",
|
|
90
|
+
"@typescript-eslint/parser": "^8.59.2",
|
|
91
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
92
|
+
"eslint": "^10.3.0",
|
|
93
93
|
"eslint-config-prettier": "^10.1.8",
|
|
94
94
|
"eslint-plugin-import": "^2.32.0",
|
|
95
|
-
"eslint-plugin-svelte": "^3.17.
|
|
95
|
+
"eslint-plugin-svelte": "^3.17.1",
|
|
96
96
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
97
|
-
"globals": "^17.
|
|
97
|
+
"globals": "^17.6.0",
|
|
98
98
|
"husky": "^9.1.7",
|
|
99
|
-
"jsdom": "^29.
|
|
99
|
+
"jsdom": "^29.1.1",
|
|
100
100
|
"katex": "^0.16.45",
|
|
101
|
-
"marked-katex-extension": "^5.1.8",
|
|
102
101
|
"mermaid": "^11.14.0",
|
|
103
102
|
"mprocs": "^0.9.2",
|
|
104
|
-
"prettier": "^3.8.
|
|
103
|
+
"prettier": "^3.8.3",
|
|
105
104
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
106
105
|
"prettier-plugin-svelte": "^3.5.1",
|
|
107
|
-
"prettier-plugin-tailwindcss": "^0.
|
|
106
|
+
"prettier-plugin-tailwindcss": "^0.8.0",
|
|
108
107
|
"publint": "^0.3.18",
|
|
109
|
-
"svelte": "^5.55.
|
|
110
|
-
"svelte-check": "^4.4.
|
|
111
|
-
"typescript": "^6.0.
|
|
112
|
-
"typescript-eslint": "^8.
|
|
113
|
-
"vite": "^8.0.
|
|
114
|
-
"vitest": "^4.1.
|
|
108
|
+
"svelte": "^5.55.5",
|
|
109
|
+
"svelte-check": "^4.4.7",
|
|
110
|
+
"typescript": "^6.0.3",
|
|
111
|
+
"typescript-eslint": "^8.59.2",
|
|
112
|
+
"vite": "^8.0.10",
|
|
113
|
+
"vitest": "^4.1.5"
|
|
115
114
|
},
|
|
116
115
|
"peerDependencies": {
|
|
116
|
+
"katex": ">=0.16.0",
|
|
117
117
|
"mermaid": ">=10.0.0",
|
|
118
118
|
"svelte": "^5.0.0"
|
|
119
119
|
},
|
|
120
120
|
"peerDependenciesMeta": {
|
|
121
|
+
"katex": {
|
|
122
|
+
"optional": true
|
|
123
|
+
},
|
|
121
124
|
"mermaid": {
|
|
122
125
|
"optional": true
|
|
123
126
|
}
|
|
124
127
|
},
|
|
125
128
|
"volta": {
|
|
126
|
-
"node": "24.
|
|
129
|
+
"node": "24.15.0"
|
|
127
130
|
},
|
|
128
131
|
"publishConfig": {
|
|
129
132
|
"access": "public"
|