@brett_lamy/docstream 0.5.2 → 0.5.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/package.json +2 -1
- package/src/docs/DocsRenderer.tsx +25 -12
- package/src/gitbook/parse.ts +9 -6
- package/src/gitbook/serialize.ts +8 -12
- package/src/styles.css +61 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brett_lamy/docstream",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "GitBook-aware readonly markdown and AI stream renderer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@brett_lamy/viz-engine": ">=0.2.0",
|
|
73
|
+
"framer-motion": "^13.1.1",
|
|
73
74
|
"hast-util-to-html": "^9.0.5",
|
|
74
75
|
"lowlight": "^3.3.0",
|
|
75
76
|
"lucide-react": "^1.17.0",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState, type ReactNode } from "react"
|
|
2
|
+
import { LayoutGroup, motion } from "framer-motion"
|
|
2
3
|
import {
|
|
3
4
|
AlertTriangle,
|
|
4
5
|
CheckCircle2,
|
|
@@ -92,18 +93,30 @@ function Tabs({ block, liveRenderer, sourceRenderer }: { block: Extract<Block, {
|
|
|
92
93
|
const [active, setActive] = useState(0)
|
|
93
94
|
return (
|
|
94
95
|
<div className="docs-tabs">
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
<LayoutGroup>
|
|
97
|
+
<div className="docs-tabs-header" role="tablist" aria-label="Content tabs">
|
|
98
|
+
{block.tabs.map((t, i) => (
|
|
99
|
+
<button
|
|
100
|
+
key={i}
|
|
101
|
+
type="button"
|
|
102
|
+
role="tab"
|
|
103
|
+
aria-selected={i === active}
|
|
104
|
+
className={i === active ? "docs-tab-active" : ""}
|
|
105
|
+
onClick={() => setActive(i)}
|
|
106
|
+
>
|
|
107
|
+
{i === active ? (
|
|
108
|
+
<motion.span
|
|
109
|
+
className="docs-tab-indicator"
|
|
110
|
+
layoutId="docs-tab-indicator"
|
|
111
|
+
transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
|
|
112
|
+
/>
|
|
113
|
+
) : null}
|
|
114
|
+
<span className="docs-tab-label">{t.title}</span>
|
|
115
|
+
</button>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</LayoutGroup>
|
|
119
|
+
<div className="docs-tabs-body" role="tabpanel">
|
|
107
120
|
<Blocks blocks={block.tabs[active]?.children ?? []} liveRenderer={liveRenderer} sourceRenderer={sourceRenderer} />
|
|
108
121
|
</div>
|
|
109
122
|
</div>
|
package/src/gitbook/parse.ts
CHANGED
|
@@ -225,7 +225,7 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
225
225
|
const code = inner.find((b) => b.type === "code")
|
|
226
226
|
if (code && code.type === "code") {
|
|
227
227
|
code.title = tag.attrs.title ?? null
|
|
228
|
-
code.lineNumbers = tag.attrs
|
|
228
|
+
code.lineNumbers = booleanAttr(tag.attrs, "lineNumbers") ?? booleanAttr(tag.attrs, "showLineNumbers") ?? true
|
|
229
229
|
code.live = tag.attrs.live === "true"
|
|
230
230
|
code.entry = tag.attrs.entry ?? null
|
|
231
231
|
blocks.push(code)
|
|
@@ -428,13 +428,14 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
// fenced code (``` or ~~~)
|
|
431
|
-
const fence = trimmed.match(/^(`{3,}|~{3,})(
|
|
431
|
+
const fence = trimmed.match(/^(`{3,}|~{3,})([^\s`]*)?(?:\s+(.*?))?\s*$/)
|
|
432
432
|
if (fence) {
|
|
433
433
|
flushParagraph()
|
|
434
434
|
const marker = fence[1][0]
|
|
435
|
+
const attrs = parseAttrs(fence[3])
|
|
435
436
|
const code: string[] = []
|
|
436
437
|
i++
|
|
437
|
-
while (i < lines.length && !lines[i].trim().startsWith(
|
|
438
|
+
while (i < lines.length && !lines[i].trim().startsWith(fence[1])) {
|
|
438
439
|
code.push(lines[i])
|
|
439
440
|
i++
|
|
440
441
|
}
|
|
@@ -442,9 +443,11 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
442
443
|
blocks.push({
|
|
443
444
|
type: "code",
|
|
444
445
|
language: fence[2] || null,
|
|
445
|
-
title: null,
|
|
446
|
-
lineNumbers:
|
|
446
|
+
title: attrs.title ?? null,
|
|
447
|
+
lineNumbers: booleanAttr(attrs, "lineNumbers") ?? booleanAttr(attrs, "showLineNumbers") ?? true,
|
|
447
448
|
code: code.join("\n"),
|
|
449
|
+
...(booleanAttr(attrs, "live") === undefined ? {} : { live: booleanAttr(attrs, "live") }),
|
|
450
|
+
...(attrs.entry ? { entry: attrs.entry } : {}),
|
|
448
451
|
})
|
|
449
452
|
continue
|
|
450
453
|
}
|
|
@@ -459,7 +462,7 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
459
462
|
code.push(lines[i].slice(4))
|
|
460
463
|
i++
|
|
461
464
|
}
|
|
462
|
-
blocks.push({ type: "code", language: null, title: null, lineNumbers:
|
|
465
|
+
blocks.push({ type: "code", language: null, title: null, lineNumbers: true, code: code.join("\n") })
|
|
463
466
|
continue
|
|
464
467
|
}
|
|
465
468
|
|
package/src/gitbook/serialize.ts
CHANGED
|
@@ -34,18 +34,14 @@ function serializeBlock(b: Block): string {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
case "code": {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
].join("")
|
|
46
|
-
return `{% code${attrs} %}\n${body}\n{% endcode %}`
|
|
47
|
-
}
|
|
48
|
-
return body
|
|
37
|
+
const attrs = [
|
|
38
|
+
b.title ? `title="${b.title}"` : "",
|
|
39
|
+
!b.lineNumbers ? `lineNumbers="false"` : "",
|
|
40
|
+
b.live ? `live="true"` : "",
|
|
41
|
+
b.entry ? `entry="${b.entry}"` : "",
|
|
42
|
+
].filter(Boolean)
|
|
43
|
+
const info = [b.language ?? "", ...attrs].filter(Boolean).join(" ")
|
|
44
|
+
return `\`\`\`${info}\n${b.code}\n\`\`\``
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
case "hint":
|
package/src/styles.css
CHANGED
|
@@ -176,6 +176,7 @@
|
|
|
176
176
|
|
|
177
177
|
.docs-hint {
|
|
178
178
|
display: flex;
|
|
179
|
+
align-items: center;
|
|
179
180
|
gap: 12px;
|
|
180
181
|
padding: 14px 16px;
|
|
181
182
|
border-radius: 12px;
|
|
@@ -186,7 +187,6 @@
|
|
|
186
187
|
width: 18px;
|
|
187
188
|
height: 18px;
|
|
188
189
|
flex-shrink: 0;
|
|
189
|
-
margin-top: 3px;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
.docs-hint > div {
|
|
@@ -194,6 +194,14 @@
|
|
|
194
194
|
min-width: 0;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
.docs-hint > div > :first-child {
|
|
198
|
+
margin-top: 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.docs-hint > div > :last-child {
|
|
202
|
+
margin-bottom: 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
197
205
|
.docs-hint-info {
|
|
198
206
|
border-color: var(--gb-info-border);
|
|
199
207
|
background: var(--gb-info-bg);
|
|
@@ -234,31 +242,77 @@
|
|
|
234
242
|
border: 1px solid var(--gb-border);
|
|
235
243
|
border-radius: 12px;
|
|
236
244
|
overflow: hidden;
|
|
245
|
+
background: var(--gb-panel);
|
|
237
246
|
}
|
|
238
247
|
|
|
239
248
|
.docs-tabs-header {
|
|
240
249
|
display: flex;
|
|
250
|
+
width: fit-content;
|
|
251
|
+
max-width: calc(100% - 24px);
|
|
241
252
|
gap: 2px;
|
|
242
|
-
|
|
253
|
+
margin: 10px auto 0;
|
|
254
|
+
padding: 4px;
|
|
255
|
+
overflow-x: auto;
|
|
256
|
+
border: 1px solid var(--gb-border);
|
|
257
|
+
border-radius: 999px;
|
|
243
258
|
background: var(--gb-muted);
|
|
244
|
-
border-bottom: 1px solid var(--gb-border);
|
|
245
259
|
}
|
|
246
260
|
|
|
247
261
|
.docs-tabs-header button {
|
|
262
|
+
position: relative;
|
|
263
|
+
z-index: 0;
|
|
264
|
+
flex: 0 0 auto;
|
|
265
|
+
border: 0;
|
|
266
|
+
border-radius: 999px;
|
|
248
267
|
padding: 7px 14px;
|
|
268
|
+
background: transparent;
|
|
269
|
+
font: inherit;
|
|
249
270
|
font-size: 13.5px;
|
|
250
|
-
|
|
271
|
+
line-height: 1.25;
|
|
251
272
|
color: var(--gb-muted-foreground);
|
|
273
|
+
cursor: pointer;
|
|
274
|
+
-webkit-tap-highlight-color: transparent;
|
|
275
|
+
transition: color 160ms ease;
|
|
252
276
|
}
|
|
253
277
|
|
|
254
278
|
.docs-tabs-header .docs-tab-active {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
279
|
+
color: var(--gb-panel-foreground);
|
|
280
|
+
font-weight: 600;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.docs-tabs-header button:hover:not(.docs-tab-active) {
|
|
284
|
+
color: var(--gb-panel-foreground);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.docs-tabs-header button:focus-visible {
|
|
288
|
+
outline: 2px solid var(--gb-primary);
|
|
289
|
+
outline-offset: 2px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.docs-tab-indicator {
|
|
293
|
+
position: absolute;
|
|
294
|
+
inset: 0;
|
|
295
|
+
z-index: -1;
|
|
296
|
+
border: 1px solid var(--gb-border);
|
|
297
|
+
border-radius: inherit;
|
|
298
|
+
background: var(--gb-panel);
|
|
299
|
+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08), 0 1px 6px rgba(15, 23, 42, 0.06);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.docs-tab-label {
|
|
303
|
+
position: relative;
|
|
258
304
|
}
|
|
259
305
|
|
|
260
306
|
.docs-tabs-body {
|
|
307
|
+
margin-top: 10px;
|
|
261
308
|
padding: 14px 16px;
|
|
309
|
+
border-top: 1px solid var(--gb-border);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@media (prefers-reduced-motion: reduce) {
|
|
313
|
+
.docs-tabs-header button {
|
|
314
|
+
transition: none;
|
|
315
|
+
}
|
|
262
316
|
}
|
|
263
317
|
|
|
264
318
|
.docs-expandable {
|