flux-md 0.13.0 → 0.14.0
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/CHANGELOG.md +51 -0
- package/README.md +118 -16
- package/package.json +2 -1
- package/src/html-to-react.ts +23 -8
- package/src/react.tsx +7 -3
- package/src/server.tsx +209 -0
- package/src/types-core.ts +33 -1
- package/src/wasm/flux_md_core.d.ts +17 -0
- package/src/wasm/flux_md_core.js +35 -0
- package/src/wasm/flux_md_core_bg.wasm +0 -0
- package/src/wasm/flux_md_core_bg.wasm.d.ts +2 -0
- package/src/wasm/package.json +1 -1
- package/src/worker.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ Notable changes to flux-md. Format based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); this project aims to follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## 0.14.0 — 2026-06-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Inline custom component tags (`inlineComponentTags`)** — the headline gap for
|
|
12
|
+
rich apps. An allowlisted inline tag like `<tik symbol="AAPL">AAPL</tik>` (or
|
|
13
|
+
self-closing `<tik/>`) **anywhere inline** — paragraphs, headings, list items,
|
|
14
|
+
and **table cells** — renders as a real custom element with its inner parsed as
|
|
15
|
+
**inline markdown** and its attributes sanitized (event handlers dropped,
|
|
16
|
+
dangerous URL schemes → `#`). The React renderer dispatches it to
|
|
17
|
+
`components[tag]` with the inner markdown as `children` and the attributes as
|
|
18
|
+
props — **XSS-safe without `unsafeHtml`**. Independent of `componentTags`
|
|
19
|
+
(block containers): list a tag under either or both. Use lowercase tag names.
|
|
20
|
+
- **`children` on `Component` block overrides** — a `Component` override now also
|
|
21
|
+
receives the inner content pre-parsed to a React tree (`children`), so you can
|
|
22
|
+
`return <Chip {...attrs}>{children}</Chip>` instead of
|
|
23
|
+
`dangerouslySetInnerHTML`-ing `html`. The html-vs-children contract is now loud
|
|
24
|
+
in the types and docs (an override that renders neither shows empty).
|
|
25
|
+
- **`flux-md/server` — worker-free synchronous SSR / RSC rendering.** The Rust→
|
|
26
|
+
WASM core is a plain synchronous parser, so finished markdown renders on the
|
|
27
|
+
server with no worker: `initFlux()` (async, idempotent — reads the co-located
|
|
28
|
+
`.wasm` in Node, or `initFluxSync(bytes)` on edge), `renderToString(md, {
|
|
29
|
+
config })` (sync HTML string, zero React dep), `parseToBlocks(md, { config })`,
|
|
30
|
+
and `<FluxMarkdownStatic content config components />` — a hookless, RSC-safe
|
|
31
|
+
React component that emits the same `flux-md` tree a client `<FluxMarkdown>`
|
|
32
|
+
hydrates, with the same overrides (inline/block component tags dispatch on the
|
|
33
|
+
server too).
|
|
34
|
+
- **`FluxParser.allBlocks()` (WASM)** — returns the whole parsed document as a
|
|
35
|
+
block array, the one-shot render primitive used by `flux-md/server`.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **Data-loss: a block component tag used inline swallowed sibling blocks.** With
|
|
40
|
+
e.g. `componentTags: ["tik"]`, an inline occurrence such as
|
|
41
|
+
`<tik>AAPL</tik> is up.` on a line with following content opened a block
|
|
42
|
+
container that consumed the rest of the document (the paragraph and a following
|
|
43
|
+
table vanished). A block component open tag must now be the **whole line** (only
|
|
44
|
+
trailing whitespace after `>`); otherwise it's treated as inline and degrades
|
|
45
|
+
inertly — it never eats surrounding content.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- The React HTML→tree converter (`htmlToReact` / `parseTrustedHtml`) now preserves
|
|
50
|
+
a tag's original **case** for component dispatch (so a capitalized inline tag
|
|
51
|
+
like `<Cite>` maps to `components.Cite`); HTML semantics (void elements, `input`,
|
|
52
|
+
close-tag matching) still compare case-insensitively, so standard output is
|
|
53
|
+
unchanged.
|
|
54
|
+
|
|
55
|
+
Feature-off output is byte-identical (CommonMark 652 + GFM floors hold); both
|
|
56
|
+
allowlists are empty by default.
|
|
57
|
+
|
|
7
58
|
## 0.13.0 — 2026-06-04
|
|
8
59
|
|
|
9
60
|
### Added
|
package/README.md
CHANGED
|
@@ -18,7 +18,10 @@ import.meta.url)`** pattern, so any bundler with asset-module support resolves
|
|
|
18
18
|
them: **Vite** (the reference setup), **webpack 5**, **Rollup** (with asset
|
|
19
19
|
modules), **Parcel**, and **Next.js** (App Router — Turbopack *and* webpack;
|
|
20
20
|
**verified on Next.js 16**, see the [Next.js callout](#nextjs) below). It is
|
|
21
|
-
|
|
21
|
+
The streaming client (`<FluxMarkdown>` / `FluxClient`) is **browser-only** (it
|
|
22
|
+
constructs Web Workers). For **server-side / static rendering of finished
|
|
23
|
+
content** — SSR, React Server Components, build steps — use the worker-free,
|
|
24
|
+
synchronous [`flux-md/server`](#server-side-rendering) entry. The framework packages — `react`,
|
|
22
25
|
`vue`, `svelte`, `solid-js` — are all **optional** peer dependencies; you only
|
|
23
26
|
need the one whose binding you import. The core (`flux-md`, `flux-md/client`,
|
|
24
27
|
`flux-md/dom`, `flux-md/element`) needs none.
|
|
@@ -403,6 +406,55 @@ issue if your Solid setup trips on it. The component is a thin `ref`'d `<div>`;
|
|
|
403
406
|
if you hit a transform edge, `mountFluxMarkdown` from `flux-md/dom` inside
|
|
404
407
|
`onMount`/`onCleanup` is the zero-surprise fallback.
|
|
405
408
|
|
|
409
|
+
## Server-side rendering
|
|
410
|
+
|
|
411
|
+
`<FluxMarkdown>` / `FluxClient` are browser-only (they spawn a Web Worker), but
|
|
412
|
+
the Rust→WASM core is a plain **synchronous** parser. So `flux-md/server` renders
|
|
413
|
+
**finished** markdown on the server with no worker and no async ceremony — Node
|
|
414
|
+
SSR, React Server Components, or a build step:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
import { initFlux, renderToString } from "flux-md/server";
|
|
418
|
+
|
|
419
|
+
await initFlux(); // once at startup (loads the WASM)
|
|
420
|
+
const html = renderToString("# Hello\n\n**world**"); // sync HTML string, no worker
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
For React server rendering (RSC, static generation, or SSR), use
|
|
424
|
+
`<FluxMarkdownStatic>` — a hookless, RSC-safe component that renders finished
|
|
425
|
+
content with the same `components` overrides (inline/block component tags
|
|
426
|
+
dispatch on the server too):
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
import { initFlux, FluxMarkdownStatic } from "flux-md/server";
|
|
430
|
+
|
|
431
|
+
await initFlux();
|
|
432
|
+
export default function Doc({ md }: { md: string }) {
|
|
433
|
+
return (
|
|
434
|
+
<FluxMarkdownStatic
|
|
435
|
+
content={md}
|
|
436
|
+
config={{ inlineComponentTags: ["tik"] }}
|
|
437
|
+
components={{ tik: ({ symbol }) => <span className="ticker">{symbol}</span> }}
|
|
438
|
+
/>
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
- **`initFlux()`** — async, idempotent. In Node it reads the package's `.wasm` off
|
|
444
|
+
disk (Node's `fetch` can't load `file://`); on the web it fetches the
|
|
445
|
+
bundler-resolved asset. On edge runtimes pass bytes yourself:
|
|
446
|
+
`initFluxSync(wasmBytes)`.
|
|
447
|
+
- **`renderToString(md, { config })`** — synchronous HTML string, **zero React
|
|
448
|
+
dependency**.
|
|
449
|
+
- **`parseToBlocks(md, { config })`** — the block array, for custom rendering.
|
|
450
|
+
- **`<FluxMarkdownStatic content config components />`** — synchronous React tree
|
|
451
|
+
for **render-once** contexts; render it with your framework's server renderer
|
|
452
|
+
(`renderToStaticMarkup`, RSC, …). For live streaming, client-side code
|
|
453
|
+
highlighting, or Mermaid, render `<FluxMarkdown>` on the client instead — it's a
|
|
454
|
+
separate component. (If you SSR-then-hydrate, use the *same* component on both
|
|
455
|
+
sides; the dedicated client renderers in `<FluxMarkdown>` don't hydrate
|
|
456
|
+
`<FluxMarkdownStatic>`'s plainer markup.)
|
|
457
|
+
|
|
406
458
|
## What it does
|
|
407
459
|
|
|
408
460
|
| Concern | flux-md | conventional main-thread renderer |
|
|
@@ -428,6 +480,11 @@ highlighter's colors** (without any CSS, `highlight()` renders uncolored). The
|
|
|
428
480
|
theme is scoped to `.flux-md`, zero-runtime, and **does not change the rendered
|
|
429
481
|
HTML** — skip the import and nothing is styled.
|
|
430
482
|
|
|
483
|
+
> **Next.js Pages Router:** `flux-md/styles.css` is global CSS, which the Pages
|
|
484
|
+
> Router only allows importing from `pages/_app`. Import it there (App Router and
|
|
485
|
+
> other bundlers can import it from any component). Or skip it and bring your own
|
|
486
|
+
> `.flux-md` styles.
|
|
487
|
+
|
|
431
488
|
Re-theme by overriding a few CSS variables; it's light by default and switches to
|
|
432
489
|
dark automatically via `prefers-color-scheme` (force a mode with
|
|
433
490
|
`class="flux-md flux-dark"` or `flux-light`):
|
|
@@ -499,7 +556,8 @@ const client = new FluxClient({
|
|
|
499
556
|
dirAuto: true, // per-block dir="auto" for RTL/bidi text (default false)
|
|
500
557
|
a11y: true, // task-list <label> + <th scope="col"> a11y markup (default false)
|
|
501
558
|
unsafeHtml: false, // pass raw HTML through (default false — keep it false for untrusted input)
|
|
502
|
-
componentTags: ["Thinking", "Callout"], // custom tags
|
|
559
|
+
componentTags: ["Thinking", "Callout"], // BLOCK custom tags w/ markdown inside (default none)
|
|
560
|
+
inlineComponentTags: ["tik", "cite"], // INLINE custom tags (chips/citations) w/ markdown inside (default none)
|
|
503
561
|
blockData: true, // opt-in structured kind.data per block (default false — see "Structured block data")
|
|
504
562
|
},
|
|
505
563
|
});
|
|
@@ -527,10 +585,13 @@ When to enable each flag:
|
|
|
527
585
|
- `unsafeHtml: true` — only when rendering trusted HTML. For untrusted /
|
|
528
586
|
LLM-produced HTML, pair this with `<FluxMarkdown sanitize={…} />` (DOMPurify or
|
|
529
587
|
similar — see [Security](#security)).
|
|
530
|
-
- `componentTags: ["Thinking", …]` — when your LLM emits custom tags
|
|
531
|
-
`<Thinking>…</Thinking>` and you want their inner
|
|
532
|
-
and dispatched to a React component. Safe without
|
|
533
|
-
sanitized; allowlisted tags only).
|
|
588
|
+
- `componentTags: ["Thinking", …]` — when your LLM emits **block** custom tags
|
|
589
|
+
like `<Thinking>…</Thinking>` (on their own line) and you want their inner
|
|
590
|
+
content parsed as markdown and dispatched to a React component. Safe without
|
|
591
|
+
`unsafeHtml` (attributes are sanitized; allowlisted tags only).
|
|
592
|
+
- `inlineComponentTags: ["tik", …]` — same idea for **inline** custom elements
|
|
593
|
+
that sit inside a paragraph, heading, list item, or **table cell** (ticker
|
|
594
|
+
chips, citations, `@mentions`). See [Inline component tags](#inline-component-tags).
|
|
534
595
|
|
|
535
596
|
**Footnotes** (`gfmFootnotes`) work in streaming with one honest caveat: a
|
|
536
597
|
`[^1]` reference renders speculatively the moment it's seen (committed blocks
|
|
@@ -700,29 +761,70 @@ sanitized (event handlers dropped, dangerous URL schemes → `#`).
|
|
|
700
761
|
|
|
701
762
|
Each renders as a `Component` block. Override it in React by tag name (or with
|
|
702
763
|
the generic `Component` fallback). The override receives `tag`, the sanitized
|
|
703
|
-
`attrs`,
|
|
704
|
-
|
|
764
|
+
`attrs`, the inner content as ready-to-render **`children`** (the easy path), and
|
|
765
|
+
also `html` (the inner already-rendered markdown string, for
|
|
766
|
+
`dangerouslySetInnerHTML`):
|
|
705
767
|
|
|
706
768
|
```tsx
|
|
707
769
|
<FluxMarkdown
|
|
708
770
|
client={client}
|
|
709
771
|
components={{
|
|
710
|
-
Thinking: ({
|
|
772
|
+
Thinking: ({ children }) => (
|
|
711
773
|
<details className="thinking">
|
|
712
774
|
<summary>Reasoning</summary>
|
|
713
|
-
|
|
775
|
+
{children}
|
|
714
776
|
</details>
|
|
715
777
|
),
|
|
716
778
|
}}
|
|
717
779
|
/>
|
|
718
780
|
```
|
|
719
781
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
782
|
+
> **`children` vs `html`.** A `Component` override that renders *neither* shows
|
|
783
|
+
> **empty** (a common first-try gotcha). Prefer **`children`** — a parsed React
|
|
784
|
+
> tree with nested overrides applied; reach for `dangerouslySetInnerHTML={{ __html:
|
|
785
|
+
> html }}` only when you need the raw string. `attrs` keys are React-form
|
|
786
|
+
> (`class`→`className`, `for`→`htmlFor`) so `{...attrs}` spreads cleanly. While
|
|
787
|
+
> streaming, both reflect the partial inner content and re-render as more arrives.
|
|
788
|
+
> With no override the block renders as `<thinking …>…</thinking>`. Tag names
|
|
789
|
+
> match case-sensitively; off unless `componentTags` is set.
|
|
790
|
+
|
|
791
|
+
<a id="inline-component-tags"></a>
|
|
792
|
+
|
|
793
|
+
#### Inline component tags
|
|
794
|
+
|
|
795
|
+
`componentTags` handles **block** containers (a `<Thinking>` on its own line). For
|
|
796
|
+
**inline** custom elements — ticker chips, citations, `@mentions`, inline tooltips
|
|
797
|
+
that sit *inside* a paragraph, heading, list item, or **table cell** — use
|
|
798
|
+
`inlineComponentTags`:
|
|
799
|
+
|
|
800
|
+
```tsx
|
|
801
|
+
const client = new FluxClient({ config: { inlineComponentTags: ["tik"] } });
|
|
802
|
+
|
|
803
|
+
<FluxMarkdown
|
|
804
|
+
client={client}
|
|
805
|
+
components={{
|
|
806
|
+
tik: ({ symbol, children }) => <span className="ticker">{children ?? symbol}</span>,
|
|
807
|
+
}}
|
|
808
|
+
/>;
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
Now `Apple <tik symbol="AAPL">AAPL</tik> rose 2%` (or self-closing
|
|
812
|
+
`<tik symbol="AAPL"/>`) dispatches the inline `<tik>` to `components.tik`: its
|
|
813
|
+
inner is parsed as **inline markdown** (the `children`), its attributes become
|
|
814
|
+
props, and it's **safe without `unsafeHtml`** (attributes sanitized, allowlisted
|
|
815
|
+
tags only). It works everywhere inline content does — **including table cells**.
|
|
816
|
+
Tag names match **case-sensitively** and dispatch verbatim to `components[tag]`
|
|
817
|
+
(`<tik>`→`components.tik`, `<Cite>`→`components.Cite`). The
|
|
818
|
+
two lists are independent: list a tag under `componentTags` for blocks,
|
|
819
|
+
`inlineComponentTags` for inline, or both for both. An allowlisted tag used in an
|
|
820
|
+
unsupported position degrades **inertly** (escaped) — it never consumes
|
|
821
|
+
surrounding content.
|
|
822
|
+
|
|
823
|
+
> **Link-bridge alternative.** Before `inlineComponentTags`, the way to get an
|
|
824
|
+
> inline custom element was the link bridge: emit `[$AAPL](tik://AAPL)` and
|
|
825
|
+
> override `a` to render a chip when the href scheme matches. It's XSS-safe and
|
|
826
|
+
> renders inline-in-cells too — `inlineComponentTags` simply replaces that
|
|
827
|
+
> workaround with first-class inline elements.
|
|
726
828
|
|
|
727
829
|
### Types
|
|
728
830
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flux-md",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Zero-dep streaming markdown for the browser. Rust→WASM core, Web Worker per stream, incremental parse with speculative closure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": ["./src/worker.ts", "./src/styles.css"],
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
".": "./src/index.ts",
|
|
11
11
|
"./client": "./src/client.ts",
|
|
12
12
|
"./react": "./src/react.tsx",
|
|
13
|
+
"./server": "./src/server.tsx",
|
|
13
14
|
"./dom": "./src/dom.ts",
|
|
14
15
|
"./element": "./src/element.ts",
|
|
15
16
|
"./vue": "./src/vue.ts",
|
package/src/html-to-react.ts
CHANGED
|
@@ -38,8 +38,17 @@ const URL_ATTRS = new Set(["href", "src", "xlink:href", "formaction", "action",
|
|
|
38
38
|
* strip control chars (C0, DEL, C1 — matching Rust char::is_control),
|
|
39
39
|
* lowercase, then match. The strip affects only the probe, never output. */
|
|
40
40
|
function safeUrl(value: string): string {
|
|
41
|
+
// Decode-STABLE probe: a value can be entity-decoded more than once before it
|
|
42
|
+
// reaches the DOM, so peel layers to a fixpoint before the scheme check —
|
|
43
|
+
// catches `javascript:` and double-encoded `javascript&#58;`. Only the
|
|
44
|
+
// probe is decoded; the returned value is untouched (safe URLs stay verbatim).
|
|
45
|
+
let decoded = value;
|
|
46
|
+
for (let prev = ""; decoded !== prev; ) {
|
|
47
|
+
prev = decoded;
|
|
48
|
+
decoded = decodeEntities(decoded);
|
|
49
|
+
}
|
|
41
50
|
// eslint-disable-next-line no-control-regex
|
|
42
|
-
const probe =
|
|
51
|
+
const probe = decoded.replace(/[\u0000-\u001f\u007f-\u009f]/g, "").replace(/^\s+/, "").toLowerCase();
|
|
43
52
|
if (
|
|
44
53
|
probe.startsWith("javascript:") ||
|
|
45
54
|
probe.startsWith("vbscript:") ||
|
|
@@ -106,7 +115,11 @@ function parseOpenTag(html: string, start: number) {
|
|
|
106
115
|
let i = start + 1;
|
|
107
116
|
let j = i;
|
|
108
117
|
while (j < html.length && /[a-zA-Z0-9-]/.test(html[j])) j++;
|
|
109
|
-
|
|
118
|
+
// Preserve the tag's ORIGINAL case so an inline custom-component element (e.g.
|
|
119
|
+
// `<Cite>`) dispatches to `components.Cite`. Standard elements the core emits
|
|
120
|
+
// are already lowercase; the semantic checks below (VOID, `input`, close-tag
|
|
121
|
+
// matching) lowercase as needed, so HTML behavior is unchanged.
|
|
122
|
+
const tag = html.slice(i, j);
|
|
110
123
|
i = j;
|
|
111
124
|
const attrs: Record<string, string | true> = {};
|
|
112
125
|
while (i < html.length) {
|
|
@@ -193,9 +206,9 @@ export function parseTrustedHtml(html: string): HNode[] {
|
|
|
193
206
|
}
|
|
194
207
|
if (html[lt + 1] === "/") {
|
|
195
208
|
const end = html.indexOf(">", lt);
|
|
196
|
-
const
|
|
209
|
+
const closeLower = html.slice(lt + 2, end === -1 ? html.length : end).trim().toLowerCase();
|
|
197
210
|
for (let s = stack.length - 1; s >= 0; s--) {
|
|
198
|
-
if (stack[s].tag ===
|
|
211
|
+
if (stack[s].tag.toLowerCase() === closeLower) {
|
|
199
212
|
stack.length = s;
|
|
200
213
|
break;
|
|
201
214
|
}
|
|
@@ -216,7 +229,7 @@ export function parseTrustedHtml(html: string): HNode[] {
|
|
|
216
229
|
const { tag, attrs, selfClose, next } = parseOpenTag(html, lt);
|
|
217
230
|
const el: Extract<HNode, { kind: "el" }> = { kind: "el", tag, attrs, children: [] };
|
|
218
231
|
push(el);
|
|
219
|
-
if (!selfClose && !VOID.has(tag)) stack.push(el);
|
|
232
|
+
if (!selfClose && !VOID.has(tag.toLowerCase())) stack.push(el);
|
|
220
233
|
i = next;
|
|
221
234
|
}
|
|
222
235
|
return root;
|
|
@@ -242,7 +255,7 @@ function attrsToProps(tag: string, attrs: Record<string, string | true>, key: st
|
|
|
242
255
|
}
|
|
243
256
|
// A static checkbox carries `checked` with no handler; render it
|
|
244
257
|
// uncontrolled so React doesn't warn about a missing onChange.
|
|
245
|
-
if (tag === "input" && lower === "checked") {
|
|
258
|
+
if (tag.toLowerCase() === "input" && lower === "checked") {
|
|
246
259
|
props.defaultChecked = value === true ? true : value;
|
|
247
260
|
continue;
|
|
248
261
|
}
|
|
@@ -262,13 +275,15 @@ function nodesToReact(nodes: HNode[], components: Components, keyPrefix: string)
|
|
|
262
275
|
const key = keyPrefix + idx;
|
|
263
276
|
const type = components[n.tag] ?? n.tag;
|
|
264
277
|
const props = attrsToProps(n.tag, n.attrs, key);
|
|
265
|
-
if (VOID.has(n.tag)) {
|
|
278
|
+
if (VOID.has(n.tag.toLowerCase())) {
|
|
266
279
|
out.push(createElement(type, props));
|
|
267
280
|
} else {
|
|
268
281
|
out.push(createElement(type, props, nodesToReact(n.children, components, key + ".")));
|
|
269
282
|
}
|
|
270
283
|
}
|
|
271
|
-
|
|
284
|
+
// `null` (not an empty array) for no children, so a self-closing / empty inline
|
|
285
|
+
// component's `children` is nullish and a `{children ?? fallback}` override fires.
|
|
286
|
+
return out.length === 0 ? null : out.length === 1 ? out[0] : out;
|
|
272
287
|
}
|
|
273
288
|
|
|
274
289
|
/**
|
package/src/react.tsx
CHANGED
|
@@ -308,7 +308,7 @@ function decodeMathText(html: string): string {
|
|
|
308
308
|
return decodeCodeText(html);
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
function blockKindProps(block: Block): BlockComponentProps {
|
|
311
|
+
export function blockKindProps(block: Block, components?: Components): BlockComponentProps {
|
|
312
312
|
const props: BlockComponentProps = {
|
|
313
313
|
block,
|
|
314
314
|
html: block.html,
|
|
@@ -343,6 +343,10 @@ function blockKindProps(block: Block): BlockComponentProps {
|
|
|
343
343
|
// An override replaces the `<tag>` wrapper, so it gets the *inner* HTML
|
|
344
344
|
// (markdown already rendered) rather than the full wrapped block.
|
|
345
345
|
props.html = componentInnerHtml(block.html, props.tag);
|
|
346
|
+
// Convenience: the inner markdown pre-parsed to a React tree (with nested
|
|
347
|
+
// tag/inline-component overrides applied). Render `{children}` directly
|
|
348
|
+
// instead of dangerouslySetInnerHTML-ing `html` — the easy, correct path.
|
|
349
|
+
props.children = htmlToReact(props.html, components ?? {});
|
|
346
350
|
} else if (block.kind.type === "Table") {
|
|
347
351
|
// Pure structured data (present only when `blockData` is on) — unlike
|
|
348
352
|
// `attrs` there is no React/DOM name-form divergence, so this is the same
|
|
@@ -440,12 +444,12 @@ function renderBlockContent({
|
|
|
440
444
|
const tag = (block.kind.data as { tag?: string } | undefined)?.tag;
|
|
441
445
|
const override = (tag && components[tag]) || components.Component;
|
|
442
446
|
if (override) {
|
|
443
|
-
return createElement(override, blockKindProps(block));
|
|
447
|
+
return createElement(override, blockKindProps(block, components));
|
|
444
448
|
}
|
|
445
449
|
}
|
|
446
450
|
const blockOverride = components[kind];
|
|
447
451
|
if (blockOverride) {
|
|
448
|
-
return createElement(blockOverride, blockKindProps(block));
|
|
452
|
+
return createElement(blockOverride, blockKindProps(block, components));
|
|
449
453
|
}
|
|
450
454
|
}
|
|
451
455
|
|
package/src/server.tsx
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { createElement, type ReactNode } from "react";
|
|
2
|
+
import initWasmAsync, { FluxParser, initSync } from "./wasm/flux_md_core.js";
|
|
3
|
+
import { htmlToReact } from "./html-to-react";
|
|
4
|
+
import { blockKindProps } from "./react";
|
|
5
|
+
import type { Block, Components, ParserConfig } from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Synchronous, worker-free server / static rendering for flux-md.
|
|
9
|
+
*
|
|
10
|
+
* The browser path runs the Rust→WASM core in a Web Worker, but the very same
|
|
11
|
+
* `FluxParser` is a plain synchronous class — so on the server (Node, RSC, a
|
|
12
|
+
* build step) you can parse a finished markdown string with no worker and no
|
|
13
|
+
* async ceremony:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { initFlux, renderToString } from "flux-md/server";
|
|
17
|
+
* await initFlux(); // once, at startup
|
|
18
|
+
* const html = renderToString(markdown); // sync, no worker
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* For React server rendering (RSC, static generation, SSR) use {@link
|
|
22
|
+
* FluxMarkdownStatic} — a hookless, RSC-safe component with the same `components`
|
|
23
|
+
* overrides. It targets **render-once** contexts; the streaming, interactive
|
|
24
|
+
* `<FluxMarkdown>` (client-side code highlighting, Mermaid, live updates) is a
|
|
25
|
+
* separate component. If you SSR-then-hydrate, use the *same* component on both
|
|
26
|
+
* sides.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
let ready = false;
|
|
30
|
+
|
|
31
|
+
/** Has the sync WASM core been initialized in this process? */
|
|
32
|
+
export function isFluxReady(): boolean {
|
|
33
|
+
return ready;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Initialize the sync core from compiled WASM bytes (or a `WebAssembly.Module`).
|
|
37
|
+
* Idempotent. Use on runtimes without a filesystem (edge) or to control exactly
|
|
38
|
+
* when init happens; otherwise {@link initFlux} auto-loads the co-located WASM. */
|
|
39
|
+
export function initFluxSync(wasm: BufferSource | WebAssembly.Module): void {
|
|
40
|
+
if (ready) return;
|
|
41
|
+
initSync({ module: wasm });
|
|
42
|
+
ready = true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let initPromise: Promise<void> | null = null;
|
|
46
|
+
|
|
47
|
+
/** Initialize the sync core once. In Node it reads the package's co-located
|
|
48
|
+
* `.wasm` off disk (Node's `fetch` can't load `file://`); on the web it fetches
|
|
49
|
+
* the bundler-resolved asset URL. Pass `{ wasm }` to supply bytes yourself
|
|
50
|
+
* (edge runtimes). Safe to call repeatedly / concurrently. */
|
|
51
|
+
export function initFlux(opts?: { wasm?: BufferSource | WebAssembly.Module }): Promise<void> {
|
|
52
|
+
if (ready) return Promise.resolve();
|
|
53
|
+
if (opts?.wasm) {
|
|
54
|
+
initFluxSync(opts.wasm);
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
}
|
|
57
|
+
if (!initPromise) {
|
|
58
|
+
initPromise = (async () => {
|
|
59
|
+
const wasmUrl = new URL("./wasm/flux_md_core_bg.wasm", import.meta.url);
|
|
60
|
+
if (wasmUrl.protocol === "file:") {
|
|
61
|
+
// Node: read the bytes (Node's fetch can't load file://). A non-literal
|
|
62
|
+
// specifier keeps `node:fs` out of web bundles and off tsc's module graph
|
|
63
|
+
// (no @types/node needed to compile this source).
|
|
64
|
+
const nodeFs = "node:fs/promises";
|
|
65
|
+
const { readFile } = await import(nodeFs);
|
|
66
|
+
initFluxSync(await readFile(wasmUrl));
|
|
67
|
+
} else {
|
|
68
|
+
await initWasmAsync({ module_or_path: wasmUrl });
|
|
69
|
+
ready = true;
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
}
|
|
73
|
+
return initPromise;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Configure a one-shot parser exactly as the worker does, so server output is
|
|
77
|
+
// byte-identical to the streamed/browser output (defaults: autolinks + alerts
|
|
78
|
+
// on, raw HTML escaped, footnotes/math off).
|
|
79
|
+
function makeParser(config?: ParserConfig): FluxParser {
|
|
80
|
+
const p = new FluxParser();
|
|
81
|
+
p.setGfmAutolinks(config?.gfmAutolinks ?? true);
|
|
82
|
+
p.setGfmAlerts(config?.gfmAlerts ?? true);
|
|
83
|
+
p.setGfmFootnotes(config?.gfmFootnotes ?? false);
|
|
84
|
+
p.setGfmMath(config?.gfmMath ?? false);
|
|
85
|
+
p.setDirAuto(config?.dirAuto ?? false);
|
|
86
|
+
p.setA11y(config?.a11y ?? false);
|
|
87
|
+
p.setUnsafeHtml(config?.unsafeHtml ?? false);
|
|
88
|
+
p.setComponentTags(config?.componentTags ?? []);
|
|
89
|
+
p.setInlineComponentTags(config?.inlineComponentTags ?? []);
|
|
90
|
+
p.setBlockData(config?.blockData ?? false);
|
|
91
|
+
return p;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function requireReady(): void {
|
|
95
|
+
if (!ready) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"flux-md/server: WASM not initialized. Call `await initFlux()` (or `initFluxSync(bytes)`) once before rendering.",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse a complete markdown string to its block array synchronously (committed +
|
|
104
|
+
* any trailing block, in document order). Requires {@link initFlux} to have run.
|
|
105
|
+
*/
|
|
106
|
+
export function parseToBlocks(markdown: string, opts?: { config?: ParserConfig }): Block[] {
|
|
107
|
+
requireReady();
|
|
108
|
+
const p = makeParser(opts?.config);
|
|
109
|
+
try {
|
|
110
|
+
p.append(markdown);
|
|
111
|
+
p.finalize();
|
|
112
|
+
return p.allBlocks() as Block[];
|
|
113
|
+
} finally {
|
|
114
|
+
p.free();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Render a complete markdown string to an HTML string synchronously — no worker,
|
|
120
|
+
* no React. The concatenated per-block HTML (XSS-safe with `unsafeHtml` off).
|
|
121
|
+
* For component dispatch / a `<FluxMarkdown>`-matching React tree, use
|
|
122
|
+
* {@link FluxMarkdownStatic} with your framework's server renderer instead.
|
|
123
|
+
*/
|
|
124
|
+
export function renderToString(markdown: string, opts?: { config?: ParserConfig }): string {
|
|
125
|
+
return parseToBlocks(markdown, opts)
|
|
126
|
+
.map((b) => b.html)
|
|
127
|
+
.join("");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Hookless block renderer (RSC-safe): mirrors the client renderer's dispatch
|
|
131
|
+
// (block-kind overrides, a Component block dispatched by tag, tag-level overrides
|
|
132
|
+
// via htmlToReact) but uses no hooks and skips the client-only interactive
|
|
133
|
+
// renderers (Mermaid; client-side code highlighting) — those activate on the
|
|
134
|
+
// client after hydration. Kept in step with react.tsx's renderBlockContent.
|
|
135
|
+
function renderStaticBlock(block: Block, components?: Components): ReactNode {
|
|
136
|
+
const kind = block.kind.type;
|
|
137
|
+
if (components) {
|
|
138
|
+
if (kind === "Component") {
|
|
139
|
+
const tag = (block.kind.data as { tag?: string } | undefined)?.tag;
|
|
140
|
+
const override = (tag && components[tag]) || components.Component;
|
|
141
|
+
if (override) return createElement(override, { key: block.id, ...blockKindProps(block, components) });
|
|
142
|
+
}
|
|
143
|
+
const blockOverride = components[kind];
|
|
144
|
+
if (blockOverride) return createElement(blockOverride, { key: block.id, ...blockKindProps(block, components) });
|
|
145
|
+
}
|
|
146
|
+
const className =
|
|
147
|
+
"flux-block flux-block-" +
|
|
148
|
+
kind.toLowerCase() +
|
|
149
|
+
(block.open ? " flux-open" : "") +
|
|
150
|
+
(block.speculative ? " flux-speculative" : "");
|
|
151
|
+
if (components) {
|
|
152
|
+
return createElement("div", { key: block.id, className }, htmlToReact(block.html, components));
|
|
153
|
+
}
|
|
154
|
+
return createElement("div", { key: block.id, className, dangerouslySetInnerHTML: { __html: block.html } });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface FluxMarkdownStaticProps {
|
|
158
|
+
/** The complete markdown to render (server/static use is for finished content). */
|
|
159
|
+
content: string;
|
|
160
|
+
/** Parser config (same shape as the streaming client's). */
|
|
161
|
+
config?: ParserConfig;
|
|
162
|
+
/** Tag-level / block-kind / component-tag overrides (see {@link Components}). */
|
|
163
|
+
components?: Components;
|
|
164
|
+
/** Appended to the root's `className` (the `flux-md` class is always present). */
|
|
165
|
+
className?: string;
|
|
166
|
+
/** Set on the root element. */
|
|
167
|
+
id?: string;
|
|
168
|
+
/** Set on the root element (e.g. `"article"`). */
|
|
169
|
+
role?: string;
|
|
170
|
+
/** Make the root a live region (parity with `<FluxMarkdown>` if you hydrate). */
|
|
171
|
+
"aria-live"?: "off" | "polite" | "assertive";
|
|
172
|
+
/** Live-region atomicity; pair with `aria-live`. */
|
|
173
|
+
"aria-atomic"?: boolean;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Synchronous, worker-free React rendering of finished markdown — a React Server
|
|
178
|
+
* Component, or any one-shot SSR / static render. Emits the `flux-md` root +
|
|
179
|
+
* per-block structure with the same `components` overrides (inline/block
|
|
180
|
+
* component tags dispatch here too). Requires {@link initFlux} (or
|
|
181
|
+
* {@link initFluxSync}) to have run. Uses no hooks (RSC-safe). A **render-once**
|
|
182
|
+
* component: for live streaming, client-side code highlighting, or Mermaid use
|
|
183
|
+
* the client `<FluxMarkdown>` instead (and if you SSR-then-hydrate, render the
|
|
184
|
+
* *same* component on both sides).
|
|
185
|
+
*/
|
|
186
|
+
export function FluxMarkdownStatic({
|
|
187
|
+
content,
|
|
188
|
+
config,
|
|
189
|
+
components,
|
|
190
|
+
className,
|
|
191
|
+
id,
|
|
192
|
+
role,
|
|
193
|
+
"aria-live": ariaLive,
|
|
194
|
+
"aria-atomic": ariaAtomic,
|
|
195
|
+
}: FluxMarkdownStaticProps): ReactNode {
|
|
196
|
+
const blocks = parseToBlocks(content, { config });
|
|
197
|
+
const comps = components && Object.keys(components).length > 0 ? components : undefined;
|
|
198
|
+
return createElement(
|
|
199
|
+
"div",
|
|
200
|
+
{
|
|
201
|
+
className: className ? `flux-md ${className}` : "flux-md",
|
|
202
|
+
id,
|
|
203
|
+
role,
|
|
204
|
+
"aria-live": ariaLive,
|
|
205
|
+
"aria-atomic": ariaAtomic,
|
|
206
|
+
},
|
|
207
|
+
blocks.map((b) => renderStaticBlock(b, comps)),
|
|
208
|
+
);
|
|
209
|
+
}
|
package/src/types-core.ts
CHANGED
|
@@ -116,8 +116,25 @@ export interface Patch {
|
|
|
116
116
|
export interface BlockComponentProps {
|
|
117
117
|
/** The full parsed block, including `kind` (with `kind.data`) and offsets. */
|
|
118
118
|
block: Block;
|
|
119
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Rendered, XSS-safe HTML for this block. For `Component` blocks this is the
|
|
121
|
+
* **inner** rendered-markdown HTML (not the `<tag>…</tag>` wrapper). NOTE: a
|
|
122
|
+
* `Component` override that ignores both `html` and `children` renders empty —
|
|
123
|
+
* use {@link children} (the easy path) or `dangerouslySetInnerHTML={{__html:
|
|
124
|
+
* html}}`.
|
|
125
|
+
*/
|
|
120
126
|
html: string;
|
|
127
|
+
/**
|
|
128
|
+
* React only: this block's inner content already parsed to a React node tree
|
|
129
|
+
* (markdown rendered, nested tag/inline-component overrides applied). For a
|
|
130
|
+
* `Component` block it is the inner markdown — render it directly
|
|
131
|
+
* (`return <Chip {...attrs}>{children}</Chip>`) instead of dangerously setting
|
|
132
|
+
* `html`. Populated by `<FluxMarkdown>` / `<FluxMarkdownStatic>` when a
|
|
133
|
+
* `components` map is supplied; DOM and other bindings leave it `undefined`
|
|
134
|
+
* (they consume `html`). Typed `unknown` to keep this surface framework-neutral
|
|
135
|
+
* — cast to `ReactNode` in a React override.
|
|
136
|
+
*/
|
|
137
|
+
children?: unknown;
|
|
121
138
|
/** True while the block is still streaming (its HTML may still change). */
|
|
122
139
|
open: boolean;
|
|
123
140
|
/** True if the block was closed speculatively and may yet be revised. */
|
|
@@ -229,6 +246,21 @@ export interface ParserConfig {
|
|
|
229
246
|
* off. Names match case-sensitively.
|
|
230
247
|
*/
|
|
231
248
|
componentTags?: string[];
|
|
249
|
+
/**
|
|
250
|
+
* Opt-in allowlist of INLINE component tag names (e.g. `["tik", "cite"]`). An
|
|
251
|
+
* allowlisted `<tik>…</tik>` (or self-closing `<tik/>`) anywhere in inline
|
|
252
|
+
* content — paragraphs, headings, table cells, list items — renders as a real
|
|
253
|
+
* custom element with **markdown** inner content and sanitized attributes
|
|
254
|
+
* (event handlers dropped, dangerous URL schemes neutralized) — XSS-safe
|
|
255
|
+
* without `unsafeHtml`. The React renderer dispatches it via `components[tag]`,
|
|
256
|
+
* with the inner markdown as the component's `children` and the sanitized
|
|
257
|
+
* attributes as props. Separate from `componentTags` (block containers): list a
|
|
258
|
+
* tag here for inline chips (tickers, citations, @mentions), or in both lists
|
|
259
|
+
* to allow both positions. Names match **case-sensitively** and dispatch
|
|
260
|
+
* verbatim to `components[tag]` (e.g. `"Cite"` → `components.Cite`), same as
|
|
261
|
+
* `componentTags`. Empty/omitted = off.
|
|
262
|
+
*/
|
|
263
|
+
inlineComponentTags?: string[];
|
|
232
264
|
/**
|
|
233
265
|
* Opt-in structured table data. When on, a `Table` block's `kind.data` is
|
|
234
266
|
* populated with `{ headers, rows, aligns }` (each cell `{ text, html }`) so a
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
export class FluxParser {
|
|
5
5
|
free(): void;
|
|
6
6
|
[Symbol.dispose](): void;
|
|
7
|
+
/**
|
|
8
|
+
* All blocks currently parsed (committed + active), in document order — the
|
|
9
|
+
* whole rendered document as a JS array of `Block`. The one-shot /
|
|
10
|
+
* server-side render primitive: feed the full markdown via `append`, call
|
|
11
|
+
* `finalize`, then read `allBlocks()` (no worker, no patch accumulation).
|
|
12
|
+
*/
|
|
13
|
+
allBlocks(): any;
|
|
7
14
|
append(chunk: string): any;
|
|
8
15
|
bufferLen(): number;
|
|
9
16
|
finalize(): any;
|
|
@@ -64,6 +71,14 @@ export class FluxParser {
|
|
|
64
71
|
* `<div class="math math-display">` for a KaTeX pass on the JS side.
|
|
65
72
|
*/
|
|
66
73
|
setGfmMath(on: boolean): void;
|
|
74
|
+
/**
|
|
75
|
+
* Set the opt-in INLINE component-tag allowlist (e.g. `["tik", "cite"]`).
|
|
76
|
+
* An allowlisted inline `<tik>…</tik>` (or self-closing `<tik/>`) renders as
|
|
77
|
+
* a custom element (markdown inner, sanitized attributes) so a JSX/DOM layer
|
|
78
|
+
* can dispatch it via `components[tag]` — in paragraphs, headings, table
|
|
79
|
+
* cells, and list items. Empty by default (inline output unchanged).
|
|
80
|
+
*/
|
|
81
|
+
setInlineComponentTags(tags: string[]): void;
|
|
67
82
|
/**
|
|
68
83
|
* Enable or disable raw-HTML pass-through. Default off. Do not enable
|
|
69
84
|
* when rendering untrusted input — bypasses XSS protection.
|
|
@@ -76,6 +91,7 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
76
91
|
export interface InitOutput {
|
|
77
92
|
readonly memory: WebAssembly.Memory;
|
|
78
93
|
readonly __wbg_fluxparser_free: (a: number, b: number) => void;
|
|
94
|
+
readonly fluxparser_allBlocks: (a: number, b: number) => void;
|
|
79
95
|
readonly fluxparser_append: (a: number, b: number, c: number, d: number) => void;
|
|
80
96
|
readonly fluxparser_bufferLen: (a: number) => number;
|
|
81
97
|
readonly fluxparser_finalize: (a: number, b: number) => void;
|
|
@@ -89,6 +105,7 @@ export interface InitOutput {
|
|
|
89
105
|
readonly fluxparser_setGfmAutolinks: (a: number, b: number) => void;
|
|
90
106
|
readonly fluxparser_setGfmFootnotes: (a: number, b: number) => void;
|
|
91
107
|
readonly fluxparser_setGfmMath: (a: number, b: number) => void;
|
|
108
|
+
readonly fluxparser_setInlineComponentTags: (a: number, b: number, c: number) => void;
|
|
92
109
|
readonly fluxparser_setUnsafeHtml: (a: number, b: number) => void;
|
|
93
110
|
readonly __wbindgen_export: (a: number, b: number) => number;
|
|
94
111
|
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
package/src/wasm/flux_md_core.js
CHANGED
|
@@ -11,6 +11,28 @@ export class FluxParser {
|
|
|
11
11
|
const ptr = this.__destroy_into_raw();
|
|
12
12
|
wasm.__wbg_fluxparser_free(ptr, 0);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* All blocks currently parsed (committed + active), in document order — the
|
|
16
|
+
* whole rendered document as a JS array of `Block`. The one-shot /
|
|
17
|
+
* server-side render primitive: feed the full markdown via `append`, call
|
|
18
|
+
* `finalize`, then read `allBlocks()` (no worker, no patch accumulation).
|
|
19
|
+
* @returns {any}
|
|
20
|
+
*/
|
|
21
|
+
allBlocks() {
|
|
22
|
+
try {
|
|
23
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
24
|
+
wasm.fluxparser_allBlocks(retptr, this.__wbg_ptr);
|
|
25
|
+
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
26
|
+
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
27
|
+
var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
|
|
28
|
+
if (r2) {
|
|
29
|
+
throw takeObject(r1);
|
|
30
|
+
}
|
|
31
|
+
return takeObject(r0);
|
|
32
|
+
} finally {
|
|
33
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
14
36
|
/**
|
|
15
37
|
* @param {string} chunk
|
|
16
38
|
* @returns {any}
|
|
@@ -149,6 +171,19 @@ export class FluxParser {
|
|
|
149
171
|
setGfmMath(on) {
|
|
150
172
|
wasm.fluxparser_setGfmMath(this.__wbg_ptr, on);
|
|
151
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Set the opt-in INLINE component-tag allowlist (e.g. `["tik", "cite"]`).
|
|
176
|
+
* An allowlisted inline `<tik>…</tik>` (or self-closing `<tik/>`) renders as
|
|
177
|
+
* a custom element (markdown inner, sanitized attributes) so a JSX/DOM layer
|
|
178
|
+
* can dispatch it via `components[tag]` — in paragraphs, headings, table
|
|
179
|
+
* cells, and list items. Empty by default (inline output unchanged).
|
|
180
|
+
* @param {string[]} tags
|
|
181
|
+
*/
|
|
182
|
+
setInlineComponentTags(tags) {
|
|
183
|
+
const ptr0 = passArrayJsValueToWasm0(tags, wasm.__wbindgen_export);
|
|
184
|
+
const len0 = WASM_VECTOR_LEN;
|
|
185
|
+
wasm.fluxparser_setInlineComponentTags(this.__wbg_ptr, ptr0, len0);
|
|
186
|
+
}
|
|
152
187
|
/**
|
|
153
188
|
* Enable or disable raw-HTML pass-through. Default off. Do not enable
|
|
154
189
|
* when rendering untrusted input — bypasses XSS protection.
|
|
Binary file
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
export const memory: WebAssembly.Memory;
|
|
4
4
|
export const __wbg_fluxparser_free: (a: number, b: number) => void;
|
|
5
|
+
export const fluxparser_allBlocks: (a: number, b: number) => void;
|
|
5
6
|
export const fluxparser_append: (a: number, b: number, c: number, d: number) => void;
|
|
6
7
|
export const fluxparser_bufferLen: (a: number) => number;
|
|
7
8
|
export const fluxparser_finalize: (a: number, b: number) => void;
|
|
@@ -15,6 +16,7 @@ export const fluxparser_setGfmAlerts: (a: number, b: number) => void;
|
|
|
15
16
|
export const fluxparser_setGfmAutolinks: (a: number, b: number) => void;
|
|
16
17
|
export const fluxparser_setGfmFootnotes: (a: number, b: number) => void;
|
|
17
18
|
export const fluxparser_setGfmMath: (a: number, b: number) => void;
|
|
19
|
+
export const fluxparser_setInlineComponentTags: (a: number, b: number, c: number) => void;
|
|
18
20
|
export const fluxparser_setUnsafeHtml: (a: number, b: number) => void;
|
|
19
21
|
export const __wbindgen_export: (a: number, b: number) => number;
|
|
20
22
|
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
package/src/wasm/package.json
CHANGED
package/src/worker.ts
CHANGED
|
@@ -30,6 +30,7 @@ const core = new WorkerCore({
|
|
|
30
30
|
p.setA11y(c?.a11y ?? false);
|
|
31
31
|
p.setUnsafeHtml(c?.unsafeHtml ?? false);
|
|
32
32
|
p.setComponentTags(c?.componentTags ?? []);
|
|
33
|
+
p.setInlineComponentTags(c?.inlineComponentTags ?? []);
|
|
33
34
|
p.setBlockData(c?.blockData ?? false);
|
|
34
35
|
return p;
|
|
35
36
|
},
|