refactor: simplify reference docs nav menu & code split

This commit is contained in:
Greg Richardson
2023-05-08 12:46:33 -06:00
parent 695b00c0ce
commit 0a4700f2a1
16 changed files with 556 additions and 624 deletions

View File

@@ -3,177 +3,261 @@ import { memo, useEffect } from 'react'
import { menuState, useMenuLevelId } from '~/hooks/useMenuState'
import NavigationMenuGuideList from './NavigationMenuGuideList'
import NavigationMenuRefList from './NavigationMenuRefList'
import spec_js_v2 from '~/../../spec/supabase_js_v2.yml' assert { type: 'yml' }
import spec_js_v1 from '~/../../spec/supabase_js_v1.yml' assert { type: 'yml' }
import spec_dart_v1 from '~/../../spec/supabase_dart_v1.yml' assert { type: 'yml' }
import spec_dart_v0 from '~/../../spec/supabase_dart_v0.yml' assert { type: 'yml' }
import spec_csharp_v0 from '~/../../spec/supabase_csharp_v0.yml' assert { type: 'yml' }
import spec_python_v2 from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' }
import spec_swift_v0 from '~/../../spec/supabase_swift_v0.yml' assert { type: 'yml' }
// import { gen_v3 } from '~/lib/refGenerator/helpers'
import apiCommonSections from '~/../../spec/common-api-sections.json'
import cliCommonSections from '~/../../spec/common-cli-sections.json'
import libCommonSections from '~/../../spec/common-client-libs-sections.json'
import authServerCommonSections from '~/../../spec/common-self-hosting-auth-sections.json'
import realtimeServerCommonSections from '~/../../spec/common-self-hosting-realtime-sections.json'
import analyticsServerCommonSections from '~/../../spec/common-self-hosting-analytics-sections.json'
import functionsServerCommonSections from '~/../../spec/common-self-hosting-functions-sections.json'
import storageServerCommonSections from '~/../../spec/common-self-hosting-storage-sections.json'
import { flattenSections } from '~/lib/helpers'
import NavigationMenuHome from './HomeMenu'
import { Json } from '~/types'
import { ICommonBase } from '~/components/reference/Reference.types'
// Filter libCommonSections for just the relevant sections in the current library
export function generateAllowedClientLibKeys(sections, spec) {
// Filter parent sections first
const specIds = spec.functions.map((func) => {
return func.id
})
const newShape = flattenSections(sections).filter((section) => {
if (specIds.includes(section.id)) {
return section
}
})
const final = newShape.map((func) => {
return func.id
})
return final
// Import dynamically to code split / reduce app bundle size
const specImports = {
specJsV1: async () => (await import('~/../../spec/supabase_js_v1.yml')).default,
specJsV2: async () => (await import('~/../../spec/supabase_js_v2.yml')).default,
specDartV0: async () => (await import('~/../../spec/supabase_dart_v0.yml')).default,
specDartV1: async () => (await import('~/../../spec/supabase_dart_v1.yml')).default,
specPythonV2: async () => (await import('~/../../spec/supabase_py_v2.yml')).default,
specCSharpV0: async () => (await import('~/../../spec/supabase_csharp_v0.yml')).default,
specSwiftV0: async () => (await import('~/../../spec/supabase_swift_v0.yml')).default,
}
export type RefIdOptions =
| 'reference_javascript_v1'
| 'reference_javascript_v2'
| 'reference_dart_v0'
| 'reference_dart_v1'
| 'reference_csharp_v0'
| 'reference_python_v2'
| 'reference_swift_v0'
| 'reference_cli'
| 'reference_api'
| 'reference_self_hosting_auth'
| 'reference_self_hosting_storage'
| 'reference_self_hosting_realtime'
| 'reference_self_hosting_analytics'
| 'reference_self_hosting_functions'
const commonSectionImports = {
apiCommonSections: async () => (await import('~/../../spec/common-api-sections.json')).default,
cliCommonSections: async () => (await import('~/../../spec/common-cli-sections.json')).default,
libCommonSections: async () =>
(await import('~/../../spec/common-client-libs-sections.json')).default,
analyticsServerCommonSections: async () =>
(await import('~/../../spec/common-self-hosting-analytics-sections.json')).default,
authServerCommonSections: async () =>
(await import('~/../../spec/common-self-hosting-auth-sections.json')).default,
functionsServerCommonSections: async () =>
(await import('~/../../spec/common-self-hosting-functions-sections.json')).default,
realtimeServerCommonSections: async () =>
(await import('~/../../spec/common-self-hosting-realtime-sections.json')).default,
storageServerCommonSections: async () =>
(await import('~/../../spec/common-self-hosting-storage-sections.json')).default,
}
export type RefKeyOptions =
| 'javascript'
| 'dart'
| 'csharp'
| 'python'
| 'swift'
| 'cli'
| 'api'
| 'self-hosting-auth'
| 'self-hosting-storage'
| 'self-hosting-realtime'
| 'self-hosting-analytics'
| 'self-hosting-functions'
interface BaseMenu {
id: string
path: string
type: string
}
interface HomeMenu extends BaseMenu {
type: 'home'
}
interface GuideMenu extends BaseMenu {
type: 'guide'
}
interface ReferenceMenu extends BaseMenu {
type: 'reference'
commonSectionImport: () => Promise<ICommonBase[]>
specImport?: () => Promise<Json>
}
type Menu = HomeMenu | GuideMenu | ReferenceMenu
const menus: Menu[] = [
{
id: 'home',
path: '/',
type: 'home',
},
{
id: 'gettingstarted',
path: '/guides/getting-started',
type: 'guide',
},
{
id: 'database',
path: '/guides/database',
type: 'guide',
},
{
id: 'api',
path: '/guides/api',
type: 'guide',
},
{
id: 'auth',
path: '/guides/auth',
type: 'guide',
},
{
id: 'functions',
path: '/guides/functions',
type: 'guide',
},
{
id: 'realtime',
path: '/guides/realtime',
type: 'guide',
},
{
id: 'storage',
path: '/guides/storage',
type: 'guide',
},
{
id: 'platform',
path: '/guides/platform',
type: 'guide',
},
{
id: 'resources',
path: '/guides/resources',
type: 'guide',
},
{
id: 'self_hosting',
path: '/guides/self-hosting',
type: 'guide',
},
{
id: 'integrations',
path: '/guides/integrations',
type: 'guide',
},
{
id: 'supabase_cli',
// TODO: Add path '/reference/cli/config'
path: '/guides/cli',
type: 'guide',
},
{
id: 'reference_javascript_v1',
path: '/reference/javascript/v1',
commonSectionImport: commonSectionImports.libCommonSections,
specImport: specImports.specJsV1,
type: 'reference',
},
{
id: 'reference_javascript_v2',
path: '/reference/javascript',
commonSectionImport: commonSectionImports.libCommonSections,
specImport: specImports.specJsV2,
type: 'reference',
},
{
id: 'reference_dart_v0',
path: '/reference/dart/v0',
commonSectionImport: commonSectionImports.libCommonSections,
specImport: specImports.specDartV0,
type: 'reference',
},
{
id: 'reference_dart_v1',
path: '/reference/dart',
commonSectionImport: commonSectionImports.libCommonSections,
specImport: specImports.specDartV1,
type: 'reference',
},
{
id: 'reference_csharp_v0',
path: '/reference/csharp',
commonSectionImport: commonSectionImports.libCommonSections,
specImport: specImports.specCSharpV0,
type: 'reference',
},
{
id: 'reference_python_v2',
path: '/reference/python',
commonSectionImport: commonSectionImports.libCommonSections,
specImport: specImports.specPythonV2,
type: 'reference',
},
{
id: 'reference_swift_v0',
path: '/reference/swift',
commonSectionImport: commonSectionImports.libCommonSections,
specImport: specImports.specSwiftV0,
type: 'reference',
},
{
id: 'reference_cli',
path: '/reference/cli',
commonSectionImport: commonSectionImports.cliCommonSections,
type: 'reference',
},
{
id: 'reference_api',
path: '/reference/api',
commonSectionImport: commonSectionImports.apiCommonSections,
type: 'reference',
},
{
id: 'reference_self_hosting_auth',
path: '/reference/self-hosting-auth',
commonSectionImport: commonSectionImports.authServerCommonSections,
type: 'reference',
},
{
id: 'reference_self_hosting_storage',
path: '/reference/self-hosting-storage',
commonSectionImport: commonSectionImports.storageServerCommonSections,
type: 'reference',
},
{
id: 'reference_self_hosting_realtime',
path: '/reference/self-hosting-realtime',
commonSectionImport: commonSectionImports.realtimeServerCommonSections,
type: 'reference',
},
{
id: 'reference_self_hosting_analytics',
path: '/reference/self-hosting-analytics',
commonSectionImport: commonSectionImports.analyticsServerCommonSections,
type: 'reference',
},
{
id: 'reference_self_hosting_functions',
path: '/reference/self-hosting-functions',
commonSectionImport: commonSectionImports.functionsServerCommonSections,
type: 'reference',
},
]
function getMenuById(id: string) {
return menus.find((menu) => menu.id === id)
}
function getMenuByUrl(basePath: string, url: string) {
// If multiple matches, choose the menu with the longest path
const [menu] = menus
.filter(({ path }) => url.startsWith(`${basePath}${path}`))
.sort((a, b) => b.path.length - a.path.length)
return menu
}
function getMenuElement(menu: Menu) {
const menuType = menu.type
switch (menuType) {
case 'home':
return <NavigationMenuHome active />
case 'guide':
return <NavigationMenuGuideList id={menu.id} active />
case 'reference':
return (
<NavigationMenuRefList
id={menu.id}
basePath={menu.path}
commonSectionsImport={menu.commonSectionImport}
specImport={menu.specImport}
/>
)
default:
throw new Error(`Unknown menu type '${menuType}'`)
}
}
const NavigationMenu = () => {
const router = useRouter()
function handleRouteChange(url: string) {
switch (url) {
case `/docs`:
menuState.setMenuLevelId('home')
break
case url.includes(`/docs/guides/getting-started`) && url:
menuState.setMenuLevelId('gettingstarted')
break
case url.includes(`/docs/guides/database`) && url:
menuState.setMenuLevelId('database')
break
case url.includes(`/docs/guides/api`) && url:
menuState.setMenuLevelId('api')
break
case url.includes(`/docs/guides/auth`) && url:
menuState.setMenuLevelId('auth')
break
case url.includes(`/docs/guides/functions`) && url:
menuState.setMenuLevelId('functions')
break
case url.includes(`/docs/guides/realtime`) && url:
menuState.setMenuLevelId('realtime')
break
case url.includes(`/docs/guides/storage`) && url:
menuState.setMenuLevelId('storage')
break
case url.includes(`/docs/guides/platform`) && url:
menuState.setMenuLevelId('platform')
break
case url.includes(`/docs/guides/resources`) && url:
menuState.setMenuLevelId('resources')
break
case url.includes(`/docs/guides/self-hosting`) && url:
menuState.setMenuLevelId('self_hosting')
break
case url.includes(`/docs/guides/integrations`) && url:
menuState.setMenuLevelId('integrations')
break
case url.includes(`/docs/guides/cli`) && url:
menuState.setMenuLevelId('supabase_cli')
break
// JS v1
case url.includes(`/docs/reference/javascript/v1`) && url:
menuState.setMenuLevelId('reference_javascript_v1')
break
// JS v2 (latest)
case url.includes(`/docs/reference/javascript`) && url:
menuState.setMenuLevelId('reference_javascript_v2')
break
// dart v0
case url.includes(`/docs/reference/dart/v0`) && url:
menuState.setMenuLevelId('reference_dart_v0')
break
// dart v1 (latest)
case url.includes(`/docs/reference/dart`) && url:
menuState.setMenuLevelId('reference_dart_v1')
break
// C# v0 (latest)
case url.includes(`/docs/reference/csharp`) && url:
menuState.setMenuLevelId('reference_csharp_v0')
break
// python v2 (latest)
case url.includes(`/docs/reference/python`) && url:
menuState.setMenuLevelId('reference_python_v2')
break
// swift v1 (latest)
case url.includes(`/docs/reference/swift`) && url:
menuState.setMenuLevelId('reference_swift_v0')
break
case url.includes(`/docs/reference/cli/config`) && url:
menuState.setMenuLevelId('supabase_cli')
break
case url.includes(`/docs/reference/cli`) && url:
menuState.setMenuLevelId('reference_cli')
break
case url.includes(`/docs/reference/api`) && url:
menuState.setMenuLevelId('reference_api')
break
case url.includes(`/docs/reference/self-hosting-auth`) && url:
menuState.setMenuLevelId('reference_self_hosting_auth')
break
case url.includes(`/docs/reference/self-hosting-storage`) && url:
menuState.setMenuLevelId('reference_self_hosting_storage')
break
case url.includes(`/docs/reference/self-hosting-realtime`) && url:
menuState.setMenuLevelId('reference_self_hosting_realtime')
break
case url.includes(`/docs/reference/self-hosting-analytics`) && url:
menuState.setMenuLevelId('reference_self_hosting_analytics')
break
case url.includes(`/docs/reference/self-hosting-functions`) && url:
menuState.setMenuLevelId('reference_self_hosting_functions')
break
default:
break
const menu = getMenuByUrl(router.basePath, url)
if (menu) {
menuState.setMenuLevelId(menu.id)
}
}
@@ -187,168 +271,9 @@ const NavigationMenu = () => {
}, [router.events])
const level = useMenuLevelId()
const menu = getMenuById(level)
const isHomeActive = 'home' === level
const isGettingStartedActive = 'gettingstarted' === level
const isDatabaseActive = 'database' === level
const isApiActive = 'api' === level
const isAuthActive = 'auth' === level
const isFunctionsActive = 'functions' === level
const isRealtimeActive = 'realtime' === level
const isStorageActive = 'storage' === level
const issupabase_cliActive = 'supabase_cli' === level
const isPlatformActive = 'platform' === level
const isResourcesActive = 'resources' === level
const isSelfHosting = 'self_hosting' === level
const isIntegrationsActive = 'integrations' === level
const isReferenceActive = 'reference' === level
const isReference_Javascript_V1 = 'reference_javascript_v1' === level
const isReference_Javascript_V2 = 'reference_javascript_v2' === level
const isReference_Dart_V0 = 'reference_dart_v0' === level
const isReference_Dart_V1 = 'reference_dart_v1' === level
const isReference_Csharp_V0 = 'reference_csharp_v0' === level
const isReference_Python_V2 = 'reference_python_v2' === level
const isReference_Swift_V0 = 'reference_swift_v0' === level
const isReference_Cli = 'reference_cli' === level
const isReference_Api = 'reference_api' === level
const isReference_Self_Hosting_Auth = 'reference_self_hosting_auth' === level
const isReference_Self_Hosting_Storage = 'reference_self_hosting_storage' === level
const isReference_Self_Hosting_Realtime = 'reference_self_hosting_realtime' === level
const isReference_Self_Hosting_Analytics = 'reference_self_hosting_analytics' === level
const isReference_Self_Hosting_Functions = 'reference_self_hosting_functions' === level
return (
<div className={['flex relative', 'justify-center lg:justify-start'].join(' ')}>
{/* // main menu */}
<NavigationMenuHome active={isHomeActive} />
<NavigationMenuGuideList id={'gettingstarted'} active={isGettingStartedActive} />
<NavigationMenuGuideList id={'database'} active={isDatabaseActive} />
<NavigationMenuGuideList id={'api'} active={isApiActive} />
<NavigationMenuGuideList id={'auth'} active={isAuthActive} />
<NavigationMenuGuideList id={'functions'} active={isFunctionsActive} />
<NavigationMenuGuideList
id={'realtime'}
active={isRealtimeActive}
value={['/guides/realtime/extensions']}
/>
<NavigationMenuGuideList id={'storage'} active={isStorageActive} />
<NavigationMenuGuideList id={'supabase_cli'} active={issupabase_cliActive} />
<NavigationMenuGuideList id={'platform'} active={isPlatformActive} />
<NavigationMenuGuideList id={'resources'} active={isResourcesActive} />
<NavigationMenuGuideList id={'self_hosting'} active={isSelfHosting} />
<NavigationMenuGuideList id={'integrations'} active={isIntegrationsActive} />
<NavigationMenuGuideList id={'reference'} active={isReferenceActive} />
{/* // Client Libs */}
<NavigationMenuRefList
key={'reference-js-menu-v1'}
id={'reference_javascript_v1'}
active={isReference_Javascript_V1}
commonSections={libCommonSections}
lib="javascript"
spec={spec_js_v1}
/>
<NavigationMenuRefList
key={'reference-js-menu'}
id={'reference_javascript_v2'}
active={isReference_Javascript_V2}
commonSections={libCommonSections}
lib="javascript"
spec={spec_js_v2}
/>
<NavigationMenuRefList
key={'reference-dart-menu'}
id={'reference_dart_v0'}
active={isReference_Dart_V0}
commonSections={libCommonSections}
lib="dart"
spec={spec_dart_v0}
/>
<NavigationMenuRefList
key={'reference-dart-menu-v1'}
id={'reference_dart_v1'}
active={isReference_Dart_V1}
commonSections={libCommonSections}
lib="dart"
spec={spec_dart_v1}
/>
<NavigationMenuRefList
key={'reference-csharp-menu-v0'}
id={'reference_csharp_v0'}
active={isReference_Csharp_V0}
commonSections={libCommonSections}
lib="csharp"
spec={spec_csharp_v0}
/>
<NavigationMenuRefList
key={'reference-swift-menu-v1'}
id={'reference_swift_v0'}
active={isReference_Swift_V0}
commonSections={libCommonSections}
lib="swift"
spec={spec_swift_v0}
/>
<NavigationMenuRefList
key={'reference-python-menu-v2'}
id={'reference_python_v2'}
active={isReference_Python_V2}
commonSections={libCommonSections}
lib="python"
spec={spec_python_v2}
/>
{/* // Tools */}
<NavigationMenuRefList
key={'reference-cli-menu'}
id={'reference_cli'}
active={isReference_Cli}
commonSections={cliCommonSections}
lib="cli"
/>
<NavigationMenuRefList
key={'reference-api-menu'}
id={'reference_api'}
active={isReference_Api}
commonSections={apiCommonSections}
lib="api"
/>
{/* // Self Hosting Server */}
<NavigationMenuRefList
key={'reference-self-hosting-auth-menu'}
id={'reference_self_hosting_auth'}
active={isReference_Self_Hosting_Auth}
commonSections={authServerCommonSections}
lib="self-hosting-auth"
/>
<NavigationMenuRefList
key={'reference-self-hosting-storage-menu'}
id={'reference_self_hosting_storage'}
active={isReference_Self_Hosting_Storage}
commonSections={storageServerCommonSections}
lib="self-hosting-storage"
/>
<NavigationMenuRefList
key={'reference-self-hosting-realtime-menu'}
id={'reference_self_hosting_realtime'}
active={isReference_Self_Hosting_Realtime}
commonSections={realtimeServerCommonSections}
lib="self-hosting-realtime"
/>
<NavigationMenuRefList
key={'reference-self-hosting-analytics-menu'}
id={'reference_self_hosting_analytics'}
active={isReference_Self_Hosting_Analytics}
commonSections={analyticsServerCommonSections}
lib="self-hosting-analytics"
/>
<NavigationMenuRefList
key={'reference-self-hosting-functions-menu'}
id={'reference_self_hosting_functions'}
active={isReference_Self_Hosting_Functions}
commonSections={functionsServerCommonSections}
lib="self-hosting-functions"
/>
</div>
)
return getMenuElement(menu)
}
export default memo(NavigationMenu)

View File

@@ -1,6 +1,31 @@
import { ICommonItem } from '~/components/reference/Reference.types'
// check if the link is allowed to be displayed
export function isFuncNotInLibraryOrVersion(id, type, allowedKeys) {
if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') {
return true
}
}
/**
* Recursively filter common sections and their sub items based on
* what is available in their spec
*/
export function deepFilterSections(sections: ICommonItem[], specFunctionIds: string[]) {
return sections
.filter(
(section) =>
section.type === 'category' ||
section.type === 'markdown' ||
specFunctionIds.includes(section.id)
)
.map((section) => {
if ('items' in section) {
return {
...section,
items: deepFilterSections(section.items, specFunctionIds),
}
}
return section
})
}

View File

@@ -1,31 +1,51 @@
// import apiCommonSections from '~/../../spec/common-client-libs-sections.json'
import { RefIdOptions, RefKeyOptions } from './NavigationMenu'
import { Json } from '~/types'
import NavigationMenuRefListItems from './NavigationMenuRefListItems'
import React from 'react'
import React, { useEffect, useState } from 'react'
import { ICommonBase, ICommonItem } from '~/components/reference/Reference.types'
interface INavigationMenuRefList {
id: RefIdOptions
lib: RefKeyOptions
commonSections: any[] // to do type up
// the keys of menu items that are allowed to be shown on the side menu
// if undefined, we show all the menu items
allowedClientKeys?: string[]
active: boolean
spec?: any
interface NavigationMenuRefListProps {
id: string
basePath: string
commonSectionsImport: () => Promise<ICommonBase[]>
specImport?: () => Promise<Json>
}
const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
const NavigationMenuRefList = ({
id,
lib,
commonSections,
basePath,
commonSectionsImport,
specImport,
}: NavigationMenuRefListProps) => {
const [commonSections, setCommonSections] = useState<ICommonItem[]>()
const [spec, setSpec] = useState<Json>()
active,
spec,
}) => {
if (!active) {
// Dynamic imports allow for code splitting which
// dramatically reduces app bundle size
useEffect(() => {
async function fetchCommonSections() {
const commonSections = await commonSectionsImport()
setCommonSections(commonSections as ICommonItem[])
}
fetchCommonSections()
}, [commonSectionsImport])
useEffect(() => {
if (!specImport) {
return
}
async function fetchSpec() {
const spec = await specImport()
setSpec(spec)
}
fetchSpec()
}, [specImport])
if (!commonSections) {
return null
}
if (specImport && !spec) {
return null
}
@@ -34,19 +54,13 @@ const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
})
return (
<div
className={[
'transition-all duration-150 ease-out',
// enabled
active && 'opacity-100 ml-0 delay-150 h-auto',
// move menu back to margin-left
// level === 'home' && 'ml-12',
// disabled
// level !== 'home' && level !== id ? '-ml-8' : '',
!active ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '',
].join(' ')}
>
<NavigationMenuRefListItems id={id} lib={lib} commonSections={filteredSections} spec={spec} />
<div className="transition-all duration-150 ease-out opacity-100 ml-0 delay-150 h-auto">
<NavigationMenuRefListItems
id={id}
commonSections={filteredSections}
spec={spec}
basePath={basePath}
/>
</div>
)
}

View File

@@ -4,17 +4,15 @@ import { useRouter } from 'next/router'
import { IconChevronLeft } from 'ui'
import * as NavItems from './NavigationMenu.constants'
import { find } from 'lodash'
import Image from 'next/image'
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import RevVersionDropdown from '~/components/RefVersionDropdown'
import { useMenuActiveRefId } from '~/hooks/useMenuState'
import { RefIdOptions, RefKeyOptions } from './NavigationMenu'
import React, { Fragment } from 'react'
import { generateAllowedClientLibKeys } from '~/lib/refGenerator/helpers'
import { isFuncNotInLibraryOrVersion } from './NavigationMenu.utils'
import { ICommonItem, ICommonSection } from '~/components/reference/Reference.types'
import { deepFilterSections } from './NavigationMenu.utils'
const HeaderImage = React.memo(function HeaderImage(props: any) {
const router = useRouter()
@@ -40,38 +38,29 @@ const HeaderLink = React.memo(function HeaderLink(props: any) {
)
})
const FunctionLink = React.memo(function FunctionLink({
title,
id,
icon,
library,
slug,
}: {
interface FunctionLinkProps {
title: string
name?: string
id: string
icon?: string
product?: string
library: string
basePath: string
slug: string
}) {
}
const FunctionLink = React.memo(function FunctionLink({
title,
id,
icon,
basePath,
slug,
}: FunctionLinkProps) {
const router = useRouter()
const activeAccordianItem = useMenuActiveRefId()
const activeAccordionItem = useMenuActiveRefId()
// check if we're on a versioned page
let version = ''
if (router.asPath.includes('v1')) {
version = 'v1'
}
if (router.asPath.includes('v0')) {
version = 'v0'
}
const active = activeAccordianItem === id
const active = activeAccordionItem === id
return (
<li key={id} className="function-link-item">
<Link href={`/reference/${library}/${version ? version + '/' : ''}${slug}`} passHref>
<li className="function-link-item">
<Link href={`${basePath}/${slug}`} passHref>
<a
className={[
'cursor-pointer transition text-sm hover:text-brand-900 flex gap-3',
@@ -86,63 +75,43 @@ const FunctionLink = React.memo(function FunctionLink({
)
})
const RenderLink = React.memo(function RenderLink(props: any) {
const activeAccordianItem = useMenuActiveRefId()
let active = false
export interface RenderLinkProps {
section: ICommonSection
basePath: string
}
const isFilter = props.filterIds?.includes(activeAccordianItem)
const isModifier = props.modifierIds?.includes(activeAccordianItem)
const isAuthServer = props.authServerIds?.includes(activeAccordianItem)
const RenderLink = React.memo(function RenderLink({ section, basePath }: RenderLinkProps) {
const activeAccordionItem = useMenuActiveRefId()
if (
(isFilter && props.id === 'using-filters') ||
(activeAccordianItem === 'using-filters' && props.id === 'using-filters')
) {
active = true
} else if (
(isModifier && props.id === 'using-modifiers') ||
(activeAccordianItem === 'using-modifiers' && props.id === 'using-modifiers')
) {
active = true
} else if (
(isAuthServer && props.id === 'admin-api') ||
(activeAccordianItem === 'admin-api' && props.id === 'admin-api')
) {
active = true
} else {
active = false
if (!('items' in section)) {
return (
<FunctionLink title={section.title} id={section.id} slug={section.slug} basePath={basePath} />
)
}
let active = section.items.some((item) => item.id === activeAccordionItem)
return (
<Accordion.Root
collapsible
key={props.id + '-accordian-root-for-func-' + props.index}
type="single"
value={active ? props.id : ''}
>
<Accordion.Item key={props.id + '-accordian-item'} value={props.id}>
<FunctionLink library={props.lib} title={props.title} id={props.id} slug={props.slug} />
<Accordion.Content
key={props.id + '-sub-items-accordion-container'}
className="transition data-open:animate-slide-down data-closed:animate-slide-up ml-2"
>
{props.items &&
props.items
.filter((item) => props.allowedKeys.includes(item.id))
.map((item) => {
return (
<FunctionLink
key={item.title}
library={props.lib}
title={item.title}
id={item.id}
slug={item.slug}
/>
)
})}
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
<>
<FunctionLink title={section.title} id={section.id} slug={section.slug} basePath={basePath} />
<Accordion.Root collapsible type="single" value={active ? section.id : ''}>
<Accordion.Item value={section.id}>
<Accordion.Content className="transition data-open:animate-slide-down data-closed:animate-slide-up ml-2">
{section.items.map((item) => {
return (
<FunctionLink
key={item.id}
title={item.title}
id={item.id}
slug={item.slug}
basePath={basePath}
/>
)
})}
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
</>
)
})
@@ -158,67 +127,25 @@ const Divider = () => {
return <div className="h-px w-full bg-blackA-300 dark:bg-whiteA-300 my-3"></div>
}
interface INavigationMenuRefList {
id: RefIdOptions
lib: RefKeyOptions
commonSections: any[] // to do type up
// the keys of menu items that are allowed to be shown on the side menu
// if undefined, we show all the menu items
allowedClientKeys?: string[]
interface NavigationMenuRefListItemsProps {
id: string
basePath: string
commonSections: ICommonItem[]
spec?: any
}
const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, spec }) => {
const allowedClientKeys = spec ? generateAllowedClientLibKeys(commonSections, spec) : undefined
let sections = commonSections
const allowedKeys = allowedClientKeys
if (!sections) console.error('no common sections imported')
const NavigationMenuRefListItems = ({
id,
basePath,
commonSections,
spec,
}: NavigationMenuRefListItemsProps) => {
const menu = NavItems[id]
const databaseFunctions = find(sections, { title: 'Database' })
? find(sections, { title: 'Database' }).items
: []
const authFunctions = find(sections, { title: 'Auth' })
? find(sections, { title: 'Auth' }).items
: []
const filterIds =
databaseFunctions.length > 0
? find(databaseFunctions, {
id: 'using-filters',
}) &&
find(databaseFunctions, {
id: 'using-filters',
})
.items.filter((x) => allowedKeys.includes(x.id))
.map((x) => x.id)
: []
const modifierIds =
databaseFunctions.length > 0
? find(databaseFunctions, {
id: 'using-modifiers',
}) &&
find(databaseFunctions, {
id: 'using-modifiers',
})
.items.filter((x) => allowedKeys.includes(x.id))
.map((x) => x.id)
: []
const authServerIds =
databaseFunctions.length > 0
? find(authFunctions, {
id: 'admin-api',
}) &&
find(authFunctions, {
id: 'admin-api',
}).items.map((x) => x.id)
: []
const specFunctionIds = spec?.functions.map(({ id }) => id)
const filteredSections = spec
? deepFilterSections(commonSections, specFunctionIds)
: commonSections
return (
<div className={'w-full flex flex-col gap-0 sticky top-8'}>
@@ -237,61 +164,26 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
<span>Back to Main Menu</span>
</a>
</Link>
<div className="flex items-center gap-3 my-3">
<HeaderImage icon={menu.icon} />
<HeaderLink title={menu.title} url={menu.url} id={id} />
<RevVersionDropdown />
</div>
<ul className="function-link-list flex flex-col gap-1">
{sections.map((fn: any, fnIndex) => {
// run allow check
if (isFuncNotInLibraryOrVersion(fn.id, fn.type, allowedKeys)) {
return <Fragment key={fn.id}></Fragment>
}
// handle subtitles with subitems
return fn.id ? (
<Fragment key={fn.id}>
<RenderLink {...fn} lib={lib} />
{fn.items &&
fn.items.map((item) => (
<RenderLink
{...item}
library={menu.title}
index={fnIndex}
modifierIds={modifierIds}
filterIds={filterIds}
authServerIds={authServerIds}
lib={lib}
allowedKeys={allowedKeys}
/>
))}
</Fragment>
) : (
<Fragment key={fn.title}>
<Divider />
<SideMenuTitle title={fn.title} />
{fn.items &&
fn.items.map((item, i) => {
// run allow check
if (isFuncNotInLibraryOrVersion(item.id, item.type, allowedKeys))
return <Fragment key={item.id + i}></Fragment>
return (
<RenderLink
{...item}
key={item.id + i}
library={menu.title}
index={fnIndex}
modifierIds={modifierIds}
filterIds={filterIds}
authServerIds={authServerIds}
lib={lib}
allowedKeys={allowedKeys}
/>
)
})}
{filteredSections.map((section) => {
return (
<Fragment key={section.title}>
{section.type === 'category' ? (
<>
<Divider />
<SideMenuTitle title={section.title} />
{section.items.map((item) => (
<RenderLink section={item} basePath={basePath} />
))}
</>
) : (
<RenderLink section={section} basePath={basePath} />
)}
</Fragment>
)
})}
@@ -300,4 +192,4 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
)
}
export default React.memo(Content)
export default React.memo(NavigationMenuRefListItems)

View File

@@ -8,10 +8,10 @@ import RefFunctionSection from '~/components/reference/RefFunctionSection'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
import ApiOperationSection from './ApiOperationSection'
import CliCommandSection from './CLICommandSection'
import { IAPISpec, ICommonFunc, IRefStaticDoc, ISpec, TypeSpec } from './Reference.types'
import { IAPISpec, ICommonSection, IRefStaticDoc, ISpec, TypeSpec } from './Reference.types'
interface RefSectionHandlerProps {
sections: ICommonFunc[]
sections: ICommonSection[]
spec?: ISpec | IAPISpec
typeSpec?: TypeSpec
pageProps: { docs: IRefStaticDoc[] }
@@ -59,46 +59,48 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => {
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head>
<RefSubLayout>
{props.sections.map((x, i) => {
switch (x.type) {
{props.sections.map((section, i) => {
const sectionType = section.type
switch (sectionType) {
case 'markdown':
const markdownData = props.pageProps.docs.find((doc) => doc.id === x.id)
const markdownData = props.pageProps.docs.find((doc) => doc.id === section.id)
return <RefEducationSection key={x.id + i} item={x} markdownContent={markdownData} />
break
return (
<RefEducationSection
key={section.id + i}
item={section}
markdownContent={markdownData}
/>
)
case 'function':
return (
<RefFunctionSection
key={x.id + i}
funcData={x}
commonFuncData={x}
key={section.id + i}
funcData={section}
commonFuncData={section}
spec={props.spec}
typeSpec={props.typeSpec}
/>
)
case 'cli-command':
return <CliCommandSection key={x.id + i} funcData={x} commonFuncData={x} />
break
return (
<CliCommandSection
key={section.id + i}
funcData={section}
commonFuncData={section}
/>
)
case 'operation':
return (
<ApiOperationSection
key={x.id + i}
funcData={x}
commonFuncData={x}
key={section.id + i}
funcData={section}
commonFuncData={section}
spec={props.spec}
/>
)
default:
return (
<RefFunctionSection
key={x.id + i}
funcData={x}
commonFuncData={x}
spec={props.spec}
typeSpec={props.typeSpec}
/>
)
break
throw new Error(`Unknown common section type '${sectionType}'`)
}
})}
</RefSubLayout>

View File

@@ -33,19 +33,61 @@ export interface IFunctionDefinition {
examples?: []
}
export interface ICommonFunc {
id?: string
export interface ICommonBase {
type: string
title: string
slug?: string
product?: string
type?: string
parent?: string
items?: ICommonFunc[]
summary?: string
}
export interface ICommonBaseSection extends ICommonBase {
id: string
slug: string
excludes?: string[]
}
export interface ICommonCategory extends ICommonBase {
type: 'category'
items: ICommonSection[]
excludes?: string[]
}
export interface ICommonMarkdown extends ICommonBaseSection {
type: 'markdown'
}
export interface ICommonFunctionGroup extends ICommonBaseSection {
type: 'function'
isFunc: false
product: string
items: ICommonFunction[]
}
export interface ICommonFunction extends ICommonBaseSection {
type: 'function'
product: string
parent?: string
}
export interface ICommonCliCommand extends ICommonBaseSection {
type: 'cli-command'
}
export interface ICommonApiOperation extends ICommonBaseSection {
type: 'operation'
}
export type ICommonSection =
| ICommonMarkdown
| ICommonFunctionGroup
| ICommonFunction
| ICommonCliCommand
| ICommonApiOperation
export type ICommonItem = ICommonCategory | ICommonSection
export interface IRefFunctionSection {
funcData: any
commonFuncData: ICommonFunc
commonFuncData: ICommonFunction
spec: any
typeSpec?: TypeSpec
}

View File

@@ -4,10 +4,10 @@ import Link from 'next/link'
import NavigationMenu from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TopNavBarRef from '~/components/Navigation/NavigationMenu/TopNavBarRef'
import { memo, useEffect } from 'react'
import Head from 'next/head'
import { PropsWithChildren, memo } from 'react'
import Footer from '~/components/Navigation/Footer'
import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState'
import Head from 'next/head'
const levelsData = {
home: {
@@ -207,25 +207,6 @@ const MobileMenuBackdrop = memo(function MobileMenuBackdrop() {
)
})
const SideMenu = memo(function SideMenu() {
return (
<div
className={[
'transition-all ease-out duration-200',
'absolute left-0 right-0 h-screen',
'px-5 pl-5 py-16',
'top-[0px]',
'bg-scale-200',
// desktop styles
'lg:relative lg:top-0 lg:left-0 lg:pb-10 lg:px-10 lg:pt-0 lg:flex',
'lg:opacity-100 lg:visible',
].join(' ')}
>
<NavigationMenu />
</div>
)
})
const HeaderLogo = memo(function HeaderLogo() {
const { isDarkMode } = useTheme()
return (
@@ -311,13 +292,26 @@ const NavContainer = memo(function NavContainer() {
</div>
</div>
</div>
<SideMenu />
<div
className={[
'transition-all ease-out duration-200',
'absolute left-0 right-0 h-screen',
'px-5 pl-5 py-16',
'top-[0px]',
'bg-scale-200',
// desktop styles
'lg:relative lg:top-0 lg:left-0 lg:pb-10 lg:px-10 lg:pt-0 lg:flex',
'lg:opacity-100 lg:visible',
].join(' ')}
>
<NavigationMenu />
</div>
</div>
</div>
)
})
const SiteLayout = ({ children }) => {
const SiteLayout = ({ children }: PropsWithChildren<{}>) => {
return (
<>
<Head>

View File

@@ -1,6 +1,4 @@
// [Joshen] I think this can be done better, as its mostly used to choose what
import { ICommonFunc } from '../components/reference/Reference.types'
import { ICommonBase, ICommonItem, ICommonSection } from '../components/reference/Reference.types'
// menus to render in the SideBar.js (Ref Nav.constants.ts)
export function getPageType(asPath: string) {
@@ -36,18 +34,25 @@ export function getPageType(asPath: string) {
return page
}
export function flattenSections(sections: ICommonFunc[]) {
let a: ICommonFunc[] = []
for (let i = 0; i < sections.length; i++) {
if (sections[i].id) {
// only push a section that has an id
// these are reserved for sidebar subtitles
a.push(sections[i])
/**
* Flattens common sections recursively by their `items`.
*
* _Note:_ `sections` type set to `ICommonBase[]` instead of
* `ICommonItem[]` until TypeScript supports JSON imports as const:
* https://github.com/microsoft/TypeScript/issues/32063
*/
export function flattenSections(sections: ICommonBase[]): ICommonSection[] {
return sections.reduce<ICommonSection[]>((acc, section: ICommonItem) => {
// Flatten sub-items
if ('items' in section) {
let newSections = acc
if (section.type !== 'category') {
newSections.push(section)
}
return newSections.concat(flattenSections(section.items))
}
if (sections[i].items) {
// if there are subitems, loop through
a = a.concat(flattenSections(sections[i].items))
}
}
return a
return acc.concat(section)
}, [])
}

View File

@@ -3,6 +3,7 @@ import { TsDoc } from '../../generator/legacy/definitions'
import { values, mapValues } from 'lodash'
import { OpenAPIV3 } from 'openapi-types'
import { flattenSections } from '../helpers'
import { ICommonItem } from '~/components/reference/Reference.types'
export function extractTsDocNode(nodeToFind: string, definition: any) {
const nodePath = nodeToFind.split('.')
@@ -294,12 +295,13 @@ export const toArrayWithKey = (obj: object, keyAs: string) =>
})
)
export function generateAllowedClientLibKeys(sections, spec) {
/**
* Get a list of common section IDs that are available in this spec
*/
export function getAvailableSectionIds(sections: ICommonItem[], spec: any) {
// Filter parent sections first
const specIds = spec.functions.map((func) => {
return func.id
})
const specIds = spec.functions.map(({ id }) => id)
const newShape = flattenSections(sections).filter((section) => {
if (specIds.includes(section.id)) {

View File

@@ -3,7 +3,7 @@ import { readFile } from 'fs/promises'
import yaml from 'js-yaml'
import { OpenAPIV3 } from 'openapi-types'
import {
ICommonFunc,
ICommonFunction,
IFunctionDefinition,
ISpec,
} from '../../../components/reference/Reference.types'
@@ -29,7 +29,7 @@ export abstract class ReferenceSource<SpecSection> extends BaseSource {
const specContents = await readFile(this.specFilePath, 'utf8')
const refSectionsContents = await readFile(this.sectionsFilePath, 'utf8')
const refSections: ICommonFunc[] = JSON.parse(refSectionsContents)
const refSections: ICommonFunction[] = JSON.parse(refSectionsContents)
const flattenedRefSections = flattenSections(refSections)
const checksum = createHash('sha256')
@@ -69,7 +69,7 @@ export abstract class ReferenceSource<SpecSection> extends BaseSource {
abstract getSpecSections(specContents: string): SpecSection[]
abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection
abstract formatSection(specSection: SpecSection, refSection: ICommonFunc): string
abstract formatSection(specSection: SpecSection, refSection: ICommonFunction): string
}
export class OpenApiReferenceSource extends ReferenceSource<enrichedOperation> {
@@ -106,7 +106,7 @@ export class ClientLibReferenceSource extends ReferenceSource<IFunctionDefinitio
matchSpecSection(functionDefinitions: IFunctionDefinition[], id: string): IFunctionDefinition {
return functionDefinitions.find((functionDefinition) => functionDefinition.id === id)
}
formatSection(functionDefinition: IFunctionDefinition, refSection: ICommonFunc): string {
formatSection(functionDefinition: IFunctionDefinition, refSection: ICommonFunction): string {
const { title } = refSection
const { description, title: functionName } = functionDefinition

View File

@@ -6,6 +6,7 @@
"type": "markdown"
},
{
"type": "category",
"title": "Projects",
"items": [
{
@@ -35,6 +36,7 @@
]
},
{
"type": "category",
"title": "Secrets",
"items": [
{
@@ -58,6 +60,7 @@
]
},
{
"type": "category",
"title": "Typescript",
"items": [
{
@@ -69,6 +72,7 @@
]
},
{
"type": "category",
"title": "Functions",
"items": [
{
@@ -109,8 +113,8 @@
}
]
},
{
"type": "category",
"title": "Custom Hostname",
"items": [
{
@@ -146,6 +150,7 @@
]
},
{
"type": "category",
"title": "pgsodium",
"items": [
{
@@ -163,6 +168,7 @@
]
},
{
"type": "category",
"title": "Network",
"items": [
{

View File

@@ -14,6 +14,7 @@
"type": "markdown"
},
{
"type": "category",
"title": "General",
"items": [
{
@@ -55,6 +56,7 @@
]
},
{
"type": "category",
"title": "Testing",
"items": [
{
@@ -72,6 +74,7 @@
]
},
{
"type": "category",
"title": "Generate Types",
"items": [
{
@@ -95,6 +98,7 @@
]
},
{
"type": "category",
"title": "Database",
"items": [
{
@@ -148,6 +152,7 @@
]
},
{
"type": "category",
"title": "Migrations",
"items": [
{
@@ -177,6 +182,7 @@
]
},
{
"type": "category",
"title": "Projects",
"items": [
{
@@ -200,6 +206,7 @@
]
},
{
"type": "category",
"title": "Organizations",
"items": [
{
@@ -217,6 +224,7 @@
]
},
{
"type": "category",
"title": "Edge Functions",
"items": [
{
@@ -258,6 +266,7 @@
]
},
{
"type": "category",
"title": "Secrets",
"items": [
{
@@ -287,6 +296,7 @@
]
},
{
"type": "category",
"title": "Authentication",
"items": [
{
@@ -334,6 +344,7 @@
]
},
{
"type": "category",
"title": "Custom Domains",
"items": [
{
@@ -375,6 +386,7 @@
]
},
{
"type": "category",
"title": "Vanity Subdomains",
"items": [
{
@@ -410,6 +422,7 @@
]
},
{
"type": "category",
"title": "Network Bans",
"items": [
{
@@ -433,6 +446,7 @@
]
},
{
"type": "category",
"title": "Network Restrictions",
"items": [
{
@@ -456,6 +470,7 @@
]
},
{
"type": "category",
"title": "SSL Enforcement",
"items": [
{
@@ -479,6 +494,7 @@
]
},
{
"type": "category",
"title": "Autocompletion Scripts",
"items": [
{

View File

@@ -51,6 +51,7 @@
]
},
{
"type": "category",
"title": "Database",
"items": [
{
@@ -375,6 +376,7 @@
]
},
{
"type": "category",
"title": "Auth",
"items": [
{
@@ -688,6 +690,7 @@
]
},
{
"type": "category",
"title": "Functions",
"items": [
{
@@ -700,6 +703,7 @@
]
},
{
"type": "category",
"title": "Realtime",
"items": [
{
@@ -761,6 +765,7 @@
]
},
{
"type": "category",
"title": "Storage",
"items": [
{
@@ -892,6 +897,7 @@
]
},
{
"type": "category",
"title": "Misc",
"excludes": [
"reference_dart_v1",

View File

@@ -7,6 +7,7 @@
"type": "markdown"
},
{
"type": "category",
"title": "Management API",
"items": [
{

View File

@@ -7,6 +7,7 @@
"type": "markdown"
},
{
"type": "category",
"title": "Usage",
"items": [
{

View File

@@ -7,6 +7,7 @@
"type": "markdown"
},
{
"type": "category",
"title": "Usage",
"items": [
{