@brainfish-ai/devdoc 0.1.21 → 0.1.23
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 +1 -1
- package/renderer/app/api/collections/route.ts +2 -16
- package/renderer/app/api/docs/route.ts +10 -0
- package/renderer/app/api/schema/route.ts +11 -3
- package/renderer/app/globals.css +88 -0
- package/renderer/components/docs/mdx/index.ts +33 -0
- package/renderer/components/docs/mdx/landing.tsx +684 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +81 -14
- package/renderer/components/docs-viewer/index.tsx +203 -59
- package/renderer/components/docs-viewer/sidebar/index.tsx +3 -95
- package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +2 -16
- package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +2 -16
- package/renderer/lib/api-docs/factories.ts +45 -26
- package/renderer/lib/api-docs/parsers/graphql/parser.ts +1 -1
- package/renderer/lib/api-docs/parsers/openapi/transformer.ts +1 -0
- package/renderer/lib/docs/config/schema.ts +11 -1
- package/renderer/lib/docs/mdx/frontmatter.ts +11 -0
- package/renderer/lib/utils/icons.ts +48 -0
- package/renderer/components/docs-viewer/content/introduction.tsx +0 -21
|
@@ -120,6 +120,20 @@ import {
|
|
|
120
120
|
PDF,
|
|
121
121
|
Audio,
|
|
122
122
|
Download,
|
|
123
|
+
// Landing Page Components
|
|
124
|
+
Hero,
|
|
125
|
+
Pre,
|
|
126
|
+
Tagline,
|
|
127
|
+
Headline,
|
|
128
|
+
Description,
|
|
129
|
+
CommandBox,
|
|
130
|
+
Section,
|
|
131
|
+
Center,
|
|
132
|
+
FeatureGrid,
|
|
133
|
+
FeatureItem,
|
|
134
|
+
ButtonLink,
|
|
135
|
+
Spacer,
|
|
136
|
+
Divider,
|
|
123
137
|
} from '../../docs/mdx/index'
|
|
124
138
|
|
|
125
139
|
// MDX components mapping
|
|
@@ -190,6 +204,21 @@ const mdxComponents = {
|
|
|
190
204
|
Audio,
|
|
191
205
|
Download,
|
|
192
206
|
|
|
207
|
+
// Landing Page
|
|
208
|
+
Hero,
|
|
209
|
+
Pre,
|
|
210
|
+
Tagline,
|
|
211
|
+
Headline,
|
|
212
|
+
Description,
|
|
213
|
+
CommandBox,
|
|
214
|
+
Section,
|
|
215
|
+
Center,
|
|
216
|
+
FeatureGrid,
|
|
217
|
+
FeatureItem,
|
|
218
|
+
ButtonLink,
|
|
219
|
+
Spacer,
|
|
220
|
+
Divider,
|
|
221
|
+
|
|
193
222
|
// Links
|
|
194
223
|
a: MdxLink,
|
|
195
224
|
}
|
|
@@ -204,6 +233,11 @@ interface DocPageData {
|
|
|
204
233
|
title: string
|
|
205
234
|
description?: string
|
|
206
235
|
icon?: string
|
|
236
|
+
// Page mode and layout options
|
|
237
|
+
mode?: 'default' | 'wide' | 'custom'
|
|
238
|
+
hideHeader?: boolean
|
|
239
|
+
fullWidth?: boolean
|
|
240
|
+
background?: string
|
|
207
241
|
}
|
|
208
242
|
mdxSource: MDXRemoteSerializeResult
|
|
209
243
|
}
|
|
@@ -244,7 +278,7 @@ export function DocPage({ slug }: DocPageProps) {
|
|
|
244
278
|
|
|
245
279
|
if (loading) {
|
|
246
280
|
return (
|
|
247
|
-
<div className="docs-page docs-page-loading
|
|
281
|
+
<div className="docs-page docs-page-loading w-full min-h-[200px]">
|
|
248
282
|
<div className="docs-loading flex items-center justify-center py-12">
|
|
249
283
|
<Spinner className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
250
284
|
</div>
|
|
@@ -254,7 +288,7 @@ export function DocPage({ slug }: DocPageProps) {
|
|
|
254
288
|
|
|
255
289
|
if (error || !pageData) {
|
|
256
290
|
return (
|
|
257
|
-
<div className="docs-page docs-page-error
|
|
291
|
+
<div className="docs-page docs-page-error w-full min-h-[200px]">
|
|
258
292
|
<div className="docs-error text-center py-12">
|
|
259
293
|
<p className="text-destructive">{error || 'Page not found'}</p>
|
|
260
294
|
</div>
|
|
@@ -262,19 +296,52 @@ export function DocPage({ slug }: DocPageProps) {
|
|
|
262
296
|
)
|
|
263
297
|
}
|
|
264
298
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
299
|
+
const { mode = 'default', hideHeader, fullWidth, background } = pageData.frontmatter
|
|
300
|
+
const isCustomMode = mode === 'custom'
|
|
301
|
+
const isWideMode = mode === 'wide'
|
|
302
|
+
const showHeader = !hideHeader && !isCustomMode
|
|
303
|
+
|
|
304
|
+
// Custom mode: Full-width layout for landing pages
|
|
305
|
+
if (isCustomMode) {
|
|
306
|
+
return (
|
|
307
|
+
<div
|
|
308
|
+
ref={contentRef}
|
|
309
|
+
className="docs-page docs-page-custom docs-content w-full min-h-full"
|
|
310
|
+
style={{
|
|
311
|
+
background: background || 'var(--background)',
|
|
312
|
+
// Ensure the content fills the entire viewport width within the content area
|
|
313
|
+
marginLeft: 0,
|
|
314
|
+
marginRight: 0,
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
317
|
+
{/* Custom pages render MDX content directly without container constraints */}
|
|
318
|
+
<div className="docs-custom-content [&>*]:w-full">
|
|
319
|
+
<MDXRemote {...pageData.mdxSource} components={mdxComponents} />
|
|
320
|
+
</div>
|
|
277
321
|
</div>
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Default/Wide mode: Standard documentation layout
|
|
326
|
+
const containerClass = isWideMode
|
|
327
|
+
? 'max-w-6xl' // Wide mode
|
|
328
|
+
: 'max-w-4xl' // Default mode
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<div ref={contentRef} className={`docs-page docs-content ${containerClass} mx-auto px-4 py-6 sm:px-8 sm:py-8`}>
|
|
332
|
+
{/* Page header - shown unless hideHeader is true */}
|
|
333
|
+
{showHeader && (
|
|
334
|
+
<div className="docs-page-header mb-6">
|
|
335
|
+
<h1 className="docs-content-title text-2xl sm:text-3xl font-bold mb-2 text-foreground">
|
|
336
|
+
{pageData.frontmatter.title}
|
|
337
|
+
</h1>
|
|
338
|
+
{pageData.frontmatter.description && (
|
|
339
|
+
<p className="docs-content-description text-lg text-muted-foreground">
|
|
340
|
+
{pageData.frontmatter.description}
|
|
341
|
+
</p>
|
|
342
|
+
)}
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
278
345
|
|
|
279
346
|
{/* Page content - MDX rendered with custom components */}
|
|
280
347
|
<div className="docs-prose prose prose-sm max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-strong:text-foreground prose-code:text-foreground prose-pre:bg-muted prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-pre:overflow-x-auto prose-table:w-full prose-th:text-left prose-th:p-3 prose-th:bg-muted prose-td:p-3 prose-td:border-b prose-td:border-border">
|
|
@@ -7,7 +7,6 @@ import { DocsSidebar } from './sidebar'
|
|
|
7
7
|
import { ApiPlayground } from './playground'
|
|
8
8
|
import type { DebugContext } from './playground/response-viewer'
|
|
9
9
|
import { RightSidebar } from './sidebar/right-sidebar'
|
|
10
|
-
import { Introduction } from './content/introduction'
|
|
11
10
|
import { RequestDetails } from './content/request-details'
|
|
12
11
|
import { DocPage } from './content/doc-page'
|
|
13
12
|
import { ChangelogPage } from './content/changelog-page'
|
|
@@ -241,6 +240,53 @@ function findRequestById(collection: BrainfishCollection, id: string): Brainfish
|
|
|
241
240
|
return null
|
|
242
241
|
}
|
|
243
242
|
|
|
243
|
+
// Helper to get the first endpoint from collection
|
|
244
|
+
function getFirstEndpoint(collection: BrainfishCollection): BrainfishRESTRequest | null {
|
|
245
|
+
// Check direct requests first
|
|
246
|
+
if (collection.requests.length > 0) {
|
|
247
|
+
return collection.requests[0]
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check folders recursively
|
|
251
|
+
for (const folder of collection.folders) {
|
|
252
|
+
const firstInFolder = getFirstEndpoint(folder)
|
|
253
|
+
if (firstInFolder) return firstInFolder
|
|
254
|
+
}
|
|
255
|
+
return null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Helper to check if a doc group belongs to a specific tab
|
|
259
|
+
// Group IDs are formatted as: group-{tab-id}-{group-name}
|
|
260
|
+
function isGroupForTab(groupId: string, tabId: string): boolean {
|
|
261
|
+
// Check if the group ID starts with "group-{tabId}-"
|
|
262
|
+
const prefix = `group-${tabId}-`
|
|
263
|
+
return groupId.startsWith(prefix)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Helper to check if a tab has doc groups (MDX pages)
|
|
267
|
+
function hasDocGroupsForTab(
|
|
268
|
+
docGroups: BrainfishDocGroup[] | undefined,
|
|
269
|
+
tabId: string
|
|
270
|
+
): boolean {
|
|
271
|
+
if (!docGroups || docGroups.length === 0) return false
|
|
272
|
+
|
|
273
|
+
// Check if any doc group belongs to this tab and has pages
|
|
274
|
+
return docGroups.some(g => isGroupForTab(g.id, tabId) && g.pages.length > 0)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Helper to get the first page from doc groups for a tab
|
|
278
|
+
function getFirstDocPageForTab(
|
|
279
|
+
docGroups: BrainfishDocGroup[] | undefined,
|
|
280
|
+
tabId: string
|
|
281
|
+
): string | null {
|
|
282
|
+
if (!docGroups || docGroups.length === 0) return null
|
|
283
|
+
|
|
284
|
+
const tabDocGroup = docGroups.find(g => isGroupForTab(g.id, tabId) && g.pages.length > 0)
|
|
285
|
+
|
|
286
|
+
return tabDocGroup?.pages[0]?.slug || null
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
244
290
|
// API version
|
|
245
291
|
interface ApiVersion {
|
|
246
292
|
version: string
|
|
@@ -391,19 +437,21 @@ function DocsContent() {
|
|
|
391
437
|
const navigateToHash = useCallback((collectionData: BrainfishCollection) => {
|
|
392
438
|
const hash = window.location.hash.slice(1) // Remove #
|
|
393
439
|
|
|
394
|
-
console.log('[Docs] Navigating to hash:', hash)
|
|
395
|
-
|
|
396
440
|
if (!hash) {
|
|
397
|
-
// No hash -
|
|
398
|
-
setSelectedRequest(null)
|
|
441
|
+
// No hash - auto-select first endpoint
|
|
399
442
|
setSelectedDocSection(null)
|
|
400
443
|
setSelectedDocPage(null)
|
|
444
|
+
const firstEndpoint = getFirstEndpoint(collectionData)
|
|
445
|
+
if (firstEndpoint) {
|
|
446
|
+
setSelectedRequest(firstEndpoint)
|
|
447
|
+
} else {
|
|
448
|
+
setSelectedRequest(null)
|
|
449
|
+
}
|
|
401
450
|
return
|
|
402
451
|
}
|
|
403
452
|
|
|
404
453
|
// Notes mode is handled by ModeContext, just clear API selection
|
|
405
454
|
if (hash === 'notes' || hash.startsWith('notes/')) {
|
|
406
|
-
console.log('[Docs] Notes mode detected, clearing API selection')
|
|
407
455
|
setSelectedRequest(null)
|
|
408
456
|
setSelectedDocSection(null)
|
|
409
457
|
setSelectedDocPage(null)
|
|
@@ -428,9 +476,7 @@ function DocsContent() {
|
|
|
428
476
|
const actualId = id || (legacyType ? hash.replace(`${legacyType}/`, '') : null)
|
|
429
477
|
|
|
430
478
|
if (actualType === 'endpoint' && actualId) {
|
|
431
|
-
console.log('[Docs] Looking for endpoint:', actualId)
|
|
432
479
|
const request = findRequestById(collectionData, actualId)
|
|
433
|
-
console.log('[Docs] Found request:', request?.name || 'NOT FOUND')
|
|
434
480
|
if (request) {
|
|
435
481
|
setSelectedRequest(request)
|
|
436
482
|
setSelectedDocSection(null)
|
|
@@ -598,18 +644,55 @@ function DocsContent() {
|
|
|
598
644
|
// Reset mode to docs when switching tabs (exit sandbox/api-client mode)
|
|
599
645
|
switchToDocs()
|
|
600
646
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
updateUrlHash('', tabId)
|
|
607
|
-
} else if (tabId === 'changelog') {
|
|
647
|
+
// Find the tab config to check its type
|
|
648
|
+
const tabConfig = collection?.navigationTabs?.find(t => t.id === tabId)
|
|
649
|
+
const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || tabId === 'api-reference'
|
|
650
|
+
|
|
651
|
+
if (tabId === 'changelog') {
|
|
608
652
|
// Switch to Changelog tab
|
|
609
653
|
setSelectedDocPage(null)
|
|
610
654
|
setSelectedRequest(null)
|
|
611
655
|
setSelectedDocSection(null)
|
|
612
656
|
updateUrlHash('', tabId)
|
|
657
|
+
} else if (isApiTab) {
|
|
658
|
+
// API Reference or GraphQL tab
|
|
659
|
+
setSelectedDocSection(null)
|
|
660
|
+
|
|
661
|
+
// Check if there are doc groups for this tab (MDX pages)
|
|
662
|
+
const hasGroups = hasDocGroupsForTab(collection?.docGroups, tabId)
|
|
663
|
+
|
|
664
|
+
if (hasGroups) {
|
|
665
|
+
// Has doc groups - select the first page from groups
|
|
666
|
+
const firstPage = getFirstDocPageForTab(collection?.docGroups, tabId)
|
|
667
|
+
if (firstPage) {
|
|
668
|
+
setSelectedDocPage(firstPage)
|
|
669
|
+
setSelectedRequest(null)
|
|
670
|
+
updateUrlHash(`page/${firstPage}`, tabId)
|
|
671
|
+
switchToDocs()
|
|
672
|
+
} else {
|
|
673
|
+
// No pages in groups - auto-select first endpoint
|
|
674
|
+
setSelectedDocPage(null)
|
|
675
|
+
const firstEndpoint = collection ? getFirstEndpoint(collection) : null
|
|
676
|
+
if (firstEndpoint) {
|
|
677
|
+
setSelectedRequest(firstEndpoint)
|
|
678
|
+
updateUrlHash(`endpoint/${firstEndpoint.id}`, tabId)
|
|
679
|
+
} else {
|
|
680
|
+
setSelectedRequest(null)
|
|
681
|
+
updateUrlHash('', tabId)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
// No doc groups - auto-select first endpoint
|
|
686
|
+
setSelectedDocPage(null)
|
|
687
|
+
const firstEndpoint = collection ? getFirstEndpoint(collection) : null
|
|
688
|
+
if (firstEndpoint) {
|
|
689
|
+
setSelectedRequest(firstEndpoint)
|
|
690
|
+
updateUrlHash(`endpoint/${firstEndpoint.id}`, tabId)
|
|
691
|
+
} else {
|
|
692
|
+
setSelectedRequest(null)
|
|
693
|
+
updateUrlHash('', tabId)
|
|
694
|
+
}
|
|
695
|
+
}
|
|
613
696
|
} else {
|
|
614
697
|
// Switch to a doc group tab - find and select the first page in that tab
|
|
615
698
|
setSelectedRequest(null)
|
|
@@ -617,10 +700,7 @@ function DocsContent() {
|
|
|
617
700
|
|
|
618
701
|
// Find the first doc group for this tab and select its first page
|
|
619
702
|
if (collection?.docGroups) {
|
|
620
|
-
const tabDocGroup = collection.docGroups.find(g =>
|
|
621
|
-
const groupTabPart = g.id.replace('group-', '').split('-')[0]
|
|
622
|
-
return groupTabPart === tabId
|
|
623
|
-
})
|
|
703
|
+
const tabDocGroup = collection.docGroups.find(g => isGroupForTab(g.id, tabId))
|
|
624
704
|
|
|
625
705
|
if (tabDocGroup && tabDocGroup.pages.length > 0) {
|
|
626
706
|
const firstPage = tabDocGroup.pages[0]
|
|
@@ -733,8 +813,6 @@ function DocsContent() {
|
|
|
733
813
|
// Also update shortcut icon and apple-touch-icon
|
|
734
814
|
updateFaviconLink('shortcut icon', faviconPath)
|
|
735
815
|
updateFaviconLink('apple-touch-icon', faviconPath)
|
|
736
|
-
|
|
737
|
-
console.log('[Docs] Set favicon to:', faviconPath)
|
|
738
816
|
}, [collection?.docsFavicon])
|
|
739
817
|
|
|
740
818
|
useEffect(() => {
|
|
@@ -761,16 +839,6 @@ function DocsContent() {
|
|
|
761
839
|
}
|
|
762
840
|
|
|
763
841
|
const data = await response.json()
|
|
764
|
-
|
|
765
|
-
console.log('[Docs] Collection data:', JSON.stringify({
|
|
766
|
-
hasData: !!data,
|
|
767
|
-
name: data?.name,
|
|
768
|
-
requestsCount: data?.requests?.length || 0,
|
|
769
|
-
foldersCount: data?.folders?.length || 0,
|
|
770
|
-
apiVersions: data?.apiVersions?.length || 0,
|
|
771
|
-
selectedVersion: data?.selectedApiVersion,
|
|
772
|
-
}, null, 2))
|
|
773
|
-
|
|
774
842
|
setCollection(data)
|
|
775
843
|
|
|
776
844
|
// Set initial API version if not already set
|
|
@@ -793,24 +861,60 @@ function DocsContent() {
|
|
|
793
861
|
// Use setTimeout to ensure state is set before navigation
|
|
794
862
|
setTimeout(() => {
|
|
795
863
|
const hash = window.location.hash.slice(1)
|
|
796
|
-
console.log('[Docs] Initial hash:', hash)
|
|
797
864
|
|
|
798
865
|
if (!hash) {
|
|
799
866
|
// No hash - set URL to initial tab
|
|
800
|
-
updateUrlHash('', initialTabId)
|
|
801
|
-
setSelectedRequest(null)
|
|
802
867
|
setSelectedDocSection(null)
|
|
803
868
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
869
|
+
// Find the tab config to check its type
|
|
870
|
+
const tabConfig = data.navigationTabs?.find((t: NavigationTab) => t.id === initialTabId)
|
|
871
|
+
const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || initialTabId === 'api-reference'
|
|
872
|
+
|
|
873
|
+
if (isApiTab) {
|
|
874
|
+
// API Reference or GraphQL tab
|
|
875
|
+
setSelectedDocSection(null)
|
|
876
|
+
|
|
877
|
+
// Check if there are doc groups for this tab (MDX pages)
|
|
878
|
+
const hasGroups = hasDocGroupsForTab(data.docGroups, initialTabId)
|
|
879
|
+
|
|
880
|
+
if (hasGroups) {
|
|
881
|
+
// Has doc groups - select the first page from groups
|
|
882
|
+
const firstPage = getFirstDocPageForTab(data.docGroups, initialTabId)
|
|
883
|
+
if (firstPage) {
|
|
884
|
+
setSelectedDocPage(firstPage)
|
|
885
|
+
setSelectedRequest(null)
|
|
886
|
+
updateUrlHash(`page/${firstPage}`, initialTabId)
|
|
887
|
+
} else {
|
|
888
|
+
// No pages in groups - auto-select first endpoint
|
|
889
|
+
setSelectedDocPage(null)
|
|
890
|
+
const firstEndpoint = getFirstEndpoint(data)
|
|
891
|
+
if (firstEndpoint) {
|
|
892
|
+
setSelectedRequest(firstEndpoint)
|
|
893
|
+
updateUrlHash(`endpoint/${firstEndpoint.id}`, initialTabId)
|
|
894
|
+
} else {
|
|
895
|
+
setSelectedRequest(null)
|
|
896
|
+
updateUrlHash('', initialTabId)
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
} else {
|
|
900
|
+
// No doc groups - auto-select first endpoint
|
|
901
|
+
setSelectedDocPage(null)
|
|
902
|
+
const firstEndpoint = getFirstEndpoint(data)
|
|
903
|
+
if (firstEndpoint) {
|
|
904
|
+
setSelectedRequest(firstEndpoint)
|
|
905
|
+
updateUrlHash(`endpoint/${firstEndpoint.id}`, initialTabId)
|
|
906
|
+
} else {
|
|
907
|
+
setSelectedRequest(null)
|
|
908
|
+
updateUrlHash('', initialTabId)
|
|
909
|
+
}
|
|
910
|
+
}
|
|
807
911
|
switchToDocs()
|
|
808
912
|
} else {
|
|
809
913
|
// Doc group tab - select first page
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
914
|
+
setSelectedRequest(null)
|
|
915
|
+
const tabDocGroup = data.docGroups?.find((g: BrainfishDocGroup) =>
|
|
916
|
+
isGroupForTab(g.id, initialTabId)
|
|
917
|
+
)
|
|
814
918
|
|
|
815
919
|
if (tabDocGroup && tabDocGroup.pages.length > 0) {
|
|
816
920
|
const firstPage = tabDocGroup.pages[0]
|
|
@@ -819,12 +923,12 @@ function DocsContent() {
|
|
|
819
923
|
switchToDocs()
|
|
820
924
|
} else {
|
|
821
925
|
setSelectedDocPage(null)
|
|
926
|
+
updateUrlHash('', initialTabId)
|
|
822
927
|
switchToDocs()
|
|
823
928
|
}
|
|
824
929
|
}
|
|
825
930
|
} else if (hash === 'notes' || hash.startsWith('notes/')) {
|
|
826
931
|
// Notes mode - handled by ModeContext, just clear API selection
|
|
827
|
-
console.log('[Docs] Initial hash is notes mode')
|
|
828
932
|
setSelectedRequest(null)
|
|
829
933
|
setSelectedDocSection(null)
|
|
830
934
|
} else {
|
|
@@ -846,14 +950,13 @@ function DocsContent() {
|
|
|
846
950
|
const actualId = isLegacyFormat ? parts.slice(1).join('/') : hashId
|
|
847
951
|
|
|
848
952
|
if (actualType === 'endpoint' && actualId) {
|
|
849
|
-
console.log('[Docs] Looking for endpoint on load:', actualId)
|
|
850
953
|
const request = findRequestById(data, actualId)
|
|
851
|
-
console.log('[Docs] Found:', request?.name || 'NOT FOUND')
|
|
852
954
|
if (request) {
|
|
853
955
|
setSelectedRequest(request)
|
|
854
956
|
setSelectedDocSection(null)
|
|
855
957
|
setSelectedDocPage(null)
|
|
856
958
|
switchToDocs()
|
|
959
|
+
return
|
|
857
960
|
} else {
|
|
858
961
|
setSelectedRequest(null)
|
|
859
962
|
setSelectedDocSection(null)
|
|
@@ -878,9 +981,49 @@ function DocsContent() {
|
|
|
878
981
|
}, 100)
|
|
879
982
|
} else if (hashTab && !hashType) {
|
|
880
983
|
// Just a tab, show its default content
|
|
881
|
-
setSelectedRequest(null)
|
|
882
984
|
setSelectedDocSection(null)
|
|
883
|
-
|
|
985
|
+
|
|
986
|
+
// Check if this is an API tab
|
|
987
|
+
const tabConfig = data.navigationTabs?.find((t: NavigationTab) => t.id === hashTab)
|
|
988
|
+
const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || hashTab === 'api-reference'
|
|
989
|
+
|
|
990
|
+
if (isApiTab) {
|
|
991
|
+
// Check if there are doc groups for this tab (MDX pages)
|
|
992
|
+
const hasGroups = hasDocGroupsForTab(data.docGroups, hashTab)
|
|
993
|
+
|
|
994
|
+
if (hasGroups) {
|
|
995
|
+
// Has doc groups - select the first page from groups
|
|
996
|
+
const firstPage = getFirstDocPageForTab(data.docGroups, hashTab)
|
|
997
|
+
if (firstPage) {
|
|
998
|
+
setSelectedDocPage(firstPage)
|
|
999
|
+
setSelectedRequest(null)
|
|
1000
|
+
updateUrlHash(`page/${firstPage}`, hashTab)
|
|
1001
|
+
} else {
|
|
1002
|
+
// No pages in groups - auto-select first endpoint
|
|
1003
|
+
setSelectedDocPage(null)
|
|
1004
|
+
const firstEndpoint = getFirstEndpoint(data)
|
|
1005
|
+
if (firstEndpoint) {
|
|
1006
|
+
setSelectedRequest(firstEndpoint)
|
|
1007
|
+
updateUrlHash(`endpoint/${firstEndpoint.id}`, hashTab)
|
|
1008
|
+
} else {
|
|
1009
|
+
setSelectedRequest(null)
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
} else {
|
|
1013
|
+
// No doc groups - auto-select first endpoint
|
|
1014
|
+
setSelectedDocPage(null)
|
|
1015
|
+
const firstEndpoint = getFirstEndpoint(data)
|
|
1016
|
+
if (firstEndpoint) {
|
|
1017
|
+
setSelectedRequest(firstEndpoint)
|
|
1018
|
+
updateUrlHash(`endpoint/${firstEndpoint.id}`, hashTab)
|
|
1019
|
+
} else {
|
|
1020
|
+
setSelectedRequest(null)
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
} else {
|
|
1024
|
+
setSelectedDocPage(null)
|
|
1025
|
+
setSelectedRequest(null)
|
|
1026
|
+
}
|
|
884
1027
|
switchToDocs()
|
|
885
1028
|
}
|
|
886
1029
|
}
|
|
@@ -1170,17 +1313,13 @@ function DocsWithMode({
|
|
|
1170
1313
|
// Find the active tab's config
|
|
1171
1314
|
const activeTabConfig = collection.navigationTabs?.find(t => t.id === activeTab)
|
|
1172
1315
|
const activeTabType = activeTabConfig?.type || 'docs'
|
|
1173
|
-
// Use the tab name for page header
|
|
1174
|
-
const activeTabTitle = activeTabConfig?.tab || 'Documentation'
|
|
1175
1316
|
|
|
1176
1317
|
|
|
1177
1318
|
// Filter doc groups for sidebar based on active tab
|
|
1178
1319
|
// Group IDs are like "group-guides-getting-started", tab IDs are like "guides"
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
return groupTabPart === activeTab
|
|
1183
|
-
}) || []
|
|
1320
|
+
// Filter doc groups for sidebar based on active tab
|
|
1321
|
+
// Group IDs are formatted as: group-{tab-id}-{group-name}
|
|
1322
|
+
const filteredDocGroups = collection.docGroups?.filter(g => isGroupForTab(g.id, activeTab)) || []
|
|
1184
1323
|
|
|
1185
1324
|
// Show endpoints in sidebar only when OpenAPI tab is active
|
|
1186
1325
|
const showEndpoints = activeTabType === 'openapi'
|
|
@@ -1285,7 +1424,7 @@ function DocsWithMode({
|
|
|
1285
1424
|
}, [collection?.defaultTheme, setTheme])
|
|
1286
1425
|
|
|
1287
1426
|
return (
|
|
1288
|
-
|
|
1427
|
+
<div className="docs-viewer-root flex flex-col h-screen overflow-hidden">
|
|
1289
1428
|
{/* Notice */}
|
|
1290
1429
|
{collection.notice && (
|
|
1291
1430
|
<Notice config={collection.notice} storageKey="docs-notice" />
|
|
@@ -1313,7 +1452,7 @@ function DocsWithMode({
|
|
|
1313
1452
|
docsNavbar={collection.docsNavbar}
|
|
1314
1453
|
/>
|
|
1315
1454
|
|
|
1316
|
-
<div className="docs-layout flex
|
|
1455
|
+
<div className="docs-layout flex flex-1 overflow-hidden relative z-0 min-h-0">
|
|
1317
1456
|
{/* Left Sidebar - Hidden for changelog */}
|
|
1318
1457
|
{!showChangelog && (
|
|
1319
1458
|
<DocsSidebar
|
|
@@ -1344,9 +1483,9 @@ function DocsWithMode({
|
|
|
1344
1483
|
)}
|
|
1345
1484
|
|
|
1346
1485
|
{/* Center - Toggles between API Client/Playground and Notes */}
|
|
1347
|
-
<div className="docs-main flex-1 flex flex-col overflow-hidden
|
|
1486
|
+
<div className="docs-main flex-1 flex flex-col overflow-hidden" style={{ minWidth: 0, flexBasis: 0, flexGrow: 1 }}>
|
|
1348
1487
|
{/* Mode Toggle Header */}
|
|
1349
|
-
<div className="docs-main-header flex items-center justify-end px-3 sm:px-4 h-[41px] border-b border-border bg-muted/30">
|
|
1488
|
+
<div className="docs-main-header flex items-center justify-end px-3 sm:px-4 h-[41px] border-b border-border bg-muted/30 flex-shrink-0">
|
|
1350
1489
|
<ModeToggleTabs hasEndpoint={!!selectedRequest} />
|
|
1351
1490
|
</div>
|
|
1352
1491
|
|
|
@@ -1386,7 +1525,12 @@ function DocsWithMode({
|
|
|
1386
1525
|
) : selectedDocPage ? (
|
|
1387
1526
|
<DocPage slug={selectedDocPage} />
|
|
1388
1527
|
) : (
|
|
1389
|
-
<
|
|
1528
|
+
<div className="flex-1 flex items-center justify-center bg-background">
|
|
1529
|
+
<div className="text-center text-muted-foreground">
|
|
1530
|
+
<Book className="h-12 w-12 mx-auto mb-3 opacity-40" />
|
|
1531
|
+
<p className="text-sm">Select an endpoint from the sidebar</p>
|
|
1532
|
+
</div>
|
|
1533
|
+
</div>
|
|
1390
1534
|
)}
|
|
1391
1535
|
</div>
|
|
1392
1536
|
</DocsNavigationProvider>
|
|
@@ -1446,7 +1590,7 @@ function DocsWithMode({
|
|
|
1446
1590
|
open={showAuthModal}
|
|
1447
1591
|
onClose={() => setShowAuthModal(false)}
|
|
1448
1592
|
/>
|
|
1449
|
-
|
|
1593
|
+
</div>
|
|
1450
1594
|
)
|
|
1451
1595
|
}
|
|
1452
1596
|
|