@brett_lamy/docstream 0.3.1 → 0.3.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 +36 -0
- package/package.json +15 -4
- package/src/docs/DocsRenderer.tsx +6 -1
- package/src/env.d.ts +4 -0
- package/src/gitbook/ast.ts +6 -0
- package/src/gitbook/parse.ts +23 -1
- package/src/gitbook/serialize.ts +12 -2
- package/src/index.ts +2 -0
- package/src/styles.css +16 -0
- package/src/viz/VizEmbed.tsx +42 -0
- package/src/viz/index.ts +4 -0
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ GitBook-aware markdown rendering for React applications and AI streaming surface
|
|
|
12
12
|
- Syntax-highlighted code blocks through `lowlight`.
|
|
13
13
|
- GitBook block support for hints, tabs, expandables, steppers, embeds, content refs, columns, figures, tables, math, dividers, updates, and OpenAPI operations.
|
|
14
14
|
- CSS exported as a stable package entrypoint so host apps can theme with CSS variables or shadcn-style design tokens.
|
|
15
|
+
- Attribute-aware direct video embeds for muted, looping inline clips in long-form posts.
|
|
16
|
+
- Optional `VizEmbed` integration for mounting deterministic `@brett_lamy/viz-engine` scenes in a document.
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
@@ -139,6 +141,40 @@ Call the API.
|
|
|
139
141
|
|
|
140
142
|
When a spec cannot be resolved, the renderer displays a fallback asking for an OpenAPI spec URL.
|
|
141
143
|
|
|
144
|
+
### Inline video clips
|
|
145
|
+
|
|
146
|
+
Direct media embeds can opt into browser-safe inline playback. `muted` is
|
|
147
|
+
important when `autoplay` is enabled:
|
|
148
|
+
|
|
149
|
+
```md
|
|
150
|
+
{% embed url="/generated/example/clips/offsets.mp4"
|
|
151
|
+
title="A reader resumes from its bookmark"
|
|
152
|
+
autoplay="true" loop="true" muted="true" controls="false" %}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The attributes round-trip through `parseMarkdown` and `serializeMarkdown` and
|
|
156
|
+
are rendered as a native `<video playsInline>` element.
|
|
157
|
+
|
|
158
|
+
### VizEngine scenes
|
|
159
|
+
|
|
160
|
+
Install the optional peer dependency when a document needs a live, seekable
|
|
161
|
+
scene rather than a rendered clip:
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
npm install @brett_lamy/docstream @brett_lamy/viz-engine
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { VizEmbed } from "@brett_lamy/docstream/viz"
|
|
169
|
+
import "@brett_lamy/docstream/styles.css"
|
|
170
|
+
|
|
171
|
+
<VizEmbed scene={scene} title="The same event, replayed into two projections" />
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`VizEmbed` keeps the scene's timeline deterministic and delegates the clock to
|
|
175
|
+
VizEngine, so a reader can pause or scrub the same explanation used to produce
|
|
176
|
+
the short clip.
|
|
177
|
+
|
|
142
178
|
## Assets and OpenAPI Specs
|
|
143
179
|
|
|
144
180
|
Relative image and OpenAPI spec paths can be resolved against an asset base:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brett_lamy/docstream",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "GitBook-aware readonly markdown and AI stream renderer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"types": "./src/video/index.ts",
|
|
38
38
|
"import": "./src/video/index.ts"
|
|
39
39
|
},
|
|
40
|
+
"./viz": {
|
|
41
|
+
"types": "./src/viz/index.ts",
|
|
42
|
+
"import": "./src/viz/index.ts"
|
|
43
|
+
},
|
|
40
44
|
"./playground": {
|
|
41
45
|
"types": "./src/playground/index.ts",
|
|
42
46
|
"import": "./src/playground/index.ts"
|
|
@@ -50,14 +54,21 @@
|
|
|
50
54
|
"mermaid": "^11.15.0",
|
|
51
55
|
"rrweb": "2.0.0-alpha.20",
|
|
52
56
|
"streamdown": "^2.5.0",
|
|
53
|
-
"yaml": "^2.9.0"
|
|
57
|
+
"yaml": "^2.9.0",
|
|
58
|
+
"@brett_lamy/viz-engine": "^0.2.0"
|
|
54
59
|
},
|
|
55
60
|
"peerDependencies": {
|
|
56
|
-
"
|
|
57
|
-
"@
|
|
61
|
+
"@agent-wasm/core": ">=0.4.0",
|
|
62
|
+
"@brett_lamy/viz-engine": ">=0.2.0",
|
|
63
|
+
"react": ">=18"
|
|
58
64
|
},
|
|
59
65
|
"repository": {
|
|
60
66
|
"type": "git",
|
|
61
67
|
"url": "git+https://github.com/BLamy/docstream.git"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@agent-wasm/core": "^0.4.0",
|
|
71
|
+
"@types/react": "^18.3.3",
|
|
72
|
+
"typescript": "^5.5.4"
|
|
62
73
|
}
|
|
63
74
|
}
|
|
@@ -187,7 +187,12 @@ function BlockView({ block }: { block: Block }) {
|
|
|
187
187
|
<VideoEmbed
|
|
188
188
|
src={src ?? block.url}
|
|
189
189
|
className="docs-embed"
|
|
190
|
-
title={block.url}
|
|
190
|
+
title={block.title ?? block.url}
|
|
191
|
+
{...(block.autoplay === undefined ? {} : { autoplay: block.autoplay })}
|
|
192
|
+
{...(block.loop === undefined ? {} : { loop: block.loop })}
|
|
193
|
+
{...(block.muted === undefined ? {} : { muted: block.muted })}
|
|
194
|
+
{...(block.controls === undefined ? {} : { controls: block.controls })}
|
|
195
|
+
{...(block.poster === undefined ? {} : { poster: resolveAsset(block.poster) })}
|
|
191
196
|
/>
|
|
192
197
|
) : (
|
|
193
198
|
<a className="docs-content-ref" href={block.url} target="_blank" rel="noreferrer">
|
package/src/env.d.ts
ADDED
package/src/gitbook/ast.ts
CHANGED
|
@@ -85,6 +85,12 @@ export interface StepperNode {
|
|
|
85
85
|
export interface EmbedNode {
|
|
86
86
|
type: "embed"
|
|
87
87
|
url: string
|
|
88
|
+
title?: string
|
|
89
|
+
autoplay?: boolean
|
|
90
|
+
loop?: boolean
|
|
91
|
+
muted?: boolean
|
|
92
|
+
controls?: boolean
|
|
93
|
+
poster?: string
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
export interface ContentRefNode {
|
package/src/gitbook/parse.ts
CHANGED
|
@@ -66,6 +66,11 @@ function parseAttrs(raw: string | undefined): Record<string, string> {
|
|
|
66
66
|
return attrs
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function booleanAttr(attrs: Record<string, string>, key: string): boolean | undefined {
|
|
70
|
+
if (!(key in attrs)) return undefined
|
|
71
|
+
return attrs[key] !== "false"
|
|
72
|
+
}
|
|
73
|
+
|
|
69
74
|
interface TemplateTag {
|
|
70
75
|
name: string
|
|
71
76
|
attrs: Record<string, string>
|
|
@@ -197,7 +202,24 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
if (tag.name === "embed") {
|
|
200
|
-
blocks.push({
|
|
205
|
+
blocks.push({
|
|
206
|
+
type: "embed",
|
|
207
|
+
url: tag.attrs.url ?? "",
|
|
208
|
+
...(tag.attrs.title ? { title: tag.attrs.title } : {}),
|
|
209
|
+
...(tag.attrs.poster ? { poster: tag.attrs.poster } : {}),
|
|
210
|
+
...(booleanAttr(tag.attrs, "autoplay") === undefined
|
|
211
|
+
? {}
|
|
212
|
+
: { autoplay: booleanAttr(tag.attrs, "autoplay") }),
|
|
213
|
+
...(booleanAttr(tag.attrs, "loop") === undefined
|
|
214
|
+
? {}
|
|
215
|
+
: { loop: booleanAttr(tag.attrs, "loop") }),
|
|
216
|
+
...(booleanAttr(tag.attrs, "muted") === undefined
|
|
217
|
+
? {}
|
|
218
|
+
: { muted: booleanAttr(tag.attrs, "muted") }),
|
|
219
|
+
...(booleanAttr(tag.attrs, "controls") === undefined
|
|
220
|
+
? {}
|
|
221
|
+
: { controls: booleanAttr(tag.attrs, "controls") }),
|
|
222
|
+
})
|
|
201
223
|
i++
|
|
202
224
|
// tolerate optional {% endembed %}
|
|
203
225
|
if (i < lines.length && templateTag(lines[i])?.name === "endembed") i++
|
package/src/gitbook/serialize.ts
CHANGED
|
@@ -60,8 +60,18 @@ function serializeBlock(b: Block): string {
|
|
|
60
60
|
})
|
|
61
61
|
.join("\n\n")}\n{% endstepper %}`
|
|
62
62
|
|
|
63
|
-
case "embed":
|
|
64
|
-
|
|
63
|
+
case "embed": {
|
|
64
|
+
const attrs = [
|
|
65
|
+
` url="${b.url}"`,
|
|
66
|
+
b.title ? ` title="${b.title}"` : "",
|
|
67
|
+
b.poster ? ` poster="${b.poster}"` : "",
|
|
68
|
+
b.autoplay === undefined ? "" : ` autoplay="${b.autoplay}"`,
|
|
69
|
+
b.loop === undefined ? "" : ` loop="${b.loop}"`,
|
|
70
|
+
b.muted === undefined ? "" : ` muted="${b.muted}"`,
|
|
71
|
+
b.controls === undefined ? "" : ` controls="${b.controls}"`,
|
|
72
|
+
].join("")
|
|
73
|
+
return `{% embed${attrs} %}`
|
|
74
|
+
}
|
|
65
75
|
|
|
66
76
|
case "content-ref":
|
|
67
77
|
return `{% content-ref url="${b.url}" %}\n${serializeInline(b.children)}\n{% endcontent-ref %}`
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type {
|
|
|
12
12
|
export { isReplayQaUrl, normalizeReplayEmbedUrl } from "./replay"
|
|
13
13
|
export { VideoEmbed } from "./video"
|
|
14
14
|
export type { VideoEmbedProps } from "./video"
|
|
15
|
+
export { VizEmbed } from "./viz"
|
|
16
|
+
export type { VizEmbedProps } from "./viz"
|
|
15
17
|
export { OpenApiOperation } from "./openapi/OpenApiOperation"
|
|
16
18
|
export {
|
|
17
19
|
ReactCodePreview,
|
package/src/styles.css
CHANGED
|
@@ -122,6 +122,22 @@
|
|
|
122
122
|
margin-top: 8px;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
.docstream-viz {
|
|
126
|
+
margin: 1.5rem 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.docstream-viz__player {
|
|
130
|
+
overflow: hidden;
|
|
131
|
+
border: 1px solid var(--gb-border);
|
|
132
|
+
border-radius: 12px;
|
|
133
|
+
background: var(--gb-muted);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.docstream-viz figcaption {
|
|
137
|
+
margin-top: 8px;
|
|
138
|
+
color: var(--gb-muted-foreground);
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
.docs-hint {
|
|
126
142
|
display: flex;
|
|
127
143
|
gap: 12px;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PlayerAudio, VideoScene } from "@brett_lamy/viz-engine"
|
|
2
|
+
import { VizPlayer } from "@brett_lamy/viz-engine"
|
|
3
|
+
|
|
4
|
+
export interface VizEmbedProps {
|
|
5
|
+
scene: VideoScene
|
|
6
|
+
audio?: PlayerAudio
|
|
7
|
+
title?: string
|
|
8
|
+
className?: string
|
|
9
|
+
autoplay?: boolean
|
|
10
|
+
loop?: boolean
|
|
11
|
+
showCaptions?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Mount a deterministic VizEngine scene inside a Docstream document. This is
|
|
16
|
+
* intentionally a component API rather than a markdown escape hatch: callers
|
|
17
|
+
* choose the scene and its evidence, while Docstream supplies document chrome
|
|
18
|
+
* and accessible figure semantics.
|
|
19
|
+
*/
|
|
20
|
+
export function VizEmbed({
|
|
21
|
+
scene,
|
|
22
|
+
audio,
|
|
23
|
+
title,
|
|
24
|
+
className,
|
|
25
|
+
autoplay = true,
|
|
26
|
+
loop = true,
|
|
27
|
+
showCaptions = false,
|
|
28
|
+
}: VizEmbedProps) {
|
|
29
|
+
return (
|
|
30
|
+
<figure className={`docstream-viz ${className ?? ""}`.trim()}>
|
|
31
|
+
<VizPlayer
|
|
32
|
+
scene={scene}
|
|
33
|
+
{...(audio === undefined ? {} : { audio })}
|
|
34
|
+
autoplay={autoplay}
|
|
35
|
+
loop={loop}
|
|
36
|
+
showCaptions={showCaptions}
|
|
37
|
+
className="docstream-viz__player"
|
|
38
|
+
/>
|
|
39
|
+
{title && <figcaption>{title}</figcaption>}
|
|
40
|
+
</figure>
|
|
41
|
+
)
|
|
42
|
+
}
|
package/src/viz/index.ts
ADDED