diff --git a/package-lock.json b/package-lock.json
index 59896686..3e391c7d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
"cmdk": "^1.0.0",
"gray-matter": "^4.0.3",
"image-size": "^1.1.1",
+ "lodash-es": "^4.17.21",
"match-sorter": "^6.3.4",
"next": "^14.2.11",
"next-mdx-remote": "^5.0.0",
@@ -38,6 +39,7 @@
"devDependencies": {
"@savvywombat/tailwindcss-grid-areas": "^4.0.0",
"@types/hast": "^3.0.4",
+ "@types/lodash-es": "^4.17.12",
"@types/minimist": "^1.2.5",
"@types/node": "^18.19.50",
"@types/react": "^18.3.5",
@@ -1915,6 +1917,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.13",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
+ "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash-es": {
+ "version": "4.17.12",
+ "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+ "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
"node_modules/@types/mdast": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
@@ -7558,7 +7577,6 @@
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
- "dev": true,
"license": "MIT"
},
"node_modules/lodash.capitalize": {
diff --git a/package.json b/package.json
index 5e971d97..9ca6f010 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"devDependencies": {
"@savvywombat/tailwindcss-grid-areas": "^4.0.0",
"@types/hast": "^3.0.4",
+ "@types/lodash-es": "^4.17.12",
"@types/minimist": "^1.2.5",
"@types/node": "^18.19.50",
"@types/react": "^18.3.5",
@@ -41,6 +42,7 @@
"cmdk": "^1.0.0",
"gray-matter": "^4.0.3",
"image-size": "^1.1.1",
+ "lodash-es": "^4.17.21",
"match-sorter": "^6.3.4",
"next": "^14.2.11",
"next-mdx-remote": "^5.0.0",
diff --git a/src/components/mdx/Codesandbox/Codesandbox.tsx b/src/components/mdx/Codesandbox/Codesandbox.tsx
index a1e626bd..34c55f62 100644
--- a/src/components/mdx/Codesandbox/Codesandbox.tsx
+++ b/src/components/mdx/Codesandbox/Codesandbox.tsx
@@ -1,16 +1,25 @@
import { Img } from '@/components/mdx'
import cn from '@/lib/cn'
+import { ComponentProps } from 'react'
+import { fetchCSB } from './fetchCSB'
export type CSB = {
id: string
- title: string
- screenshot_url: string
- description: string
- tags: string[]
+ title?: string
+ screenshot_url?: string
+ description?: string
+ tags?: string[]
}
-export function Codesandbox({
+type Codesandbox0Props = CSB & {
+ hideTitle?: boolean
+ embed?: boolean
+} & ComponentProps<'a'> & {
+ imgProps?: ComponentProps<'img'>
+ }
+
+export function Codesandbox0({
id,
title = '',
description = '',
@@ -19,10 +28,9 @@ export function Codesandbox({
//
hideTitle = false,
embed = false,
-}: CSB & {
- hideTitle: boolean
- embed: boolean
-}) {
+ className,
+ imgProps: { className: imgClassName } = {},
+}: Codesandbox0Props) {
return (
<>
{embed ? (
@@ -38,7 +46,7 @@ export function Codesandbox({
href={`https://codesandbox.io/s/${id}`}
target="_blank"
rel="noreferrer"
- className="mb-2 block"
+ className={cn('mb-2 block', className)}
>
{screenshot_url && (
)}
@@ -76,3 +84,20 @@ export function Codesandbox({
>
)
}
+
+export async function Codesandbox1({ boxes, ...props }: { boxes: string[] } & Codesandbox0Props) {
+ const ids = boxes // populated from 1.
+ // console.log('ids', ids)
+
+ //
+ // Batch fetch all CSBs of the page
+ //
+ const csbs = await fetchCSB(...ids)
+ // console.log('boxes', boxes)
+ const data = csbs[props.id]
+ // console.log('data', data)
+
+ // Merge initial props with data
+ const merged = { ...props, ...data }
+ return
+}
diff --git a/src/components/mdx/Entries/Entries.tsx b/src/components/mdx/Entries/Entries.tsx
new file mode 100644
index 00000000..ae7840af
--- /dev/null
+++ b/src/components/mdx/Entries/Entries.tsx
@@ -0,0 +1,56 @@
+import { ComponentProps } from 'react'
+
+import { groupBy } from 'lodash-es'
+import { Codesandbox1 } from '../Codesandbox'
+
+type Entry = {
+ title: string
+ url: string
+ slug: string[]
+ boxes: string[]
+}
+
+export async function Entries({
+ items,
+ excludedGroups = ['getting-started'],
+ ...props
+}: { items: Entry[]; excludedGroups?: string[] } & ComponentProps<'div'>) {
+ const groupedEntries = groupBy(items, ({ slug }) => slug[0])
+
+ return (
+
+ {Object.entries(groupedEntries)
+ .filter(([group]) => !excludedGroups.includes(group))
+ .map(([group, entries]) => {
+ return (
+ <>
+
{group}
+
+ {entries?.map(({ title, url, boxes }) => (
+ -
+
+ {title}
+
+
+ {boxes.map((id) => (
+ -
+
+
+ ))}
+
+
+ ))}
+
+ >
+ )
+ })}
+
+ )
+}
diff --git a/src/components/mdx/Entries/index.ts b/src/components/mdx/Entries/index.ts
new file mode 100644
index 00000000..031cf172
--- /dev/null
+++ b/src/components/mdx/Entries/index.ts
@@ -0,0 +1 @@
+export * from './Entries'
diff --git a/src/components/mdx/index.tsx b/src/components/mdx/index.tsx
index 7587aa38..79646017 100644
--- a/src/components/mdx/index.tsx
+++ b/src/components/mdx/index.tsx
@@ -1,6 +1,7 @@
export * from './Code'
-export * from './Codesandbox'
+// export * from './Codesandbox'
export * from './Details'
+export * from './Entries'
export * from './Gha'
export * from './Grid'
export * from './Hint'
diff --git a/src/utils/docs.tsx b/src/utils/docs.tsx
index 212c06a4..0d77d33a 100644
--- a/src/utils/docs.tsx
+++ b/src/utils/docs.tsx
@@ -1,8 +1,8 @@
import type { Doc, DocToC } from '@/app/[...slug]/DocsContext'
import * as components from '@/components/mdx'
+import { Entries } from '@/components/mdx'
import { rehypeCode } from '@/components/mdx/Code/rehypeCode'
-import { Codesandbox } from '@/components/mdx/Codesandbox'
-import { fetchCSB } from '@/components/mdx/Codesandbox/fetchCSB'
+import { Codesandbox1 } from '@/components/mdx/Codesandbox'
import { rehypeCodesandbox } from '@/components/mdx/Codesandbox/rehypeCodesandbox'
import { rehypeDetails } from '@/components/mdx/Details/rehypeDetails'
import { rehypeGha } from '@/components/mdx/Gha/rehypeGha'
@@ -15,7 +15,7 @@ import matter from 'gray-matter'
import { compileMDX } from 'next-mdx-remote/rsc'
import fs from 'node:fs'
import { dirname } from 'node:path'
-import React, { cache } from 'react'
+import { cache } from 'react'
import rehypePrismPlus from 'rehype-prism-plus'
import remarkGFM from 'remark-gfm'
@@ -73,34 +73,18 @@ async function _getDocs(
)
// console.log('files', files)
- const docs = await Promise.all(
- files.map(async (file) => {
- const relFilePath = file.substring(root.length) // "/getting-started/tutorials/store.mdx"
+ //
+ // 1st pass for `entries`
+ //
+ const entries = await Promise.all(
+ files.map(async (file) => {
// Get slug from local path
const path = file.replace(`${root}/`, '')
const slug = [...path.replace(MARKDOWN_REGEX, '').toLowerCase().split('/')]
- //
- // "Lightest" version of the doc (for `generateStaticParams`)
- //
-
- if (slugOnly) {
- return { slug } as Doc
- }
-
- //
- // Common infos (for every `docs`)
- //
-
const url = `/${slug.join('/')}`
- // editURL
- const EDIT_BASEURL = process.env.EDIT_BASEURL
- const editURL = EDIT_BASEURL?.length ? file.replace(root, EDIT_BASEURL) : undefined
-
- // Read & parse doc
-
//
// frontmatter
//
@@ -110,109 +94,159 @@ async function _getDocs(
const frontmatter = compiled.data
const _lastSegment = slug[slug.length - 1]
- const title: string = frontmatter.title ?? _lastSegment.replace(/\-/g, ' ')
-
- const description: string = frontmatter.description ?? ''
-
- const sourcecode: string = frontmatter.sourcecode ?? ''
- const SOURCECODE_BASEURL = process.env.SOURCECODE_BASEURL
- const sourcecodeURL = SOURCECODE_BASEURL?.length
- ? `${SOURCECODE_BASEURL}/${sourcecode}`
- : undefined
-
- const nav: number = frontmatter.nav ?? Infinity
-
- const frontmatterImage: string | undefined = frontmatter.image
- const srcImage = frontmatterImage || process.env.LOGO
- const image: string = srcImage ? resolveMdxUrl(srcImage, relFilePath, MDX_BASEURL) : ''
-
- //
- // MDX content
- //
-
- // Skip docs other than `slugOfInterest` -- better perfs)
- // if (JSON.stringify(slug) !== JSON.stringify(slugOfInterest)) {
- // return {
- // slug,
- // url,
- // editURL,
- // title,
- // description,
- // nav,
- // } as Doc
- // }
-
- // Sanitize markdown
- let content = compiled.content
- // Remove comments from frontMatter
- .replace(FRONTMATTER_REGEX, '')
- // Remove extraneous comments from post
- .replace(COMMENT_REGEX, '')
- // Remove inline link syntax
- .replace(INLINE_LINK_REGEX, '$1')
-
- //
- // inline images
- //
+ const title: string = frontmatter.title.trim() ?? _lastSegment.replace(/\-/g, ' ')
const boxes: string[] = []
- const tableOfContents: DocToC[] = []
- const { content: jsx } = await compileMDX({
- source: `# ${title}\n ${content}`,
+ await compileMDX({
+ source: compiled.content,
options: {
mdxOptions: {
- remarkPlugins: [remarkGFM],
rehypePlugins: [
- rehypeImg(relFilePath, MDX_BASEURL),
- rehypeDetails,
- rehypeSummary,
- rehypeGha,
- rehypePrismPlus,
- rehypeCode(),
- rehypeCodesandbox(boxes), // 1. put all Codesandbox[id] into `doc.boxes`
- rehypeToc(tableOfContents, url, title), // 2. will populate `doc.tableOfContents`
- rehypeSandpack(dirname(file)),
+ rehypeCodesandbox(boxes), // 1. put all Codesandbox[id] into `boxes`
],
},
},
- components: {
- ...components,
- Codesandbox: async (props: React.ComponentProps) => {
- const ids = boxes // populated from 1.
- // console.log('ids', ids)
-
- //
- // Batch fetch all CSBs of the page
- //
- const csbs = await fetchCSB(...ids)
- // console.log('boxes', boxes)
- const data = csbs[props.id]
- // console.log('data', data)
-
- // Merge initial props with data
- const merged = { ...props, ...data }
- return
- },
- },
})
return {
slug,
url,
- editURL,
- sourcecode,
- sourcecodeURL,
title,
- image,
- description,
- nav,
- content: jsx,
boxes,
- tableOfContents,
+ //
+ file,
+ compiled,
}
}),
)
+ // console.log('entries', entries)
+
+ //
+ // 2nd pass for `docs`
+ //
+
+ const docs = await Promise.all(
+ entries.map(
+ async ({
+ slug,
+ url,
+ title,
+ boxes,
+ // Passed from the 1st pass
+ file,
+ compiled,
+ }) => {
+ const relFilePath = file.substring(root.length) // "/getting-started/tutorials/store.mdx"
+
+ //
+ // "Lightest" version of the doc (for `generateStaticParams`)
+ //
+
+ if (slugOnly) {
+ return { slug } as Doc
+ }
+
+ //
+ // Common infos (for every `docs`)
+ //
+
+ // editURL
+ const EDIT_BASEURL = process.env.EDIT_BASEURL
+ const editURL = EDIT_BASEURL?.length ? file.replace(root, EDIT_BASEURL) : undefined
+
+ //
+ // frontmatter
+ //
+
+ const frontmatter = compiled.data
+
+ const description: string = frontmatter.description ?? ''
+
+ const sourcecode: string = frontmatter.sourcecode ?? ''
+ const SOURCECODE_BASEURL = process.env.SOURCECODE_BASEURL
+ const sourcecodeURL = SOURCECODE_BASEURL?.length
+ ? `${SOURCECODE_BASEURL}/${sourcecode}`
+ : undefined
+
+ const nav: number = frontmatter.nav ?? Infinity
+
+ const frontmatterImage: string | undefined = frontmatter.image
+ const srcImage = frontmatterImage || process.env.LOGO
+ const image: string = srcImage ? resolveMdxUrl(srcImage, relFilePath, MDX_BASEURL) : ''
+
+ //
+ // MDX content
+ //
+
+ // Skip docs other than `slugOfInterest` -- better perfs)
+ // if (JSON.stringify(slug) !== JSON.stringify(slugOfInterest)) {
+ // return {
+ // slug,
+ // url,
+ // editURL,
+ // title,
+ // description,
+ // nav,
+ // } as Doc
+ // }
+
+ // Sanitize markdown
+ let content = compiled.content
+ // Remove comments from frontMatter
+ .replace(FRONTMATTER_REGEX, '')
+ // Remove extraneous comments from post
+ .replace(COMMENT_REGEX, '')
+ // Remove inline link syntax
+ .replace(INLINE_LINK_REGEX, '$1')
+
+ //
+ // inline images
+ //
+
+ const tableOfContents: DocToC[] = []
+
+ const { content: jsx } = await compileMDX({
+ source: `# ${title}\n ${content}`,
+ options: {
+ mdxOptions: {
+ remarkPlugins: [remarkGFM],
+ rehypePlugins: [
+ rehypeImg(relFilePath, MDX_BASEURL),
+ rehypeDetails,
+ rehypeSummary,
+ rehypeGha,
+ rehypePrismPlus,
+ rehypeCode(),
+ rehypeToc(tableOfContents, url, title), // 2. will populate `doc.tableOfContents`
+ rehypeSandpack(dirname(file)),
+ ],
+ },
+ },
+ components: {
+ ...components,
+ Codesandbox: (props) => ,
+ Entries: () => ,
+ },
+ })
+
+ return {
+ slug,
+ url,
+ editURL,
+ sourcecode,
+ sourcecodeURL,
+ title,
+ image,
+ description,
+ nav,
+ content: jsx,
+ boxes,
+ tableOfContents,
+ }
+ },
+ ),
+ )
// console.log('docs', docs)
return docs.sort((a, b) => a.nav - b.nav)