@brainfish-ai/devdoc 0.1.25 → 0.1.27
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 +2 -2
- package/dist/cli/commands/create.d.ts +3 -0
- package/dist/cli/commands/create.js +180 -15
- package/dist/cli/commands/dev.js +41 -77
- package/package.json +6 -6
- package/renderer/app/api/docs/route.ts +6 -2
- package/renderer/app/icon.svg +4 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +17 -2
- package/renderer/components/docs-viewer/content/not-found-page.tsx +330 -0
- package/renderer/components/docs-viewer/index.tsx +56 -4
- package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +20 -0
- package/renderer/app/favicon.ico +0 -0
- package/templates/basic/README.md +0 -139
- package/templates/basic/assets/favicon.svg +0 -4
- package/templates/basic/assets/logo.svg +0 -9
- package/templates/basic/docs.json +0 -47
- package/templates/basic/guides/configuration.mdx +0 -149
- package/templates/basic/guides/overview.mdx +0 -96
- package/templates/basic/index.mdx +0 -39
- package/templates/basic/package.json +0 -14
- package/templates/basic/quickstart.mdx +0 -92
- package/templates/basic/vercel.json +0 -6
- package/templates/graphql/README.md +0 -139
- package/templates/graphql/api-reference/schema.graphql +0 -305
- package/templates/graphql/assets/favicon.svg +0 -4
- package/templates/graphql/assets/logo.svg +0 -9
- package/templates/graphql/docs.json +0 -54
- package/templates/graphql/guides/configuration.mdx +0 -149
- package/templates/graphql/guides/overview.mdx +0 -96
- package/templates/graphql/index.mdx +0 -39
- package/templates/graphql/package.json +0 -14
- package/templates/graphql/quickstart.mdx +0 -92
- package/templates/graphql/vercel.json +0 -6
- package/templates/openapi/README.md +0 -139
- package/templates/openapi/api-reference/openapi.json +0 -419
- package/templates/openapi/assets/favicon.svg +0 -4
- package/templates/openapi/assets/logo.svg +0 -9
- package/templates/openapi/docs.json +0 -61
- package/templates/openapi/guides/configuration.mdx +0 -149
- package/templates/openapi/guides/overview.mdx +0 -96
- package/templates/openapi/index.mdx +0 -39
- package/templates/openapi/package.json +0 -14
- package/templates/openapi/quickstart.mdx +0 -92
- package/templates/openapi/vercel.json +0 -6
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback, useRef } from 'react'
|
|
4
|
+
import { FileX, House, ArrowLeft, MagnifyingGlass, Spinner } from '@phosphor-icons/react'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { useDocsNavigation } from '@/lib/docs-navigation-context'
|
|
7
|
+
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
|
|
8
|
+
import { useCodeCopy } from '@/hooks/use-code-copy'
|
|
9
|
+
|
|
10
|
+
// Import MDX components for custom 404 pages
|
|
11
|
+
import {
|
|
12
|
+
Note,
|
|
13
|
+
Warning,
|
|
14
|
+
Info,
|
|
15
|
+
Tip,
|
|
16
|
+
Check,
|
|
17
|
+
Error as ErrorCallout,
|
|
18
|
+
Callout,
|
|
19
|
+
Card,
|
|
20
|
+
CardGroup,
|
|
21
|
+
Accordion,
|
|
22
|
+
AccordionGroup,
|
|
23
|
+
Steps,
|
|
24
|
+
Step,
|
|
25
|
+
Tabs,
|
|
26
|
+
Tab,
|
|
27
|
+
CodeGroup,
|
|
28
|
+
Frame,
|
|
29
|
+
Columns,
|
|
30
|
+
Snippet,
|
|
31
|
+
Latex,
|
|
32
|
+
ParamField,
|
|
33
|
+
ResponseField,
|
|
34
|
+
Expandable,
|
|
35
|
+
Iframe,
|
|
36
|
+
Video,
|
|
37
|
+
Loom,
|
|
38
|
+
Image,
|
|
39
|
+
Screenshot,
|
|
40
|
+
Logo,
|
|
41
|
+
Icon,
|
|
42
|
+
Highlight,
|
|
43
|
+
Marker,
|
|
44
|
+
Underline,
|
|
45
|
+
Badge,
|
|
46
|
+
Mermaid,
|
|
47
|
+
PDF,
|
|
48
|
+
Audio,
|
|
49
|
+
Download,
|
|
50
|
+
Hero,
|
|
51
|
+
Pre,
|
|
52
|
+
Tagline,
|
|
53
|
+
Headline,
|
|
54
|
+
Description,
|
|
55
|
+
CommandBox,
|
|
56
|
+
Section,
|
|
57
|
+
Center,
|
|
58
|
+
FeatureGrid,
|
|
59
|
+
FeatureItem,
|
|
60
|
+
ButtonLink,
|
|
61
|
+
Spacer,
|
|
62
|
+
Divider,
|
|
63
|
+
} from '../../docs/mdx/index'
|
|
64
|
+
|
|
65
|
+
// MDX components mapping for custom 404 pages
|
|
66
|
+
const mdxComponents = {
|
|
67
|
+
Note,
|
|
68
|
+
Warning,
|
|
69
|
+
Info,
|
|
70
|
+
Tip,
|
|
71
|
+
Check,
|
|
72
|
+
Error: ErrorCallout,
|
|
73
|
+
Callout,
|
|
74
|
+
Card,
|
|
75
|
+
CardGroup,
|
|
76
|
+
Accordion,
|
|
77
|
+
AccordionGroup,
|
|
78
|
+
Steps,
|
|
79
|
+
Step,
|
|
80
|
+
Tabs,
|
|
81
|
+
Tab,
|
|
82
|
+
CodeGroup,
|
|
83
|
+
Frame,
|
|
84
|
+
Columns,
|
|
85
|
+
Snippet,
|
|
86
|
+
Latex,
|
|
87
|
+
ParamField,
|
|
88
|
+
ResponseField,
|
|
89
|
+
Expandable,
|
|
90
|
+
Iframe,
|
|
91
|
+
Video,
|
|
92
|
+
Loom,
|
|
93
|
+
Image,
|
|
94
|
+
Screenshot,
|
|
95
|
+
Logo,
|
|
96
|
+
Icon,
|
|
97
|
+
Highlight,
|
|
98
|
+
Marker,
|
|
99
|
+
Underline,
|
|
100
|
+
Badge,
|
|
101
|
+
Mermaid,
|
|
102
|
+
PDF,
|
|
103
|
+
Audio,
|
|
104
|
+
Download,
|
|
105
|
+
Hero,
|
|
106
|
+
Pre,
|
|
107
|
+
Tagline,
|
|
108
|
+
Headline,
|
|
109
|
+
Description,
|
|
110
|
+
CommandBox,
|
|
111
|
+
Section,
|
|
112
|
+
Center,
|
|
113
|
+
FeatureGrid,
|
|
114
|
+
FeatureItem,
|
|
115
|
+
ButtonLink,
|
|
116
|
+
Spacer,
|
|
117
|
+
Divider,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface NotFoundPageProps {
|
|
121
|
+
slug?: string
|
|
122
|
+
onGoBack?: () => void
|
|
123
|
+
onSearch?: () => void
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface Custom404Data {
|
|
127
|
+
frontmatter: {
|
|
128
|
+
title?: string
|
|
129
|
+
description?: string
|
|
130
|
+
mode?: 'default' | 'wide' | 'custom'
|
|
131
|
+
hideHeader?: boolean
|
|
132
|
+
background?: string
|
|
133
|
+
}
|
|
134
|
+
mdxSource: MDXRemoteSerializeResult
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function NotFoundPage({ slug, onGoBack, onSearch }: NotFoundPageProps) {
|
|
138
|
+
const docsNav = useDocsNavigation()
|
|
139
|
+
const [customPage, setCustomPage] = useState<Custom404Data | null>(null)
|
|
140
|
+
const [loading, setLoading] = useState(true)
|
|
141
|
+
const contentRef = useRef<HTMLDivElement>(null)
|
|
142
|
+
|
|
143
|
+
// Add copy buttons to code blocks after content loads
|
|
144
|
+
useCodeCopy(contentRef, [customPage])
|
|
145
|
+
|
|
146
|
+
// Try to load custom 404.mdx page
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
async function loadCustom404() {
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch('/api/docs?slug=404&is404=true')
|
|
151
|
+
if (response.ok) {
|
|
152
|
+
const data = await response.json()
|
|
153
|
+
setCustomPage(data)
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// No custom 404 page, use default
|
|
157
|
+
} finally {
|
|
158
|
+
setLoading(false)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
loadCustom404()
|
|
162
|
+
}, [])
|
|
163
|
+
|
|
164
|
+
const handleGoHome = useCallback(() => {
|
|
165
|
+
// Navigate to the first page of the current tab or just switch to first tab
|
|
166
|
+
if (docsNav?.switchToTab) {
|
|
167
|
+
window.location.hash = ''
|
|
168
|
+
window.location.reload()
|
|
169
|
+
} else {
|
|
170
|
+
window.location.hash = ''
|
|
171
|
+
}
|
|
172
|
+
}, [docsNav])
|
|
173
|
+
|
|
174
|
+
const handleGoBack = useCallback(() => {
|
|
175
|
+
if (onGoBack) {
|
|
176
|
+
onGoBack()
|
|
177
|
+
} else {
|
|
178
|
+
window.history.back()
|
|
179
|
+
}
|
|
180
|
+
}, [onGoBack])
|
|
181
|
+
|
|
182
|
+
// Show loading state briefly while checking for custom 404
|
|
183
|
+
if (loading) {
|
|
184
|
+
return (
|
|
185
|
+
<div className="docs-page docs-page-not-found w-full min-h-[200px]">
|
|
186
|
+
<div className="flex items-center justify-center py-12">
|
|
187
|
+
<Spinner className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Render custom 404 page if available
|
|
194
|
+
if (customPage) {
|
|
195
|
+
const { mode = 'default', hideHeader, background } = customPage.frontmatter
|
|
196
|
+
const isCustomMode = mode === 'custom'
|
|
197
|
+
const showHeader = !hideHeader && !isCustomMode
|
|
198
|
+
|
|
199
|
+
// Custom mode: Full-width layout
|
|
200
|
+
if (isCustomMode) {
|
|
201
|
+
return (
|
|
202
|
+
<div
|
|
203
|
+
ref={contentRef}
|
|
204
|
+
className="docs-page docs-page-not-found docs-content w-full min-h-full"
|
|
205
|
+
style={{ background: background || 'var(--background)' }}
|
|
206
|
+
>
|
|
207
|
+
<div className="docs-custom-content [&>*]:w-full">
|
|
208
|
+
<MDXRemote {...customPage.mdxSource} components={mdxComponents} />
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Default mode with optional header
|
|
215
|
+
const containerClass = mode === 'wide' ? 'max-w-6xl' : 'max-w-4xl'
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div ref={contentRef} className={`docs-page docs-page-not-found docs-content ${containerClass} mx-auto px-4 py-6 sm:px-8 sm:py-8`}>
|
|
219
|
+
{showHeader && customPage.frontmatter.title && (
|
|
220
|
+
<div className="docs-page-header mb-6">
|
|
221
|
+
<h1 className="docs-content-title text-2xl sm:text-3xl font-bold mb-2 text-foreground">
|
|
222
|
+
{customPage.frontmatter.title}
|
|
223
|
+
</h1>
|
|
224
|
+
{customPage.frontmatter.description && (
|
|
225
|
+
<p className="docs-content-description text-lg text-muted-foreground">
|
|
226
|
+
{customPage.frontmatter.description}
|
|
227
|
+
</p>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
<div className="docs-prose prose prose-sm max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-strong:text-foreground">
|
|
232
|
+
<MDXRemote {...customPage.mdxSource} components={mdxComponents} />
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Always show navigation actions for custom 404 */}
|
|
236
|
+
<div className="mt-8 pt-6 border-t border-border">
|
|
237
|
+
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
|
238
|
+
<Button variant="outline" size="sm" onClick={handleGoBack} className="gap-2 min-w-[120px]">
|
|
239
|
+
<ArrowLeft className="w-4 h-4" />
|
|
240
|
+
Go back
|
|
241
|
+
</Button>
|
|
242
|
+
<Button variant="default" size="sm" onClick={handleGoHome} className="gap-2 min-w-[120px]">
|
|
243
|
+
<House className="w-4 h-4" />
|
|
244
|
+
Go home
|
|
245
|
+
</Button>
|
|
246
|
+
{onSearch && (
|
|
247
|
+
<Button variant="outline" size="sm" onClick={onSearch} className="gap-2 min-w-[120px]">
|
|
248
|
+
<MagnifyingGlass className="w-4 h-4" />
|
|
249
|
+
Search
|
|
250
|
+
</Button>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Default 404 page
|
|
259
|
+
return (
|
|
260
|
+
<div className="docs-page docs-page-not-found w-full min-h-[calc(100vh-200px)] flex items-center justify-center">
|
|
261
|
+
<div className="text-center px-4 py-12 max-w-md mx-auto">
|
|
262
|
+
{/* Icon */}
|
|
263
|
+
<div className="mb-6">
|
|
264
|
+
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-muted/50 text-muted-foreground">
|
|
265
|
+
<FileX className="w-10 h-10" weight="light" />
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
{/* Title */}
|
|
270
|
+
<h1 className="text-2xl font-semibold text-foreground mb-2">
|
|
271
|
+
Page not found
|
|
272
|
+
</h1>
|
|
273
|
+
|
|
274
|
+
{/* Description */}
|
|
275
|
+
<p className="text-muted-foreground mb-2">
|
|
276
|
+
The page you're looking for doesn't exist or has been moved.
|
|
277
|
+
</p>
|
|
278
|
+
|
|
279
|
+
{/* Show the slug that wasn't found */}
|
|
280
|
+
{slug && (
|
|
281
|
+
<p className="text-sm text-muted-foreground/70 mb-6 font-mono bg-muted/30 px-3 py-1.5 rounded-md inline-block">
|
|
282
|
+
/{slug}
|
|
283
|
+
</p>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
{!slug && <div className="mb-6" />}
|
|
287
|
+
|
|
288
|
+
{/* Actions */}
|
|
289
|
+
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
|
290
|
+
<Button
|
|
291
|
+
variant="outline"
|
|
292
|
+
size="sm"
|
|
293
|
+
onClick={handleGoBack}
|
|
294
|
+
className="gap-2 min-w-[120px]"
|
|
295
|
+
>
|
|
296
|
+
<ArrowLeft className="w-4 h-4" />
|
|
297
|
+
Go back
|
|
298
|
+
</Button>
|
|
299
|
+
|
|
300
|
+
<Button
|
|
301
|
+
variant="default"
|
|
302
|
+
size="sm"
|
|
303
|
+
onClick={handleGoHome}
|
|
304
|
+
className="gap-2 min-w-[120px]"
|
|
305
|
+
>
|
|
306
|
+
<House className="w-4 h-4" />
|
|
307
|
+
Go home
|
|
308
|
+
</Button>
|
|
309
|
+
|
|
310
|
+
{onSearch && (
|
|
311
|
+
<Button
|
|
312
|
+
variant="outline"
|
|
313
|
+
size="sm"
|
|
314
|
+
onClick={onSearch}
|
|
315
|
+
className="gap-2 min-w-[120px]"
|
|
316
|
+
>
|
|
317
|
+
<MagnifyingGlass className="w-4 h-4" />
|
|
318
|
+
Search
|
|
319
|
+
</Button>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* Help text */}
|
|
324
|
+
<p className="text-xs text-muted-foreground/60 mt-8">
|
|
325
|
+
If you believe this is an error, please check the URL or contact the documentation maintainer.
|
|
326
|
+
</p>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
)
|
|
330
|
+
}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useState, useCallback, useRef } from 'react'
|
|
|
4
4
|
import { Spinner } from '@phosphor-icons/react'
|
|
5
5
|
import type { BrainfishCollection, BrainfishRESTRequest, BrainfishDocGroup } from '@/lib/api-docs/types'
|
|
6
6
|
import { DocsSidebar } from './sidebar'
|
|
7
|
+
import { NotFoundPage } from './content/not-found-page'
|
|
7
8
|
import { ApiPlayground } from './playground'
|
|
8
9
|
import type { DebugContext } from './playground/response-viewer'
|
|
9
10
|
import { RightSidebar } from './sidebar/right-sidebar'
|
|
@@ -391,6 +392,7 @@ function DocsContent() {
|
|
|
391
392
|
const [isVersionLoading, setIsVersionLoading] = useState(false) // For version switch only
|
|
392
393
|
const [error, setError] = useState<string | null>(null)
|
|
393
394
|
const [showAuthModal, setShowAuthModal] = useState(false)
|
|
395
|
+
const [notFoundSlug, setNotFoundSlug] = useState<string | null>(null) // Track 404 for invalid URLs
|
|
394
396
|
|
|
395
397
|
// Prefill context for agent
|
|
396
398
|
const { setPrefill } = usePlaygroundPrefill()
|
|
@@ -481,13 +483,16 @@ function DocsContent() {
|
|
|
481
483
|
setSelectedRequest(request)
|
|
482
484
|
setSelectedDocSection(null)
|
|
483
485
|
setSelectedDocPage(null)
|
|
486
|
+
setNotFoundSlug(null)
|
|
484
487
|
} else {
|
|
485
|
-
// Endpoint not found -
|
|
488
|
+
// Endpoint not found - show 404 page
|
|
486
489
|
setSelectedRequest(null)
|
|
487
490
|
setSelectedDocSection(null)
|
|
488
491
|
setSelectedDocPage(null)
|
|
492
|
+
setNotFoundSlug(`endpoint/${actualId}`)
|
|
489
493
|
}
|
|
490
494
|
} else if (actualType === 'page' && actualId) {
|
|
495
|
+
setNotFoundSlug(null) // Clear not found state - DocPage handles its own 404
|
|
491
496
|
setSelectedDocPage(actualId)
|
|
492
497
|
setSelectedRequest(null)
|
|
493
498
|
setSelectedDocSection(null)
|
|
@@ -515,6 +520,7 @@ function DocsContent() {
|
|
|
515
520
|
setSelectedRequest(request)
|
|
516
521
|
setSelectedDocSection(null)
|
|
517
522
|
setSelectedDocPage(null)
|
|
523
|
+
setNotFoundSlug(null) // Clear 404 state when selecting an endpoint
|
|
518
524
|
updateUrlHash(`endpoint/${request.id}`)
|
|
519
525
|
// Reset tab navigation so the new endpoint can determine its default tab
|
|
520
526
|
resetNavigation()
|
|
@@ -527,6 +533,7 @@ function DocsContent() {
|
|
|
527
533
|
setSelectedDocSection(isIntro ? null : headingId)
|
|
528
534
|
setSelectedRequest(null)
|
|
529
535
|
setSelectedDocPage(null)
|
|
536
|
+
setNotFoundSlug(null) // Clear 404 state
|
|
530
537
|
|
|
531
538
|
updateUrlHash(isIntro ? '' : `doc/${headingId}`)
|
|
532
539
|
|
|
@@ -599,6 +606,7 @@ function DocsContent() {
|
|
|
599
606
|
setSelectedDocPage(pageSlug)
|
|
600
607
|
setSelectedRequest(null)
|
|
601
608
|
setSelectedDocSection(null)
|
|
609
|
+
setNotFoundSlug(null) // Clear 404 state
|
|
602
610
|
updateUrlHash(`page/${pageSlug}`, targetTab)
|
|
603
611
|
switchToDocs()
|
|
604
612
|
|
|
@@ -1162,6 +1170,7 @@ function DocsContent() {
|
|
|
1162
1170
|
selectedApiVersion={selectedApiVersion}
|
|
1163
1171
|
handleApiVersionChange={handleApiVersionChange}
|
|
1164
1172
|
isVersionLoading={isVersionLoading}
|
|
1173
|
+
notFoundSlug={notFoundSlug}
|
|
1165
1174
|
/>
|
|
1166
1175
|
</NavigationProvider>
|
|
1167
1176
|
)
|
|
@@ -1193,6 +1202,7 @@ interface DocsWithModeProps {
|
|
|
1193
1202
|
selectedApiVersion: string | null
|
|
1194
1203
|
handleApiVersionChange: (version: string) => void
|
|
1195
1204
|
isVersionLoading: boolean
|
|
1205
|
+
notFoundSlug: string | null
|
|
1196
1206
|
}
|
|
1197
1207
|
|
|
1198
1208
|
// Mode toggle tabs - switches between Docs, API Client, and Notes
|
|
@@ -1268,6 +1278,7 @@ function DocsWithMode({
|
|
|
1268
1278
|
selectedApiVersion,
|
|
1269
1279
|
handleApiVersionChange,
|
|
1270
1280
|
isVersionLoading,
|
|
1281
|
+
notFoundSlug,
|
|
1271
1282
|
}: DocsWithModeProps) {
|
|
1272
1283
|
const { mode, switchToDocs } = useModeContext()
|
|
1273
1284
|
const { setTheme } = useTheme()
|
|
@@ -1340,6 +1351,7 @@ function DocsWithMode({
|
|
|
1340
1351
|
if (!showGraphQL || !schemaPath) {
|
|
1341
1352
|
setGraphqlOperations([])
|
|
1342
1353
|
setGraphqlCollection(null)
|
|
1354
|
+
setSelectedGraphQLOperation(null) // Clear selection when leaving GraphQL tab
|
|
1343
1355
|
return
|
|
1344
1356
|
}
|
|
1345
1357
|
|
|
@@ -1364,6 +1376,44 @@ function DocsWithMode({
|
|
|
1364
1376
|
loadGraphQLSchema()
|
|
1365
1377
|
}, [showGraphQL, schemaPath, schemaEndpoint])
|
|
1366
1378
|
|
|
1379
|
+
// Auto-select GraphQL operation from URL hash or default to first operation
|
|
1380
|
+
useEffect(() => {
|
|
1381
|
+
if (!showGraphQL || graphqlOperations.length === 0) {
|
|
1382
|
+
return
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Check if there's a selection in the URL hash first
|
|
1386
|
+
const hash = window.location.hash.slice(1) // Remove #
|
|
1387
|
+
if (hash) {
|
|
1388
|
+
const parts = hash.split('/')
|
|
1389
|
+
const hashType = parts[1]
|
|
1390
|
+
const hashId = parts.slice(2).join('/')
|
|
1391
|
+
|
|
1392
|
+
if (hashType === 'endpoint' && hashId) {
|
|
1393
|
+
// Try to find and select the operation from the URL hash
|
|
1394
|
+
const operation = graphqlOperations.find(op => op.id === hashId)
|
|
1395
|
+
if (operation) {
|
|
1396
|
+
setSelectedGraphQLOperation(operation)
|
|
1397
|
+
return
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// If already have a selection, don't override it
|
|
1403
|
+
if (selectedGraphQLOperation) {
|
|
1404
|
+
return
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// Check if there are doc groups for this tab first
|
|
1408
|
+
const hasGroups = hasDocGroupsForTab(collection?.docGroups, activeTab)
|
|
1409
|
+
if (!hasGroups) {
|
|
1410
|
+
// No doc groups - auto-select first GraphQL operation
|
|
1411
|
+
const firstOperation = graphqlOperations[0]
|
|
1412
|
+
setSelectedGraphQLOperation(firstOperation)
|
|
1413
|
+
window.history.pushState(null, '', `#${activeTab}/endpoint/${firstOperation.id}`)
|
|
1414
|
+
}
|
|
1415
|
+
}, [showGraphQL, graphqlOperations, selectedGraphQLOperation, activeTab, collection?.docGroups])
|
|
1416
|
+
|
|
1367
1417
|
// Handle GraphQL operation selection from sidebar
|
|
1368
1418
|
const handleSelectGraphQLOperation = useCallback((request: BrainfishRESTRequest) => {
|
|
1369
1419
|
// Find the matching GraphQL operation
|
|
@@ -1500,10 +1550,10 @@ function DocsWithMode({
|
|
|
1500
1550
|
ref={contentRef}
|
|
1501
1551
|
className={cn(
|
|
1502
1552
|
"docs-content-area flex-1 bg-background min-w-0",
|
|
1503
|
-
(showChangelog || showGraphQL) ? "overflow-hidden flex flex-col" : "overflow-y-auto scroll-smooth"
|
|
1553
|
+
(showChangelog || (showGraphQL && selectedGraphQLOperation)) ? "overflow-hidden flex flex-col" : "overflow-y-auto scroll-smooth"
|
|
1504
1554
|
)}
|
|
1505
1555
|
>
|
|
1506
|
-
{showGraphQL ? (
|
|
1556
|
+
{showGraphQL && selectedGraphQLOperation ? (
|
|
1507
1557
|
<div className="flex-1 flex flex-col h-full">
|
|
1508
1558
|
<GraphQLPlayground
|
|
1509
1559
|
endpoint={activeGraphQLSchemas[0]?.endpoint || ''}
|
|
@@ -1523,7 +1573,9 @@ function DocsWithMode({
|
|
|
1523
1573
|
) : selectedRequest ? (
|
|
1524
1574
|
<RequestDetails request={selectedRequest} />
|
|
1525
1575
|
) : selectedDocPage ? (
|
|
1526
|
-
<DocPage slug={selectedDocPage} />
|
|
1576
|
+
<DocPage slug={selectedDocPage} onSearch={openSearch} />
|
|
1577
|
+
) : notFoundSlug ? (
|
|
1578
|
+
<NotFoundPage slug={notFoundSlug} onSearch={openSearch} />
|
|
1527
1579
|
) : (
|
|
1528
1580
|
<div className="flex-1 flex items-center justify-center bg-background">
|
|
1529
1581
|
<div className="text-center text-muted-foreground">
|
|
@@ -77,6 +77,26 @@ export function CollectionTree({
|
|
|
77
77
|
}
|
|
78
78
|
return expanded
|
|
79
79
|
})
|
|
80
|
+
|
|
81
|
+
// Re-expand all folders when collection folders change (e.g., tab switch)
|
|
82
|
+
// This ensures folders are expanded when switching between tabs with different folder sets
|
|
83
|
+
const folderIds = useMemo(() =>
|
|
84
|
+
collection.folders.map(f => f.id).sort().join(','),
|
|
85
|
+
[collection.folders]
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (level === 0 && collection.folders.length > 0) {
|
|
90
|
+
setExpandedFolders(prev => {
|
|
91
|
+
const newExpanded = new Set(prev)
|
|
92
|
+
// Add all current folder IDs to expanded set
|
|
93
|
+
collection.folders.forEach((folder) => {
|
|
94
|
+
newExpanded.add(folder.id)
|
|
95
|
+
})
|
|
96
|
+
return newExpanded
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}, [folderIds, level, collection.folders])
|
|
80
100
|
|
|
81
101
|
// Auto-expand folder when selectedRequest changes (e.g., via agent navigation)
|
|
82
102
|
useEffect(() => {
|
package/renderer/app/favicon.ico
DELETED
|
Binary file
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# My Documentation
|
|
2
|
-
|
|
3
|
-
Documentation site built with [DevDoc](https://github.com/brainfish-ai/devdoc).
|
|
4
|
-
|
|
5
|
-
[](https://vercel.com/new/clone?repository-url=https://github.com/brainfish-ai/devdoc-starter)
|
|
6
|
-
|
|
7
|
-
## Quick Start
|
|
8
|
-
|
|
9
|
-
### Prerequisites
|
|
10
|
-
|
|
11
|
-
- [Node.js](https://nodejs.org/) 18.0 or later
|
|
12
|
-
- npm, yarn, or pnpm
|
|
13
|
-
|
|
14
|
-
### Installation
|
|
15
|
-
|
|
16
|
-
1. **Clone this repository**
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
git clone https://github.com/your-username/your-docs.git
|
|
20
|
-
cd your-docs
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Or use the GitHub template button above.
|
|
24
|
-
|
|
25
|
-
2. **Install dependencies**
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm install
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
3. **Start the development server**
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm run dev
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
4. **Open your browser**
|
|
38
|
-
|
|
39
|
-
Visit [http://localhost:3000](http://localhost:3000) to see your docs.
|
|
40
|
-
|
|
41
|
-
## Project Structure
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
.
|
|
45
|
-
├── docs.json # Site configuration
|
|
46
|
-
├── index.mdx # Homepage
|
|
47
|
-
├── quickstart.mdx # Quickstart guide
|
|
48
|
-
├── guides/
|
|
49
|
-
│ ├── overview.mdx # Overview page
|
|
50
|
-
│ └── configuration.mdx # Configuration guide
|
|
51
|
-
├── public/
|
|
52
|
-
│ ├── logo.svg # Site logo
|
|
53
|
-
│ └── favicon.svg # Favicon
|
|
54
|
-
├── package.json
|
|
55
|
-
├── vercel.json # Vercel deployment config
|
|
56
|
-
└── README.md
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Configuration
|
|
60
|
-
|
|
61
|
-
Edit `docs.json` to customize your site:
|
|
62
|
-
|
|
63
|
-
```json
|
|
64
|
-
{
|
|
65
|
-
"name": "My Documentation",
|
|
66
|
-
"logo": "/logo.svg",
|
|
67
|
-
"navigation": {
|
|
68
|
-
"tabs": [
|
|
69
|
-
{
|
|
70
|
-
"tab": "Documentation",
|
|
71
|
-
"type": "docs",
|
|
72
|
-
"groups": [
|
|
73
|
-
{
|
|
74
|
-
"group": "Getting Started",
|
|
75
|
-
"pages": ["index", "quickstart"]
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
}
|
|
79
|
-
]
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Writing Content
|
|
85
|
-
|
|
86
|
-
Create `.mdx` files with frontmatter:
|
|
87
|
-
|
|
88
|
-
```mdx
|
|
89
|
-
---
|
|
90
|
-
title: Page Title
|
|
91
|
-
description: Page description for SEO
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
# Your Content Here
|
|
95
|
-
|
|
96
|
-
Write Markdown with React components.
|
|
97
|
-
|
|
98
|
-
<Note>
|
|
99
|
-
Use components like Note, Card, Steps, and more.
|
|
100
|
-
</Note>
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Available Commands
|
|
104
|
-
|
|
105
|
-
| Command | Description |
|
|
106
|
-
|---------|-------------|
|
|
107
|
-
| `npm run dev` | Start development server |
|
|
108
|
-
| `npm run build` | Build for production |
|
|
109
|
-
| `npm run start` | Start production server |
|
|
110
|
-
| `npm run check` | Validate configuration |
|
|
111
|
-
|
|
112
|
-
## Deployment
|
|
113
|
-
|
|
114
|
-
### Vercel (Recommended)
|
|
115
|
-
|
|
116
|
-
Click the "Deploy with Vercel" button above, or:
|
|
117
|
-
|
|
118
|
-
1. Push your code to GitHub
|
|
119
|
-
2. Import the repository in [Vercel](https://vercel.com)
|
|
120
|
-
3. Deploy!
|
|
121
|
-
|
|
122
|
-
### Other Platforms
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
# Build the site
|
|
126
|
-
npm run build
|
|
127
|
-
|
|
128
|
-
# Output is in the dist/ directory
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Learn More
|
|
132
|
-
|
|
133
|
-
- [DevDoc Documentation](https://devdoc.sh/docs)
|
|
134
|
-
- [MDX Documentation](https://mdxjs.com/)
|
|
135
|
-
- [Vercel Documentation](https://vercel.com/docs)
|
|
136
|
-
|
|
137
|
-
## License
|
|
138
|
-
|
|
139
|
-
MIT
|