@brett_lamy/docstream 0.3.2 → 0.3.3
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 +7 -0
- package/package.json +5 -1
- package/src/docs/DocsRenderer.tsx +31 -26
- package/src/index.ts +2 -2
- package/src/playground/PlaygroundStreamdown.tsx +43 -0
package/README.md
CHANGED
|
@@ -41,6 +41,13 @@ If your TypeScript app checks CSS side-effect imports, include Vite's standard e
|
|
|
41
41
|
|
|
42
42
|
Use `GitbookStreamdown` when markdown may arrive incrementally from an AI stream. The component accepts either `markdown` or string children.
|
|
43
43
|
|
|
44
|
+
Markdown-only applications can import the renderer from the `streamdown`
|
|
45
|
+
subpath. This keeps optional playground integrations out of the host bundle:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { GitbookStreamdown } from "@brett_lamy/docstream/streamdown"
|
|
49
|
+
```
|
|
50
|
+
|
|
44
51
|
```tsx
|
|
45
52
|
import { GitbookStreamdown } from "@brett_lamy/docstream"
|
|
46
53
|
import "@brett_lamy/docstream/styles.css"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brett_lamy/docstream",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "GitBook-aware readonly markdown and AI stream renderer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./src/gitbook/index.ts",
|
|
22
22
|
"import": "./src/gitbook/index.ts"
|
|
23
23
|
},
|
|
24
|
+
"./streamdown": {
|
|
25
|
+
"types": "./src/streamdown.tsx",
|
|
26
|
+
"import": "./src/streamdown.tsx"
|
|
27
|
+
},
|
|
24
28
|
"./assets": {
|
|
25
29
|
"types": "./src/assets.ts",
|
|
26
30
|
"import": "./src/assets.ts"
|
|
@@ -16,7 +16,14 @@ import { VideoEmbed } from "../video"
|
|
|
16
16
|
import { OpenApiOperation } from "../openapi/OpenApiOperation"
|
|
17
17
|
import { Mermaid } from "./Mermaid"
|
|
18
18
|
import { HighlightedCode } from "./HighlightedCode"
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
export interface LivePreviewProps {
|
|
21
|
+
files: Record<string, string>
|
|
22
|
+
entry: string
|
|
23
|
+
title: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type LivePreviewRenderer = (props: LivePreviewProps) => ReactNode
|
|
20
27
|
|
|
21
28
|
function InlineText({ nodes }: { nodes: Inline[] }) {
|
|
22
29
|
return (
|
|
@@ -72,7 +79,7 @@ function isDirectVideo(url: string): boolean {
|
|
|
72
79
|
return /\.(?:mp4|webm|ogg)(?:[?#]|$)/i.test(url)
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
function Tabs({ block }: { block: Extract<Block, { type: "tabs" }
|
|
82
|
+
function Tabs({ block, liveRenderer }: { block: Extract<Block, { type: "tabs" }>; liveRenderer?: LivePreviewRenderer }) {
|
|
76
83
|
const [active, setActive] = useState(0)
|
|
77
84
|
return (
|
|
78
85
|
<div className="docs-tabs">
|
|
@@ -88,13 +95,13 @@ function Tabs({ block }: { block: Extract<Block, { type: "tabs" }> }) {
|
|
|
88
95
|
))}
|
|
89
96
|
</div>
|
|
90
97
|
<div className="docs-tabs-body">
|
|
91
|
-
<Blocks blocks={block.tabs[active]?.children ?? []} />
|
|
98
|
+
<Blocks blocks={block.tabs[active]?.children ?? []} liveRenderer={liveRenderer} />
|
|
92
99
|
</div>
|
|
93
100
|
</div>
|
|
94
101
|
)
|
|
95
102
|
}
|
|
96
103
|
|
|
97
|
-
function BlockView({ block }: { block: Block }) {
|
|
104
|
+
function BlockView({ block, liveRenderer }: { block: Block; liveRenderer?: LivePreviewRenderer }) {
|
|
98
105
|
switch (block.type) {
|
|
99
106
|
case "paragraph":
|
|
100
107
|
return (
|
|
@@ -112,15 +119,13 @@ function BlockView({ block }: { block: Block }) {
|
|
|
112
119
|
}
|
|
113
120
|
case "code":
|
|
114
121
|
if (block.language === "mermaid") return <Mermaid code={block.code} />
|
|
115
|
-
if (block.live && isReactLanguage(block.language)) {
|
|
122
|
+
if (block.live && isReactLanguage(block.language) && liveRenderer) {
|
|
116
123
|
const entry = block.entry ?? "/src/main.jsx"
|
|
117
|
-
return (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
/>
|
|
123
|
-
)
|
|
124
|
+
return liveRenderer({
|
|
125
|
+
files: { [entry]: block.code },
|
|
126
|
+
entry,
|
|
127
|
+
title: block.title ?? "Live React preview",
|
|
128
|
+
})
|
|
124
129
|
}
|
|
125
130
|
return (
|
|
126
131
|
<div className="docs-code">
|
|
@@ -145,19 +150,19 @@ function BlockView({ block }: { block: Block }) {
|
|
|
145
150
|
<div className={`docs-hint docs-hint-${block.style}`}>
|
|
146
151
|
<Icon className="docs-hint-icon" />
|
|
147
152
|
<div>
|
|
148
|
-
<Blocks blocks={block.children} />
|
|
153
|
+
<Blocks blocks={block.children} liveRenderer={liveRenderer} />
|
|
149
154
|
</div>
|
|
150
155
|
</div>
|
|
151
156
|
)
|
|
152
157
|
}
|
|
153
158
|
case "tabs":
|
|
154
|
-
return <Tabs block={block} />
|
|
159
|
+
return <Tabs block={block} liveRenderer={liveRenderer} />
|
|
155
160
|
case "expandable":
|
|
156
161
|
return (
|
|
157
162
|
<details className="docs-expandable">
|
|
158
163
|
<summary>{block.summary}</summary>
|
|
159
164
|
<div className="docs-expandable-body">
|
|
160
|
-
<Blocks blocks={block.children} />
|
|
165
|
+
<Blocks blocks={block.children} liveRenderer={liveRenderer} />
|
|
161
166
|
</div>
|
|
162
167
|
</details>
|
|
163
168
|
)
|
|
@@ -172,7 +177,7 @@ function BlockView({ block }: { block: Block }) {
|
|
|
172
177
|
</div>
|
|
173
178
|
<div className="docs-step-main">
|
|
174
179
|
{s.title && <div className="docs-step-title">{s.title}</div>}
|
|
175
|
-
<Blocks blocks={s.children} />
|
|
180
|
+
<Blocks blocks={s.children} liveRenderer={liveRenderer} />
|
|
176
181
|
</div>
|
|
177
182
|
</div>
|
|
178
183
|
))}
|
|
@@ -215,7 +220,7 @@ function BlockView({ block }: { block: Block }) {
|
|
|
215
220
|
<div className="docs-columns">
|
|
216
221
|
{block.columns.map((c, i) => (
|
|
217
222
|
<div key={i} className="docs-column">
|
|
218
|
-
<Blocks blocks={c.children} />
|
|
223
|
+
<Blocks blocks={c.children} liveRenderer={liveRenderer} />
|
|
219
224
|
</div>
|
|
220
225
|
))}
|
|
221
226
|
</div>
|
|
@@ -234,7 +239,7 @@ function BlockView({ block }: { block: Block }) {
|
|
|
234
239
|
{block.items.map((item, i) => (
|
|
235
240
|
<li key={i}>
|
|
236
241
|
{block.task && <input type="checkbox" checked={!!item.checked} readOnly />}
|
|
237
|
-
<Blocks blocks={item.children} inline />
|
|
242
|
+
<Blocks blocks={item.children} inline liveRenderer={liveRenderer} />
|
|
238
243
|
</li>
|
|
239
244
|
))}
|
|
240
245
|
</Tag>
|
|
@@ -243,7 +248,7 @@ function BlockView({ block }: { block: Block }) {
|
|
|
243
248
|
case "blockquote":
|
|
244
249
|
return (
|
|
245
250
|
<blockquote>
|
|
246
|
-
<Blocks blocks={block.children} />
|
|
251
|
+
<Blocks blocks={block.children} liveRenderer={liveRenderer} />
|
|
247
252
|
</blockquote>
|
|
248
253
|
)
|
|
249
254
|
case "divider":
|
|
@@ -298,7 +303,7 @@ function BlockView({ block }: { block: Block }) {
|
|
|
298
303
|
<div key={i} className="docs-update">
|
|
299
304
|
<div className="docs-update-date">{u.date}</div>
|
|
300
305
|
<div className="docs-update-body">
|
|
301
|
-
<Blocks blocks={u.children} />
|
|
306
|
+
<Blocks blocks={u.children} liveRenderer={liveRenderer} />
|
|
302
307
|
</div>
|
|
303
308
|
</div>
|
|
304
309
|
))}
|
|
@@ -319,11 +324,11 @@ function isReactLanguage(language: string | null): boolean {
|
|
|
319
324
|
return language === "jsx" || language === "tsx" || language === "react"
|
|
320
325
|
}
|
|
321
326
|
|
|
322
|
-
function Blocks({ blocks, inline }: { blocks: Block[]; inline?: boolean }) {
|
|
327
|
+
function Blocks({ blocks, inline, liveRenderer }: { blocks: Block[]; inline?: boolean; liveRenderer?: LivePreviewRenderer }) {
|
|
323
328
|
return (
|
|
324
329
|
<div className={inline ? "docs-blocks-inline" : undefined} style={inline ? { display: "inline" } : undefined}>
|
|
325
330
|
{blocks.map((b, i) => (
|
|
326
|
-
<BlockView key={i} block={b} />
|
|
331
|
+
<BlockView key={i} block={b} liveRenderer={liveRenderer} />
|
|
327
332
|
))}
|
|
328
333
|
</div>
|
|
329
334
|
)
|
|
@@ -331,14 +336,14 @@ function Blocks({ blocks, inline }: { blocks: Block[]; inline?: boolean }) {
|
|
|
331
336
|
|
|
332
337
|
// Renders a markdown string inline — used for embedded markdown like
|
|
333
338
|
// OpenAPI descriptions, which can themselves contain GitBook blocks.
|
|
334
|
-
export function MarkdownContent({ markdown }: { markdown: string }) {
|
|
335
|
-
return <Blocks blocks={parseMarkdown(markdown).children} />
|
|
339
|
+
export function MarkdownContent({ markdown, liveRenderer }: { markdown: string; liveRenderer?: LivePreviewRenderer }) {
|
|
340
|
+
return <Blocks blocks={parseMarkdown(markdown).children} liveRenderer={liveRenderer} />
|
|
336
341
|
}
|
|
337
342
|
|
|
338
|
-
export function DocsRenderer({ doc }: { doc: DocumentNode }) {
|
|
343
|
+
export function DocsRenderer({ doc, liveRenderer }: { doc: DocumentNode; liveRenderer?: LivePreviewRenderer }) {
|
|
339
344
|
return (
|
|
340
345
|
<article className="docs-article">
|
|
341
|
-
<Blocks blocks={doc.children} />
|
|
346
|
+
<Blocks blocks={doc.children} liveRenderer={liveRenderer} />
|
|
342
347
|
</article>
|
|
343
348
|
)
|
|
344
349
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { GitbookStreamdown } from "./
|
|
2
|
-
export type { GitbookStreamdownProps } from "./
|
|
1
|
+
export { PlaygroundStreamdown as GitbookStreamdown } from "./playground/PlaygroundStreamdown"
|
|
2
|
+
export type { PlaygroundStreamdownProps as GitbookStreamdownProps } from "./playground/PlaygroundStreamdown"
|
|
3
3
|
export { DocsRenderer, MarkdownContent } from "./docs/DocsRenderer"
|
|
4
4
|
export { ReplayEmbed, ReplayPreview } from "./replay"
|
|
5
5
|
export type {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
|
|
3
|
+
import { DocsRenderer, type LivePreviewRenderer } from "../docs/DocsRenderer"
|
|
4
|
+
import { parseMarkdown } from "../gitbook/parse"
|
|
5
|
+
import { ReactCodePreview } from "./ReactCodePreview"
|
|
6
|
+
|
|
7
|
+
export interface PlaygroundStreamdownProps {
|
|
8
|
+
children?: string
|
|
9
|
+
className?: string
|
|
10
|
+
isAnimating?: boolean
|
|
11
|
+
isStreaming?: boolean
|
|
12
|
+
markdown?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The full renderer variant. It keeps the optional almost-node playground
|
|
17
|
+
* behind an explicit import boundary; markdown-only consumers should use the
|
|
18
|
+
* `@brett_lamy/docstream/streamdown` entry instead.
|
|
19
|
+
*/
|
|
20
|
+
export function PlaygroundStreamdown({
|
|
21
|
+
children,
|
|
22
|
+
className,
|
|
23
|
+
isAnimating,
|
|
24
|
+
isStreaming = false,
|
|
25
|
+
markdown,
|
|
26
|
+
}: PlaygroundStreamdownProps) {
|
|
27
|
+
const content = markdown ?? children ?? ""
|
|
28
|
+
const doc = useMemo(() => parseMarkdown(content), [content])
|
|
29
|
+
const liveRenderer: LivePreviewRenderer = ({ files, entry, title }) => (
|
|
30
|
+
<ReactCodePreview files={files} entry={entry} title={title} />
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
aria-busy={isAnimating ?? isStreaming}
|
|
36
|
+
className={className}
|
|
37
|
+
data-docstream=""
|
|
38
|
+
data-streaming={isStreaming ? "" : undefined}
|
|
39
|
+
>
|
|
40
|
+
<DocsRenderer doc={doc} liveRenderer={liveRenderer} />
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|