Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip - playing with a keystatic-based examples explorer #9285

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/app/(site)/docs/examples-explorer/[...rest]/page-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use client'

import { type ExamplePageProps } from './page'
import { Markdoc } from '../../../../../components/Markdoc'
import { Heading } from '../../../../../components/docs/Heading'
import { type Tag } from '@markdoc/markdoc'

export default function PageClient (props: ExamplePageProps) {
const readmeContent = JSON.parse(props.readmeContent) as Tag

const tsCodeBlock = props.previewFile ? {
'$$mdtype': 'Tag',
name: 'CodeBlock',
attributes: {
content: props.previewFile.content,
language: 'typescript'
},
children: []
} : null

return (
<>
{readmeContent.children.map((child, i) => (
<Markdoc key={i} content={child} />
))}

{tsCodeBlock && (
<>
<Heading level={2} id="preview-file-content">
{props.previewFile?.fileName} Preview
</Heading>
<Markdoc content={tsCodeBlock} />
</>
)}

{props.relatedDocs && (
<>
<Heading level={2} id="related-docs">
Related Docs
</Heading>
<ul>
{props.relatedDocs.map((doc, i) => (
<li key={i}>
<a href={`/docs/${doc}`}>{doc}</a>
</li>
))}
</ul>
</>
)}
</>
)
}
92 changes: 92 additions & 0 deletions docs/app/(site)/docs/examples-explorer/[...rest]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { notFound } from 'next/navigation'
import { type Tag, transform } from '@markdoc/markdoc'
import fs from 'fs/promises'
import path from 'path'

import { reader } from '../../../../../keystatic/reader'
import { baseMarkdocConfig } from '../../../../../markdoc/config'
import { extractHeadings } from '../../../../../markdoc/headings'
import PageClient from './page-client'
import { ExamplesLayout } from '../../../../../components/examples/ExamplesLayout'

export type ExamplePageProps = {
readmeContent: string // Changed to string
previewFile?: { content: string, fileName: string }
relatedDocs?: string[]
}

type Params = { rest: string[] }

export default async function DocPage ({ params }: { params: Params }) {
try {
const doc = await reader.collections.examplesReadme.read(params.rest.join('/'), {
resolveLinkedFiles: true,
})
if (!doc) return notFound()

const transformedContent = transform(doc.README.node, baseMarkdocConfig) as Tag

const preppedContent: ExamplePageProps = {
readmeContent: JSON.stringify(transformedContent) // Serialize the content
}

const headings = extractHeadings(transformedContent).slice(1)

if (doc.previewFile?.discriminant === true && doc.previewFile.value?.fileName) {
try {
const dirPath = path.join(process.cwd(), 'examples', doc.exampleSlug)
const tsFilePath = path.join(dirPath, doc.previewFile.value.fileName)
const fileContent = await fs.readFile(tsFilePath, 'utf-8')
preppedContent.previewFile = {
content: fileContent,
fileName: doc.previewFile.value.fileName
}
headings.push({ depth: 2, id: 'preview-file-content', label: `${doc.previewFile.value.fileName} Preview` })
} catch (error) {
console.error(`Error reading preview file: ${error}`)
}
}

if (doc.relatedDocs && doc.relatedDocs.length > 0) {
headings.push({ depth: 2, id: 'related-docs', label: 'Related Docs' })
preppedContent.relatedDocs = doc.relatedDocs
}

return (
<ExamplesLayout headings={headings} editPath={`docs/${params.rest.join('/')}.md`}>
<PageClient {...preppedContent} />
</ExamplesLayout>
)
} catch (error) {
console.error(`Error in ExamplePage: ${error}`)
return notFound()
}
}

// Dynamic SEO page metadata
export async function generateMetadata ({ params }: { params: Params }) {
try {
const doc = await reader.collections.docs.read(params.rest.join('/'))
return {
title: doc?.title ? `${doc.title} - Keystone 6 Examples` : 'Keystone 6 Documentation',
description: doc?.description,
}
} catch (error) {
console.error(`Error generating metadata: ${error}`)
return {
title: 'Keystone 6 Documentation',
description: 'An error occurred while loading this page',
}
}
}

// Static HTML page generation for each document page
export async function generateStaticParams () {
try {
const pages = await reader.collections.docs.list()
return pages.map((page) => ({ rest: page.split('/') }))
} catch (error) {
console.error(`Error generating static params: ${error}`)
return []
}
}
97 changes: 97 additions & 0 deletions docs/app/(site)/docs/examples-explorer/page-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/** @jsxImportSource @emotion/react */

'use client'

import { type Tag } from '@markdoc/markdoc'

import { type GroupedExamples } from './page'
import { GitHubExamplesCTA } from '../../../../components/docs/GitHubExamplesCTA'
import { Type } from '../../../../components/primitives/Type'
import { FeaturedCard, SplitCardContainer } from '../../../../components/docs/FeaturedCard'
import { useMediaQuery } from '../../../../lib/media'

export default function Docs ({
standaloneExamples,
endToEndExamples,
deploymentExamples,
}: GroupedExamples) {
return (
<>
<Type as="h1" look="heading64">
Examples
</Type>

<Type as="p" look="body18" margin="1.25rem 0 1.5rem 0">
A growing collection of projects you can run locally to learn more about Keystone’s
capabilities. Each example includes documentation explaining the how and why. Use them as a
reference for best practice, and a jumping off point when adding features to your own
Keystone project.
</Type>

<GitHubExamplesCTA />

<Type as="h2" look="heading30" margin="2rem 0 1rem 0" id="standalone-examples">
Standalone Examples
</Type>

<Type as="p" look="body18" margin="1.25rem 0 1.5rem 0">
Standalone examples demonstrate how a particular feature works or how to solve a problem
with Keystone.
</Type>

<SplitCardContainer>
{standaloneExamples.map((example) => (
<FeaturedCard
key={example.slug}
label={example.entry.exampleSlug}
href={example.entry.url}
description={example.entry.description as Tag}
/>
))}
</SplitCardContainer>

<Type as="h2" look="heading30" margin="2rem 0 1rem 0" id="end-to-end-examples">
End-to-End Examples
</Type>

<Type as="p" look="body18" margin="1.25rem 0 1.5rem 0">
End to end examples demonstrate how a feature works or how to solve a problem with an
independent frontend application and a Keystone server.
</Type>

<SplitCardContainer>
{endToEndExamples.map((example) => (
<FeaturedCard
key={example.slug}
label={example.entry.exampleSlug}
href={example.entry.url}
description={example.entry.description as Tag}
gradient="grad2"
/>
))}
</SplitCardContainer>

<Type as="h2" look="heading30" margin="2rem 0 1rem 0" id="deployment-examples">
Deployment Examples
</Type>

<Type as="p" look="body18" margin="1.25rem 0 1.5rem 0">
Examples with all the code and documentation you need to get a Keystone project hosted on
the web. You can find them in the{' '}
<a href="https://github.com/keystonejs/">Keystone Github Organisation</a>.
</Type>

<SplitCardContainer>
{deploymentExamples.map((example) => (
<FeaturedCard
key={example.slug}
label={example.entry.exampleSlug}
href={example.entry.url}
description={example.entry.description as Tag}
gradient="grad4"
/>
))}
</SplitCardContainer>
</>
)
}
49 changes: 49 additions & 0 deletions docs/app/(site)/docs/examples-explorer/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { transform } from '@markdoc/markdoc'
import { DocsLayout } from '../../../../components/docs/DocsLayout'
import { reader } from '../../../../keystatic/reader'
import PageClient from './page-client'
import { baseMarkdocConfig } from '../../../../markdoc/config'

export const metadata = {
title: 'Examples',
description: 'A growing collection of projects you can run locally to learn more about Keystone’s many features. Use them as a reference for best practice, and springboard when adding features to your own project.',
}

export type GroupedExamples = Awaited<ReturnType<typeof getGroupedExamples>>

async function getGroupedExamples () {
const examples = await reader.collections.examplesReadme.all({resolveLinkedFiles: true})
const transformedExamples = await Promise.all(examples.map(async example => {
return {
...example,
entry : {
...example.entry,
description: transform(example.entry.description.node, baseMarkdocConfig),
url: `/docs/examples-explorer/${example.slug}`
}
}
}))

// const standaloneExamples = transformedExamples.filter(example => example.entry.kind === 'standalone')
// const endToEndExamples = transformedExamples.filter(example => example.entry.kind === 'end-to-end')
// const deploymentExamples = transformedExamples.filter(example => example.entry.kind === 'deployment')
const standaloneExamples = transformedExamples
const endToEndExamples = transformedExamples
const deploymentExamples = transformedExamples


return {
standaloneExamples,
endToEndExamples,
deploymentExamples
}
}

export default async function Docs () {
const pageData = await getGroupedExamples()
return (
<DocsLayout noRightNav noProse isIndexPage>
<PageClient {...JSON.parse(JSON.stringify(pageData))} />
</DocsLayout>
)
}
6 changes: 6 additions & 0 deletions docs/components/examples/ExamplesLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ExamplesLayoutClient } from './ExamplesLayoutClient'
import { DocsNavigation } from '../docs/docs-navigation'

export async function ExamplesLayout (props) {
return <ExamplesLayoutClient {...props} docsNavigation={<DocsNavigation />} />
}
Loading