@stream-mdx/unified 0.0.0-stage → 1.0.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 +102 -0
- package/README.md +74 -2
- package/dist/index.cjs +15615 -0
- package/dist/index.d.cts +87 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.mjs +326 -0
- package/package.json +57 -4
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as unified from 'unified';
|
|
2
|
+
import { PluggableList } from 'unified';
|
|
3
|
+
export { PluggableList } from 'unified';
|
|
4
|
+
import * as hast from 'hast';
|
|
5
|
+
import * as mdast from 'mdast';
|
|
6
|
+
import React$1 from 'react';
|
|
7
|
+
import { UrlTransform, BlockResolvedLinkReference } from '@stream-mdx/core';
|
|
8
|
+
import { NodePresentations, ComponentRegistry } from '@stream-mdx/react';
|
|
9
|
+
|
|
10
|
+
interface UnifiedBlockErrorContext {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly type: string;
|
|
13
|
+
readonly finalized: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface UnifiedRendererOptions {
|
|
16
|
+
remarkPlugins?: PluggableList;
|
|
17
|
+
rehypePlugins?: PluggableList;
|
|
18
|
+
components?: NodePresentations;
|
|
19
|
+
hastComponents?: Record<string, React.ComponentType<Record<string, unknown>>>;
|
|
20
|
+
/**
|
|
21
|
+
* Block types to route through the unified pipeline.
|
|
22
|
+
* Defaults to: ["paragraph", "heading", "blockquote", "list", "table", "code", "hr"]
|
|
23
|
+
*/
|
|
24
|
+
blockTypes?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Enable math support (remark-math). Defaults to false.
|
|
27
|
+
*/
|
|
28
|
+
math?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Enable GitHub Flavored Markdown (remark-gfm). Defaults to true.
|
|
31
|
+
*/
|
|
32
|
+
gfm?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum number of rendered blocks to retain in LRU cache. Defaults to 500.
|
|
35
|
+
*/
|
|
36
|
+
cacheSize?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Explicit callback invoked when a unified plugin encounters an error.
|
|
39
|
+
* When provided, errors are forwarded here instead of only being logged.
|
|
40
|
+
*/
|
|
41
|
+
onError?: (error: Error, context: UnifiedBlockErrorContext) => void;
|
|
42
|
+
urlTransform?: UrlTransform;
|
|
43
|
+
}
|
|
44
|
+
type UnifiedRenderer = NodePresentations & {
|
|
45
|
+
clearCache(): void;
|
|
46
|
+
options: UnifiedRendererOptions;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
declare function buildProcessor(options: UnifiedRendererOptions): unified.Processor<mdast.Root, mdast.Root, hast.Root, undefined, undefined>;
|
|
50
|
+
type UnifiedProcessor = ReturnType<typeof buildProcessor>;
|
|
51
|
+
type JobOutcome = {
|
|
52
|
+
readonly kind: "success";
|
|
53
|
+
readonly node: React$1.ReactNode;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: "error";
|
|
56
|
+
readonly error: Error;
|
|
57
|
+
};
|
|
58
|
+
interface UnifiedJob {
|
|
59
|
+
readonly promise: Promise<JobOutcome>;
|
|
60
|
+
outcome?: JobOutcome;
|
|
61
|
+
}
|
|
62
|
+
declare class BoundedLruCache<K, V> {
|
|
63
|
+
private readonly maxEntries;
|
|
64
|
+
private readonly map;
|
|
65
|
+
constructor(maxEntries?: number);
|
|
66
|
+
get(key: K): V | undefined;
|
|
67
|
+
set(key: K, value: V): void;
|
|
68
|
+
clear(): void;
|
|
69
|
+
get size(): number;
|
|
70
|
+
}
|
|
71
|
+
interface UnifiedBlockItemProps {
|
|
72
|
+
id: string;
|
|
73
|
+
type: string;
|
|
74
|
+
finalized: boolean;
|
|
75
|
+
source: string;
|
|
76
|
+
linkReferences?: readonly BlockResolvedLinkReference[];
|
|
77
|
+
element: React$1.ReactElement;
|
|
78
|
+
processor: UnifiedProcessor;
|
|
79
|
+
options: UnifiedRendererOptions;
|
|
80
|
+
cache: BoundedLruCache<string, React$1.ReactNode>;
|
|
81
|
+
inflightJobs: Map<string, UnifiedJob>;
|
|
82
|
+
registry: ComponentRegistry | null;
|
|
83
|
+
}
|
|
84
|
+
declare const UnifiedBlockItem: React$1.FC<UnifiedBlockItemProps>;
|
|
85
|
+
declare function createUnifiedRenderer(options?: UnifiedRendererOptions): UnifiedRenderer;
|
|
86
|
+
|
|
87
|
+
export { type UnifiedBlockErrorContext, UnifiedBlockItem, type UnifiedRenderer, type UnifiedRendererOptions, createUnifiedRenderer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as unified from 'unified';
|
|
2
|
+
import { PluggableList } from 'unified';
|
|
3
|
+
export { PluggableList } from 'unified';
|
|
4
|
+
import * as hast from 'hast';
|
|
5
|
+
import * as mdast from 'mdast';
|
|
6
|
+
import React$1 from 'react';
|
|
7
|
+
import { UrlTransform, BlockResolvedLinkReference } from '@stream-mdx/core';
|
|
8
|
+
import { NodePresentations, ComponentRegistry } from '@stream-mdx/react';
|
|
9
|
+
|
|
10
|
+
interface UnifiedBlockErrorContext {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly type: string;
|
|
13
|
+
readonly finalized: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface UnifiedRendererOptions {
|
|
16
|
+
remarkPlugins?: PluggableList;
|
|
17
|
+
rehypePlugins?: PluggableList;
|
|
18
|
+
components?: NodePresentations;
|
|
19
|
+
hastComponents?: Record<string, React.ComponentType<Record<string, unknown>>>;
|
|
20
|
+
/**
|
|
21
|
+
* Block types to route through the unified pipeline.
|
|
22
|
+
* Defaults to: ["paragraph", "heading", "blockquote", "list", "table", "code", "hr"]
|
|
23
|
+
*/
|
|
24
|
+
blockTypes?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Enable math support (remark-math). Defaults to false.
|
|
27
|
+
*/
|
|
28
|
+
math?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Enable GitHub Flavored Markdown (remark-gfm). Defaults to true.
|
|
31
|
+
*/
|
|
32
|
+
gfm?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum number of rendered blocks to retain in LRU cache. Defaults to 500.
|
|
35
|
+
*/
|
|
36
|
+
cacheSize?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Explicit callback invoked when a unified plugin encounters an error.
|
|
39
|
+
* When provided, errors are forwarded here instead of only being logged.
|
|
40
|
+
*/
|
|
41
|
+
onError?: (error: Error, context: UnifiedBlockErrorContext) => void;
|
|
42
|
+
urlTransform?: UrlTransform;
|
|
43
|
+
}
|
|
44
|
+
type UnifiedRenderer = NodePresentations & {
|
|
45
|
+
clearCache(): void;
|
|
46
|
+
options: UnifiedRendererOptions;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
declare function buildProcessor(options: UnifiedRendererOptions): unified.Processor<mdast.Root, mdast.Root, hast.Root, undefined, undefined>;
|
|
50
|
+
type UnifiedProcessor = ReturnType<typeof buildProcessor>;
|
|
51
|
+
type JobOutcome = {
|
|
52
|
+
readonly kind: "success";
|
|
53
|
+
readonly node: React$1.ReactNode;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: "error";
|
|
56
|
+
readonly error: Error;
|
|
57
|
+
};
|
|
58
|
+
interface UnifiedJob {
|
|
59
|
+
readonly promise: Promise<JobOutcome>;
|
|
60
|
+
outcome?: JobOutcome;
|
|
61
|
+
}
|
|
62
|
+
declare class BoundedLruCache<K, V> {
|
|
63
|
+
private readonly maxEntries;
|
|
64
|
+
private readonly map;
|
|
65
|
+
constructor(maxEntries?: number);
|
|
66
|
+
get(key: K): V | undefined;
|
|
67
|
+
set(key: K, value: V): void;
|
|
68
|
+
clear(): void;
|
|
69
|
+
get size(): number;
|
|
70
|
+
}
|
|
71
|
+
interface UnifiedBlockItemProps {
|
|
72
|
+
id: string;
|
|
73
|
+
type: string;
|
|
74
|
+
finalized: boolean;
|
|
75
|
+
source: string;
|
|
76
|
+
linkReferences?: readonly BlockResolvedLinkReference[];
|
|
77
|
+
element: React$1.ReactElement;
|
|
78
|
+
processor: UnifiedProcessor;
|
|
79
|
+
options: UnifiedRendererOptions;
|
|
80
|
+
cache: BoundedLruCache<string, React$1.ReactNode>;
|
|
81
|
+
inflightJobs: Map<string, UnifiedJob>;
|
|
82
|
+
registry: ComponentRegistry | null;
|
|
83
|
+
}
|
|
84
|
+
declare const UnifiedBlockItem: React$1.FC<UnifiedBlockItemProps>;
|
|
85
|
+
declare function createUnifiedRenderer(options?: UnifiedRendererOptions): UnifiedRenderer;
|
|
86
|
+
|
|
87
|
+
export { type UnifiedBlockErrorContext, UnifiedBlockItem, type UnifiedRenderer, type UnifiedRendererOptions, createUnifiedRenderer };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/unified-renderer.tsx
|
|
4
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
5
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
6
|
+
import { unified } from "unified";
|
|
7
|
+
import remarkParse from "remark-parse";
|
|
8
|
+
import remarkGfm from "remark-gfm";
|
|
9
|
+
import remarkMath from "remark-math";
|
|
10
|
+
import remarkRehype from "remark-rehype";
|
|
11
|
+
import { toJsxRuntime } from "hast-util-to-jsx-runtime";
|
|
12
|
+
import { defaultUrlTransform, isForbiddenAttribute } from "@stream-mdx/core";
|
|
13
|
+
import { ComponentRegistry, createRendererStore, renderNodePresentation } from "@stream-mdx/react";
|
|
14
|
+
import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
|
|
15
|
+
function sanitizeHastUrls(node, transform) {
|
|
16
|
+
if (!node || typeof node !== "object") return;
|
|
17
|
+
const record = node;
|
|
18
|
+
if (record.type === "element" && record.properties && typeof record.properties === "object") {
|
|
19
|
+
const properties = record.properties;
|
|
20
|
+
for (const key of Object.keys(properties)) {
|
|
21
|
+
if (isForbiddenAttribute(key)) {
|
|
22
|
+
delete properties[key];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (properties.href !== void 0 && properties.href !== null) {
|
|
26
|
+
const safe = transform(String(properties.href), "href", { source: "markdown" });
|
|
27
|
+
if (safe != null) {
|
|
28
|
+
properties.href = safe;
|
|
29
|
+
} else {
|
|
30
|
+
delete properties.href;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (properties.src !== void 0 && properties.src !== null) {
|
|
34
|
+
const safe = transform(String(properties.src), "src", { source: "markdown" });
|
|
35
|
+
if (safe != null) {
|
|
36
|
+
properties.src = safe;
|
|
37
|
+
} else {
|
|
38
|
+
delete properties.src;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (properties.action !== void 0 && properties.action !== null) {
|
|
42
|
+
const safe = transform(String(properties.action), "href", { source: "html" });
|
|
43
|
+
if (safe != null) {
|
|
44
|
+
properties.action = safe;
|
|
45
|
+
} else {
|
|
46
|
+
delete properties.action;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (properties.formAction !== void 0 && properties.formAction !== null) {
|
|
50
|
+
const safe = transform(String(properties.formAction), "href", { source: "html" });
|
|
51
|
+
if (safe != null) {
|
|
52
|
+
properties.formAction = safe;
|
|
53
|
+
} else {
|
|
54
|
+
delete properties.formAction;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (properties.xlinkHref !== void 0 && properties.xlinkHref !== null) {
|
|
58
|
+
const safe = transform(String(properties.xlinkHref), "href", { source: "html" });
|
|
59
|
+
if (safe != null) {
|
|
60
|
+
properties.xlinkHref = safe;
|
|
61
|
+
} else {
|
|
62
|
+
delete properties.xlinkHref;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (properties["xlink:href"] !== void 0 && properties["xlink:href"] !== null) {
|
|
66
|
+
const safe = transform(String(properties["xlink:href"]), "href", { source: "html" });
|
|
67
|
+
if (safe != null) {
|
|
68
|
+
properties["xlink:href"] = safe;
|
|
69
|
+
} else {
|
|
70
|
+
delete properties["xlink:href"];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(record.children)) {
|
|
75
|
+
for (const child of record.children) {
|
|
76
|
+
sanitizeHastUrls(child, transform);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function buildProcessor(options) {
|
|
81
|
+
const transform = options.urlTransform ?? defaultUrlTransform;
|
|
82
|
+
return unified().use(remarkParse).use(options.gfm === false ? [] : [remarkGfm]).use(options.math ? [remarkMath] : []).use(options.remarkPlugins ?? []).use(remarkRehype).use(options.rehypePlugins ?? []).use(() => (tree) => {
|
|
83
|
+
sanitizeHastUrls(tree, transform);
|
|
84
|
+
}).freeze();
|
|
85
|
+
}
|
|
86
|
+
var DEFAULT_ROUTED_BLOCK_TYPES = {
|
|
87
|
+
paragraph: true,
|
|
88
|
+
heading: true,
|
|
89
|
+
blockquote: true,
|
|
90
|
+
list: true,
|
|
91
|
+
table: true,
|
|
92
|
+
code: true,
|
|
93
|
+
hr: true
|
|
94
|
+
};
|
|
95
|
+
var BoundedLruCache = class {
|
|
96
|
+
constructor(maxEntries = 500) {
|
|
97
|
+
this.map = /* @__PURE__ */ new Map();
|
|
98
|
+
this.maxEntries = Math.max(1, maxEntries);
|
|
99
|
+
}
|
|
100
|
+
get(key) {
|
|
101
|
+
const value = this.map.get(key);
|
|
102
|
+
if (value !== void 0) {
|
|
103
|
+
this.map.delete(key);
|
|
104
|
+
this.map.set(key, value);
|
|
105
|
+
}
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
set(key, value) {
|
|
109
|
+
if (this.map.has(key)) {
|
|
110
|
+
this.map.delete(key);
|
|
111
|
+
} else if (this.map.size >= this.maxEntries) {
|
|
112
|
+
const oldestKey = this.map.keys().next().value;
|
|
113
|
+
if (oldestKey !== void 0) {
|
|
114
|
+
this.map.delete(oldestKey);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.map.set(key, value);
|
|
118
|
+
}
|
|
119
|
+
clear() {
|
|
120
|
+
this.map.clear();
|
|
121
|
+
}
|
|
122
|
+
get size() {
|
|
123
|
+
return this.map.size;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
function formatLinkDefinitions(refs) {
|
|
127
|
+
const lines = [];
|
|
128
|
+
for (const ref of refs) {
|
|
129
|
+
if (!ref.label || !ref.href) continue;
|
|
130
|
+
const cleanHref = `<${ref.href.replace(/>/g, "%3E")}>`;
|
|
131
|
+
if (ref.title) {
|
|
132
|
+
const cleanTitle = ref.title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
133
|
+
lines.push(`[${ref.label}]: ${cleanHref} "${cleanTitle}"`);
|
|
134
|
+
} else {
|
|
135
|
+
lines.push(`[${ref.label}]: ${cleanHref}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
140
|
+
function getOrCreateJob(cacheKey, input, context, processor, options, cache, inflightJobs) {
|
|
141
|
+
const existing = inflightJobs.get(cacheKey);
|
|
142
|
+
if (existing) {
|
|
143
|
+
return existing;
|
|
144
|
+
}
|
|
145
|
+
let resolve;
|
|
146
|
+
const promise = new Promise((res) => {
|
|
147
|
+
resolve = res;
|
|
148
|
+
});
|
|
149
|
+
const job = { promise };
|
|
150
|
+
inflightJobs.set(cacheKey, job);
|
|
151
|
+
let mdast;
|
|
152
|
+
try {
|
|
153
|
+
mdast = processor.parse(input);
|
|
154
|
+
} catch (parseError) {
|
|
155
|
+
const error = parseError instanceof Error ? parseError : new Error(String(parseError));
|
|
156
|
+
const outcome = { kind: "error", error };
|
|
157
|
+
job.outcome = outcome;
|
|
158
|
+
inflightJobs.delete(cacheKey);
|
|
159
|
+
resolve(outcome);
|
|
160
|
+
if (options.onError) {
|
|
161
|
+
options.onError(error, context);
|
|
162
|
+
} else {
|
|
163
|
+
console.error("Unified markdown parse failed:", error);
|
|
164
|
+
}
|
|
165
|
+
return job;
|
|
166
|
+
}
|
|
167
|
+
processor.run(mdast, (runError, hast) => {
|
|
168
|
+
if (runError || !hast) {
|
|
169
|
+
const error = runError instanceof Error ? runError : new Error(String(runError ?? "Unified block transformation failed"));
|
|
170
|
+
const outcome = { kind: "error", error };
|
|
171
|
+
job.outcome = outcome;
|
|
172
|
+
inflightJobs.delete(cacheKey);
|
|
173
|
+
resolve(outcome);
|
|
174
|
+
if (options.onError) {
|
|
175
|
+
options.onError(error, context);
|
|
176
|
+
} else {
|
|
177
|
+
console.error("Unified block transformation failed:", error);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const node = toJsxRuntime(hast, {
|
|
183
|
+
Fragment,
|
|
184
|
+
jsx,
|
|
185
|
+
jsxs,
|
|
186
|
+
components: options.hastComponents
|
|
187
|
+
});
|
|
188
|
+
const outcome = { kind: "success", node };
|
|
189
|
+
job.outcome = outcome;
|
|
190
|
+
cache.set(cacheKey, node);
|
|
191
|
+
inflightJobs.delete(cacheKey);
|
|
192
|
+
resolve(outcome);
|
|
193
|
+
} catch (renderError) {
|
|
194
|
+
const error = renderError instanceof Error ? renderError : new Error(String(renderError));
|
|
195
|
+
const outcome = { kind: "error", error };
|
|
196
|
+
job.outcome = outcome;
|
|
197
|
+
inflightJobs.delete(cacheKey);
|
|
198
|
+
resolve(outcome);
|
|
199
|
+
if (options.onError) {
|
|
200
|
+
options.onError(error, context);
|
|
201
|
+
} else {
|
|
202
|
+
console.error("Unified JSX runtime conversion failed:", error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
return job;
|
|
207
|
+
}
|
|
208
|
+
var UnifiedBlockItem = ({
|
|
209
|
+
id,
|
|
210
|
+
type,
|
|
211
|
+
finalized,
|
|
212
|
+
source,
|
|
213
|
+
linkReferences,
|
|
214
|
+
element,
|
|
215
|
+
processor,
|
|
216
|
+
options,
|
|
217
|
+
cache,
|
|
218
|
+
inflightJobs,
|
|
219
|
+
registry
|
|
220
|
+
}) => {
|
|
221
|
+
const block = useMemo(() => ({
|
|
222
|
+
id,
|
|
223
|
+
type,
|
|
224
|
+
isFinalized: finalized,
|
|
225
|
+
payload: {
|
|
226
|
+
raw: source,
|
|
227
|
+
...linkReferences === void 0 ? {} : { meta: { linkReferences } }
|
|
228
|
+
}
|
|
229
|
+
}), [finalized, id, linkReferences, source, type]);
|
|
230
|
+
const presentationStore = useMemo(
|
|
231
|
+
() => registry === null ? null : createRendererStore([block]),
|
|
232
|
+
[block, registry]
|
|
233
|
+
);
|
|
234
|
+
const metaRefs = linkReferences;
|
|
235
|
+
const serializedRefs = metaRefs && metaRefs.length > 0 ? JSON.stringify(metaRefs) : "";
|
|
236
|
+
const cacheKey = `${id}::${type}::${source}::${serializedRefs}`;
|
|
237
|
+
const cached = cache.get(cacheKey);
|
|
238
|
+
const job = useMemo(() => {
|
|
239
|
+
if (cached !== void 0) {
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
242
|
+
const linkDefs = metaRefs && metaRefs.length > 0 ? formatLinkDefinitions(metaRefs) : "";
|
|
243
|
+
const input = linkDefs ? `${linkDefs}
|
|
244
|
+
|
|
245
|
+
${source}` : source;
|
|
246
|
+
return getOrCreateJob(cacheKey, input, { id, type, finalized }, processor, options, cache, inflightJobs);
|
|
247
|
+
}, [cacheKey, cached, block, processor, options, cache, inflightJobs, metaRefs, source, finalized, id, type]);
|
|
248
|
+
const [asyncState, setAsyncState] = useState(null);
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
if (!job || job.outcome !== void 0) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
let cancelled = false;
|
|
254
|
+
job.promise.then((outcome) => {
|
|
255
|
+
if (!cancelled && outcome.kind === "success") {
|
|
256
|
+
setAsyncState({ key: cacheKey, node: outcome.node });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return () => {
|
|
260
|
+
cancelled = true;
|
|
261
|
+
};
|
|
262
|
+
}, [cacheKey, job]);
|
|
263
|
+
let output = element;
|
|
264
|
+
if (cached !== void 0) {
|
|
265
|
+
output = cached;
|
|
266
|
+
} else if (job?.outcome !== void 0) {
|
|
267
|
+
if (job.outcome.kind === "success") {
|
|
268
|
+
output = job.outcome.node;
|
|
269
|
+
}
|
|
270
|
+
} else if (asyncState !== null && asyncState.key === cacheKey) {
|
|
271
|
+
output = asyncState.node;
|
|
272
|
+
}
|
|
273
|
+
const presentedOutput = registry === null || presentationStore === null ? output : renderNodePresentation(block, output, presentationStore, registry);
|
|
274
|
+
if (React.isValidElement(presentedOutput)) {
|
|
275
|
+
return presentedOutput;
|
|
276
|
+
}
|
|
277
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: presentedOutput });
|
|
278
|
+
};
|
|
279
|
+
function createUnifiedRenderer(options = {}) {
|
|
280
|
+
const cache = new BoundedLruCache(options.cacheSize ?? 500);
|
|
281
|
+
const inflightJobs = /* @__PURE__ */ new Map();
|
|
282
|
+
const processor = buildProcessor(options);
|
|
283
|
+
const registry = Object.keys(options.components ?? {}).length > 0 ? new ComponentRegistry({ components: options.components }) : null;
|
|
284
|
+
const routedTypeMap = options.blockTypes ? Object.fromEntries(options.blockTypes.map((type) => [type, true])) : DEFAULT_ROUTED_BLOCK_TYPES;
|
|
285
|
+
const presentationTypes = options.blockTypes ?? Object.keys(DEFAULT_ROUTED_BLOCK_TYPES);
|
|
286
|
+
const renderUnifiedBlock = ({ metadata, actions, children }) => {
|
|
287
|
+
if (!metadata.isFinalized || !routedTypeMap[metadata.type]) return /* @__PURE__ */ jsx2(Fragment2, { children });
|
|
288
|
+
const element = React.isValidElement(children) ? children : /* @__PURE__ */ jsx2(Fragment2, { children });
|
|
289
|
+
return /* @__PURE__ */ jsx2(
|
|
290
|
+
UnifiedBlockItem,
|
|
291
|
+
{
|
|
292
|
+
id: metadata.id,
|
|
293
|
+
type: metadata.type,
|
|
294
|
+
finalized: metadata.isFinalized,
|
|
295
|
+
source: actions.readSource(),
|
|
296
|
+
linkReferences: metadata.linkReferences,
|
|
297
|
+
element,
|
|
298
|
+
processor,
|
|
299
|
+
options,
|
|
300
|
+
cache,
|
|
301
|
+
inflightJobs,
|
|
302
|
+
registry
|
|
303
|
+
},
|
|
304
|
+
metadata.id
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
const renderer = {
|
|
308
|
+
...Object.fromEntries(
|
|
309
|
+
presentationTypes.map((type) => [type, { wrapper: renderUnifiedBlock }])
|
|
310
|
+
),
|
|
311
|
+
clearCache() {
|
|
312
|
+
cache.clear();
|
|
313
|
+
inflightJobs.clear();
|
|
314
|
+
},
|
|
315
|
+
options
|
|
316
|
+
};
|
|
317
|
+
Object.defineProperties(renderer, {
|
|
318
|
+
clearCache: { enumerable: false },
|
|
319
|
+
options: { enumerable: false }
|
|
320
|
+
});
|
|
321
|
+
return renderer;
|
|
322
|
+
}
|
|
323
|
+
export {
|
|
324
|
+
UnifiedBlockItem,
|
|
325
|
+
createUnifiedRenderer
|
|
326
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-mdx/unified",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Remark and Rehype unified plugin adapter for StreamMDX",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/kmccleary3301/stream-mdx.git",
|
|
9
|
+
"directory": "packages/markdown-v2-unified"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.mjs",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.mjs",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"CHANGELOG.md"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "npm run clean && tsup",
|
|
33
|
+
"test": "tsx ../../scripts/run-tests.ts __tests__",
|
|
34
|
+
"clean": "rm -rf dist",
|
|
35
|
+
"prepack": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"hast-util-to-jsx-runtime": "^2.3.6",
|
|
39
|
+
"remark-gfm": "^4.0.1",
|
|
40
|
+
"remark-math": "^6.0.0",
|
|
41
|
+
"remark-parse": "^11.0.0",
|
|
42
|
+
"remark-rehype": "^11.1.2",
|
|
43
|
+
"unified": "^11.0.5"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@stream-mdx/core": "^1.0.0",
|
|
47
|
+
"@stream-mdx/react": "^1.0.0",
|
|
48
|
+
"react": ">=18.2.0",
|
|
49
|
+
"react-dom": ">=18.2.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"@stream-mdx/core": {
|
|
53
|
+
"optional": true
|
|
54
|
+
},
|
|
55
|
+
"@stream-mdx/react": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|