Skip to content

Commit

Permalink
Allow a category landing page for Copilot Cookbook (#53002)
Browse files Browse the repository at this point in the history
  • Loading branch information
hectorsector authored Nov 7, 2024
1 parent 90e0168 commit 2d14549
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 6 deletions.
62 changes: 62 additions & 0 deletions src/frame/components/context/CategoryLandingContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import pick from 'lodash/pick'
import { createContext, useContext } from 'react'
import { LearningTrack } from './ArticleContext'
import {
FeaturedLink,
getFeaturedLinksFromReq,
} from 'src/landings/components/ProductLandingContext'

export type TocItem = {
fullPath: string
title: string
intro?: string
childTocItems?: Array<{
fullPath: string
title: string
intro: string
}>
}

export type CategoryLandingContextT = {
title: string
intro: string
productCallout: string
permissions: string
tocItems: Array<TocItem>
variant?: 'compact' | 'expanded'
featuredLinks: Record<string, Array<FeaturedLink>>
renderedPage: string
currentLearningTrack?: LearningTrack
currentLayout: string
}

export const CategoryLandingContext = createContext<CategoryLandingContextT | null>(null)

export const useCategoryLandingContext = (): CategoryLandingContextT => {
const context = useContext(CategoryLandingContext)

if (!context) {
throw new Error(
'"useCategoryLandingContext" may only be used inside "CategoryLandingContext.Provider"',
)
}

return context
}

export const getCategoryLandingContextFromRequest = (req: any): CategoryLandingContextT => {
return {
title: req.context.page.title,
productCallout: req.context.page.product || '',
permissions: req.context.page.permissions || '',
intro: req.context.page.intro,
tocItems: (req.context.genericTocFlat || req.context.genericTocNested || []).map((obj: any) =>
pick(obj, ['fullPath', 'title', 'intro', 'childTocItems']),
),
variant: req.context.genericTocFlat ? 'expanded' : 'compact',
featuredLinks: getFeaturedLinksFromReq(req),
renderedPage: req.context.renderedPage,
currentLearningTrack: req.context.currentLearningTrack,
currentLayout: req.context.currentLayoutName,
}
}
1 change: 1 addition & 0 deletions src/frame/lib/frontmatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const layoutNames = [
'product-guides',
'release-notes',
'inline',
'category-landing',
false,
]

Expand Down
14 changes: 11 additions & 3 deletions src/frame/middleware/context/generic-toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import findPageInSiteTree from '@/frame/lib/find-page-in-site-tree.js'
export default async function genericToc(req: ExtendedRequest, res: Response, next: NextFunction) {
if (!req.context) throw new Error('request not contextualized')
if (!req.context.page) return next()
if (req.context.currentLayoutName !== 'default') return next()
if (
req.context.currentLayoutName !== 'default' &&
req.context.currentLayoutName !== 'category-landing'
)
return next()
// This middleware can only run on product, category, and map topics.
if (
req.context.page.documentType === 'homepage' ||
Expand Down Expand Up @@ -92,7 +96,7 @@ export default async function genericToc(req: ExtendedRequest, res: Response, ne
renderIntros = false
req.context.genericTocNested = await getTocItems(treePage, req.context, {
recurse: isRecursive,
renderIntros,
renderIntros: req.context.currentLayoutName === 'category-landing' ? true : false,
includeHidden,
})
}
Expand Down Expand Up @@ -127,7 +131,11 @@ async function getTocItems(node: Tree, context: Context, opts: Options): Promise
// Deliberately don't use `textOnly:true` here because we intend
// to display the intro, in a table of contents component,
// with the HTML (dangerouslySetInnerHTML).
intro = await page.renderProp('rawIntro', context)
intro = await page.renderProp(
'rawIntro',
context,
context.currentLayoutName === 'category-landing' ? { textOnly: true } : {},
)
}
}

Expand Down
67 changes: 67 additions & 0 deletions src/landings/components/CategoryLanding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useRouter } from 'next/router'
import cx from 'classnames'

import { useCategoryLandingContext } from 'src/frame/components/context/CategoryLandingContext'
import { DefaultLayout } from 'src/frame/components/DefaultLayout'
import { ArticleTitle } from 'src/frame/components/article/ArticleTitle'
import { Lead } from 'src/frame/components/ui/Lead'
import { ClientSideRedirects } from 'src/rest/components/ClientSideRedirects'
import { RestRedirect } from 'src/rest/components/RestRedirect'
import { Breadcrumbs } from 'src/frame/components/page-header/Breadcrumbs'

export const CategoryLanding = () => {
const router = useRouter()
const { title, intro, tocItems } = useCategoryLandingContext()

// tocItems contains directories and its children, we only want the child articles
const onlyFlatItems = tocItems.flatMap((item) => item.childTocItems || [])

return (
<DefaultLayout>
{router.route === '/[versionId]/rest/[category]' && <RestRedirect />}
{/* Doesn't matter *where* this is included because it will
never render anything. It always just return null. */}
<ClientSideRedirects />

<div className="container-xl px-3 px-md-6 my-4">
<div className={cx('d-none d-xl-block mt-3 mr-auto width-full')}>
<Breadcrumbs />
</div>
<ArticleTitle>{title}</ArticleTitle>

{intro && <Lead data-search="lead">{intro}</Lead>}

<h2 className="py-5">Spotlight</h2>
<div className="container-lg clearfix">
<div className="col-4 float-left border p-4">Spotlight 1</div>
<div className="col-4 float-left border p-4">Spotlight 2</div>
<div className="col-4 float-left border p-4">Spotlight 3</div>
</div>

<div className="pt-8">
<div className="py-5 clearfix border-bottom">
<div className="col-5 float-left p-3">
<h2>Explore {onlyFlatItems.length} prompt articles</h2>
</div>
<div className="col-3 float-left p-4">Searchbar</div>
<div className="col-1 float-left p-4">Category</div>
<div className="col-1 float-left p-4">Complexity</div>
<div className="col-1 float-left p-4">Industry</div>
<div className="col-1 float-left p-4">Reset</div>
</div>
<div className="clearfix gutter-md-spacious">
{/* TODO: replace with card components */}
{onlyFlatItems.map((item, index) => (
<div key={index} className="col-4 float-left p-4">
<div className="px-3 pb-3 border-bottom">
<div>{item.title}</div>
<div>{item.intro}</div>
</div>
</div>
))}
</div>
</div>
</div>
</DefaultLayout>
)
}
24 changes: 21 additions & 3 deletions src/landings/pages/product.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,17 @@ import { ArticlePage } from 'src/frame/components/article/ArticlePage'
import { ProductLanding } from 'src/landings/components/ProductLanding'
import { ProductGuides } from 'src/landings/components/ProductGuides'
import { TocLanding } from 'src/landings/components/TocLanding'
import { CategoryLanding } from 'src/landings/components/CategoryLanding'
import {
getTocLandingContextFromRequest,
TocLandingContext,
TocLandingContextT,
} from 'src/frame/components/context/TocLandingContext'
import {
getCategoryLandingContextFromRequest,
CategoryLandingContext,
CategoryLandingContextT,
} from 'src/frame/components/context/CategoryLandingContext'
import { useEffect } from 'react'

function initiateArticleScripts() {
Expand All @@ -54,13 +60,15 @@ type Props = {
productGuidesContext?: ProductGuidesContextT
tocLandingContext?: TocLandingContextT
articleContext?: ArticleContextT
categoryLandingContext?: CategoryLandingContextT
}
const GlobalPage = ({
mainContext,
productLandingContext,
productGuidesContext,
tocLandingContext,
articleContext,
categoryLandingContext,
}: Props) => {
const router = useRouter()

Expand All @@ -86,6 +94,12 @@ const GlobalPage = ({
<ProductGuides />
</ProductGuidesContext.Provider>
)
} else if (categoryLandingContext) {
content = (
<CategoryLandingContext.Provider value={categoryLandingContext}>
<CategoryLanding />
</CategoryLandingContext.Provider>
)
} else if (tocLandingContext) {
content = (
<TocLandingContext.Provider value={tocLandingContext}>
Expand Down Expand Up @@ -133,9 +147,13 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
props.productGuidesContext = getProductGuidesContextFromRequest(req)
additionalUINamespaces.push('product_guides')
} else if (relativePath?.endsWith('index.md')) {
props.tocLandingContext = getTocLandingContextFromRequest(req)
if (props.tocLandingContext.currentLearningTrack?.trackName) {
additionalUINamespaces.push('learning_track_nav')
if (currentLayoutName === 'category-landing') {
props.categoryLandingContext = getCategoryLandingContextFromRequest(req)
} else {
props.tocLandingContext = getTocLandingContextFromRequest(req)
if (props.tocLandingContext.currentLearningTrack?.trackName) {
additionalUINamespaces.push('learning_track_nav')
}
}
} else if (props.mainContext.page) {
// All articles that might have hover cards needs this
Expand Down

0 comments on commit 2d14549

Please sign in to comment.