@brett_lamy/docstream 0.5.1 → 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/docs/reference.tsx +85 -14
- package/src/gitbook/parse.ts +9 -6
- package/src/gitbook/serialize.ts +8 -12
- package/src/index.ts +1 -1
- package/src/streamdown.tsx +1 -1
- package/src/styles.css +183 -8
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/docs/reference.tsx
CHANGED
|
@@ -1,17 +1,99 @@
|
|
|
1
|
-
import { createContext, useContext, useState, type ReactNode } from "react"
|
|
2
|
-
import { AtSign, Globe, Hash } from "lucide-react"
|
|
1
|
+
import { createContext, useContext, useId, useState, type ReactNode } from "react"
|
|
2
|
+
import { ArrowUpRight, AtSign, Globe, Hash } from "lucide-react"
|
|
3
3
|
|
|
4
4
|
import type { CitationDef, ReferenceNode } from "../gitbook/ast"
|
|
5
5
|
|
|
6
|
+
export interface CitationPreview {
|
|
7
|
+
title?: string
|
|
8
|
+
description?: string
|
|
9
|
+
image?: string
|
|
10
|
+
siteName?: string
|
|
11
|
+
favicon?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type CitationPreviewResolver = (citation: ReferenceNode) => CitationPreview | null | Promise<CitationPreview | null>
|
|
15
|
+
|
|
6
16
|
export interface DocstreamRefContextValue {
|
|
7
17
|
/** Called when a mention/tag chip (or a citation, if it has no url) is clicked. */
|
|
8
18
|
onReferenceClick?: (ref: ReferenceNode) => void
|
|
9
19
|
/** Full override for chip rendering; return null to fall back to the default. */
|
|
10
20
|
renderReference?: (ref: ReferenceNode) => ReactNode | null
|
|
21
|
+
/** Resolves Open Graph-style metadata when a linked citation is hovered or focused. */
|
|
22
|
+
resolveCitationPreview?: CitationPreviewResolver
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
export const DocstreamRefContext = createContext<DocstreamRefContextValue>({})
|
|
14
26
|
|
|
27
|
+
const previewCaches = new WeakMap<CitationPreviewResolver, Map<string, Promise<CitationPreview | null>>>()
|
|
28
|
+
|
|
29
|
+
function resolvePreview(resolver: CitationPreviewResolver, citation: ReferenceNode) {
|
|
30
|
+
let cache = previewCaches.get(resolver)
|
|
31
|
+
if (!cache) {
|
|
32
|
+
cache = new Map()
|
|
33
|
+
previewCaches.set(resolver, cache)
|
|
34
|
+
}
|
|
35
|
+
const key = citation.url ?? citation.id
|
|
36
|
+
let request = cache.get(key)
|
|
37
|
+
if (!request) {
|
|
38
|
+
request = Promise.resolve(resolver(citation)).catch(() => null)
|
|
39
|
+
cache.set(key, request)
|
|
40
|
+
}
|
|
41
|
+
return request
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function CitationHoverCard({ node }: { node: ReferenceNode }) {
|
|
45
|
+
const { onReferenceClick, resolveCitationPreview } = useContext(DocstreamRefContext)
|
|
46
|
+
const [preview, setPreview] = useState<CitationPreview | null>(null)
|
|
47
|
+
const [imageFailed, setImageFailed] = useState(false)
|
|
48
|
+
const [requested, setRequested] = useState(false)
|
|
49
|
+
const cardId = useId()
|
|
50
|
+
const host = node.url ? hostnameOf(node.url) : null
|
|
51
|
+
|
|
52
|
+
const requestPreview = () => {
|
|
53
|
+
if (!resolveCitationPreview || requested) return
|
|
54
|
+
setRequested(true)
|
|
55
|
+
void resolvePreview(resolveCitationPreview, node).then((value) => setPreview(value))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const title = preview?.title ?? node.label ?? host ?? node.url
|
|
59
|
+
const siteName = preview?.siteName ?? host
|
|
60
|
+
const favicon = preview?.favicon ?? (host ? `https://www.google.com/s2/favicons?sz=32&domain=${host}` : undefined)
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<span className="docs-cite-wrap" onMouseEnter={requestPreview} onFocus={requestPreview}>
|
|
64
|
+
<a
|
|
65
|
+
className="docs-cite"
|
|
66
|
+
href={node.url}
|
|
67
|
+
target="_blank"
|
|
68
|
+
rel="noreferrer"
|
|
69
|
+
aria-describedby={cardId}
|
|
70
|
+
onClick={() => onReferenceClick?.(node)}
|
|
71
|
+
>
|
|
72
|
+
{node.id}
|
|
73
|
+
</a>
|
|
74
|
+
<span className="docs-cite-card" id={cardId} role="tooltip">
|
|
75
|
+
{preview?.image && !imageFailed ? (
|
|
76
|
+
<span className="docs-cite-card-image-wrap">
|
|
77
|
+
<img className="docs-cite-card-image" src={preview.image} alt="" onError={() => setImageFailed(true)} />
|
|
78
|
+
</span>
|
|
79
|
+
) : null}
|
|
80
|
+
<span className="docs-cite-card-body">
|
|
81
|
+
<span className="docs-cite-card-source">
|
|
82
|
+
{favicon ? <img className="docs-cite-card-favicon" src={favicon} alt="" /> : <Globe className="docs-cite-card-favicon" />}
|
|
83
|
+
<span>{siteName}</span>
|
|
84
|
+
</span>
|
|
85
|
+
<span className="docs-cite-card-title">{title}</span>
|
|
86
|
+
{preview?.description ? <span className="docs-cite-card-description">{preview.description}</span> : null}
|
|
87
|
+
<span className="docs-cite-card-url">
|
|
88
|
+
{node.url}
|
|
89
|
+
<ArrowUpRight aria-hidden="true" />
|
|
90
|
+
</span>
|
|
91
|
+
</span>
|
|
92
|
+
</span>
|
|
93
|
+
</span>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
15
97
|
export function InlineReference({ node }: { node: ReferenceNode }) {
|
|
16
98
|
const { onReferenceClick, renderReference } = useContext(DocstreamRefContext)
|
|
17
99
|
const custom = renderReference?.(node)
|
|
@@ -20,18 +102,7 @@ export function InlineReference({ node }: { node: ReferenceNode }) {
|
|
|
20
102
|
if (node.kind === "citation") {
|
|
21
103
|
// Citations default to linking their source; hosts can still observe clicks.
|
|
22
104
|
if (node.url) {
|
|
23
|
-
return
|
|
24
|
-
<a
|
|
25
|
-
className="docs-cite"
|
|
26
|
-
href={node.url}
|
|
27
|
-
target="_blank"
|
|
28
|
-
rel="noreferrer"
|
|
29
|
-
title={node.label ?? node.url}
|
|
30
|
-
onClick={() => onReferenceClick?.(node)}
|
|
31
|
-
>
|
|
32
|
-
{node.id}
|
|
33
|
-
</a>
|
|
34
|
-
)
|
|
105
|
+
return <CitationHoverCard node={node} />
|
|
35
106
|
}
|
|
36
107
|
// Definition missing (not yet streamed, or standalone parseInline call).
|
|
37
108
|
return (
|
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/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { PlaygroundStreamdownProps as GitbookStreamdownProps } from "./play
|
|
|
3
3
|
export { DocsRenderer, MarkdownContent } from "./docs/DocsRenderer"
|
|
4
4
|
export type { LivePreviewProps, LivePreviewRenderer, SourceReferenceRenderer } from "./docs/DocsRenderer"
|
|
5
5
|
export { CitationSources, DocstreamRefContext, InlineReference } from "./docs/reference"
|
|
6
|
-
export type { DocstreamRefContextValue } from "./docs/reference"
|
|
6
|
+
export type { CitationPreview, CitationPreviewResolver, DocstreamRefContextValue } from "./docs/reference"
|
|
7
7
|
export { ReplayEmbed, ReplayPreview } from "./replay"
|
|
8
8
|
export type {
|
|
9
9
|
ReplayEventsSource,
|
package/src/streamdown.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { parseMarkdown, trimPartialInlineToken } from "./gitbook/parse"
|
|
|
5
5
|
// Re-exported so markdown-only consumers of ./streamdown can wire chip
|
|
6
6
|
// click handling without importing the heavy root entry.
|
|
7
7
|
export { CitationSources, DocstreamRefContext, InlineReference } from "./docs/reference"
|
|
8
|
-
export type { DocstreamRefContextValue } from "./docs/reference"
|
|
8
|
+
export type { CitationPreview, CitationPreviewResolver, DocstreamRefContextValue } from "./docs/reference"
|
|
9
9
|
|
|
10
10
|
export interface GitbookStreamdownProps {
|
|
11
11
|
children?: string
|
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 {
|
|
@@ -996,6 +1050,12 @@
|
|
|
996
1050
|
color: var(--gb-accent-foreground);
|
|
997
1051
|
}
|
|
998
1052
|
|
|
1053
|
+
.docs-cite-wrap {
|
|
1054
|
+
position: relative;
|
|
1055
|
+
display: inline-block;
|
|
1056
|
+
vertical-align: super;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
999
1059
|
.docs-cite {
|
|
1000
1060
|
display: inline-block;
|
|
1001
1061
|
padding: 1px 5px;
|
|
@@ -1005,7 +1065,7 @@
|
|
|
1005
1065
|
font-size: 10px;
|
|
1006
1066
|
font-weight: 700;
|
|
1007
1067
|
line-height: 1.4;
|
|
1008
|
-
vertical-align:
|
|
1068
|
+
vertical-align: baseline;
|
|
1009
1069
|
text-decoration: none !important;
|
|
1010
1070
|
cursor: pointer;
|
|
1011
1071
|
}
|
|
@@ -1014,6 +1074,121 @@
|
|
|
1014
1074
|
background: rgba(59, 130, 246, 0.22);
|
|
1015
1075
|
}
|
|
1016
1076
|
|
|
1077
|
+
.docs-cite:focus-visible {
|
|
1078
|
+
outline: 2px solid var(--gb-info);
|
|
1079
|
+
outline-offset: 2px;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.docs-cite-card {
|
|
1083
|
+
position: absolute;
|
|
1084
|
+
top: calc(100% + 10px);
|
|
1085
|
+
left: 50%;
|
|
1086
|
+
z-index: 50;
|
|
1087
|
+
display: block;
|
|
1088
|
+
width: min(320px, calc(100vw - 32px));
|
|
1089
|
+
overflow: hidden;
|
|
1090
|
+
border: 1px solid var(--gb-border);
|
|
1091
|
+
border-radius: 12px;
|
|
1092
|
+
color: var(--gb-panel-foreground);
|
|
1093
|
+
background: var(--gb-panel);
|
|
1094
|
+
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.16), 0 2px 8px rgba(15, 23, 42, 0.08);
|
|
1095
|
+
opacity: 0;
|
|
1096
|
+
visibility: hidden;
|
|
1097
|
+
transform: translate(-50%, -4px);
|
|
1098
|
+
transition: opacity 120ms ease, transform 120ms ease, visibility 120ms;
|
|
1099
|
+
pointer-events: none;
|
|
1100
|
+
text-align: left;
|
|
1101
|
+
white-space: normal;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
.docs-cite-wrap:hover .docs-cite-card,
|
|
1105
|
+
.docs-cite-wrap:focus-within .docs-cite-card {
|
|
1106
|
+
opacity: 1;
|
|
1107
|
+
visibility: visible;
|
|
1108
|
+
transform: translate(-50%, 0);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
.docs-cite-card-image-wrap {
|
|
1112
|
+
display: block;
|
|
1113
|
+
aspect-ratio: 1.91 / 1;
|
|
1114
|
+
overflow: hidden;
|
|
1115
|
+
border-bottom: 1px solid var(--gb-border);
|
|
1116
|
+
background: var(--gb-muted);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.docs-cite-card-image {
|
|
1120
|
+
width: 100%;
|
|
1121
|
+
height: 100%;
|
|
1122
|
+
object-fit: cover;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
.docs-cite-card-body {
|
|
1126
|
+
display: flex;
|
|
1127
|
+
flex-direction: column;
|
|
1128
|
+
gap: 6px;
|
|
1129
|
+
padding: 12px 14px 13px;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.docs-cite-card-source,
|
|
1133
|
+
.docs-cite-card-url {
|
|
1134
|
+
display: flex;
|
|
1135
|
+
align-items: center;
|
|
1136
|
+
gap: 6px;
|
|
1137
|
+
min-width: 0;
|
|
1138
|
+
color: var(--gb-muted-foreground);
|
|
1139
|
+
font-size: 11px;
|
|
1140
|
+
font-weight: 500;
|
|
1141
|
+
line-height: 1.35;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
.docs-cite-card-favicon {
|
|
1145
|
+
width: 14px;
|
|
1146
|
+
height: 14px;
|
|
1147
|
+
flex: 0 0 auto;
|
|
1148
|
+
border-radius: 3px;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.docs-cite-card-title {
|
|
1152
|
+
display: -webkit-box;
|
|
1153
|
+
overflow: hidden;
|
|
1154
|
+
color: var(--gb-panel-foreground);
|
|
1155
|
+
font-size: 14px;
|
|
1156
|
+
font-weight: 650;
|
|
1157
|
+
line-height: 1.35;
|
|
1158
|
+
-webkit-box-orient: vertical;
|
|
1159
|
+
-webkit-line-clamp: 2;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.docs-cite-card-description {
|
|
1163
|
+
display: -webkit-box;
|
|
1164
|
+
overflow: hidden;
|
|
1165
|
+
color: var(--gb-muted-foreground);
|
|
1166
|
+
font-size: 12px;
|
|
1167
|
+
font-weight: 400;
|
|
1168
|
+
line-height: 1.45;
|
|
1169
|
+
-webkit-box-orient: vertical;
|
|
1170
|
+
-webkit-line-clamp: 3;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.docs-cite-card-url {
|
|
1174
|
+
margin-top: 2px;
|
|
1175
|
+
overflow: hidden;
|
|
1176
|
+
text-overflow: ellipsis;
|
|
1177
|
+
white-space: nowrap;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.docs-cite-card-url svg {
|
|
1181
|
+
width: 12px;
|
|
1182
|
+
height: 12px;
|
|
1183
|
+
flex: 0 0 auto;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1187
|
+
.docs-cite-card {
|
|
1188
|
+
transition: none;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1017
1192
|
.docs-sources {
|
|
1018
1193
|
display: flex;
|
|
1019
1194
|
flex-wrap: wrap;
|