[Experimental] Docs: Migrate to next-docs-mdx

This commit is contained in:
SonMooSans
2023-10-23 13:28:57 +08:00
parent aa3a948652
commit 678cd3dc1f
42 changed files with 409 additions and 377 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'next-docs-zeta': minor
'next-docs-ui': minor
---
Improve CommonJS/ESM compatibility
-26
View File
@@ -1,26 +0,0 @@
# docs
## 0.1.3
### Patch Changes
- Updated dependencies [67cd8ab]
- next-docs-zeta@0.2.1
- next-docs-ui@0.1.2
## 0.1.2
### Patch Changes
- Updated dependencies [5ff94af]
- Updated dependencies [5ff94af]
- next-docs-zeta@0.2.0
- next-docs-ui@0.1.1
## 0.1.1
### Patch Changes
- Updated dependencies [d909192]
- Updated dependencies [e88eec8]
- next-docs-zeta@0.0.2
@@ -1,38 +0,0 @@
'use client'
import { Wrapper } from '@/components/preview/wrapper'
import { ThemeSwitch } from '@/components/ThemeSwitch'
import { useMDXComponent } from 'next-contentlayer/hooks'
import { Accordion, Accordions } from 'next-docs-ui/components/accordion'
import { Callout } from 'next-docs-ui/components/callout'
import { ImageZoom } from 'next-docs-ui/components/image-zoom'
import { Tab, Tabs } from 'next-docs-ui/components/tabs'
import { TypeTable } from 'next-docs-ui/components/type-table'
import defaultComponents from 'next-docs-ui/mdx'
import { type HTMLAttributes } from 'react'
const components = {
...defaultComponents,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
img: (props: any) => <ImageZoom {...props} />,
pre: (props: HTMLAttributes<HTMLPreElement>) => (
<defaultComponents.pre {...props} className="max-h-[400px]" />
),
ThemeSwitch,
Callout,
TypeTable,
Accordion,
Accordions,
Tab,
Tabs,
Wrapper,
blockquote: (props: React.QuoteHTMLAttributes<HTMLQuoteElement>) => (
<Callout className="my-4">{props.children}</Callout>
)
}
export function Content({ code }: { code: string }) {
const MDX = useMDXComponent(code)
return <MDX components={components} />
}
+34 -37
View File
@@ -1,18 +1,14 @@
import { resolve } from 'url'
import Preview from '@/components/preview'
import { createMetadata } from '@/utils/metadata'
import { getPage, getPageUrl, getTree } from '@/utils/source'
import { allDocs, type Docs } from 'contentlayer/generated'
import { getPage, getPageUrl, getTree, pages } from '@/utils/source'
import { ExternalLinkIcon } from 'lucide-react'
import type { Metadata } from 'next'
import type { Page } from 'next-docs-mdx/types'
import { Card, Cards, MDXContent } from 'next-docs-ui/mdx'
import { DocsPage } from 'next-docs-ui/page'
import {
findNeighbour,
getGitLastEditTime,
getTableOfContents
} from 'next-docs-zeta/server'
import { findNeighbour, getGitLastEditTime } from 'next-docs-zeta/server'
import { notFound } from 'next/navigation'
import { Content } from './content'
type Param = {
mode: string
@@ -21,14 +17,13 @@ type Param = {
export default async function Page({ params }: { params: Param }) {
const tree = getTree(params.mode)
const page = getPage([params.mode, ...(params.slug ?? [])])
const page = getPage([params.mode, ...(params.slug ?? [])]) as Page
if (page == null) {
notFound()
}
const toc = await getTableOfContents(page.body.raw)
const url = getPageUrl(page.slug)
const url = getPageUrl(page.slugs)
const neighbours = findNeighbour(tree, url)
const headers = new Headers()
@@ -37,24 +32,28 @@ export default async function Page({ params }: { params: Param }) {
const time = await getGitLastEditTime(
'SonMooSans/next-docs',
'apps/docs/content/' + page._raw.sourceFilePath,
resolve('apps/docs/', page.file.path),
undefined,
{
headers
}
)
const preview = page.preview?.trim()
const preview = page.matter.preview?.trim()
const MDX = page.data.default
return (
<DocsPage
toc={toc}
toc={page.data.toc}
footer={neighbours}
lastUpdate={time}
tableOfContent={{
footer: (
<a
href={`https://github.com/fuma-nama/next-docs/blob/main/apps/docs/content/${page._raw.sourceFilePath}`}
href={resolve(
`https://github.com/fuma-nama/next-docs/blob/main/apps/docs/content`,
page.file.path
)}
target="_blank"
rel="noreferrer noopener"
className="text-xs inline-flex text-muted-foreground items-center hover:text-foreground"
@@ -67,34 +66,33 @@ export default async function Page({ params }: { params: Param }) {
<MDXContent>
<div className="nd-not-prose mb-12">
<h1 className="text-foreground mb-4 text-3xl font-semibold sm:text-4xl">
{page.title}
{page.matter.title}
</h1>
<p className="text-muted-foreground sm:text-lg">{page.description}</p>
<p className="text-muted-foreground sm:text-lg">
{page.matter.description}
</p>
</div>
{preview != null && preview in Preview && Preview[preview]}
{page.index ? (
<Category page={page} />
) : (
<Content code={page.body.code} />
)}
{page.matter.index ? <Category page={page} /> : <MDX />}
</MDXContent>
</DocsPage>
)
}
function Category({ page }: { page: Docs }) {
const pages = allDocs.filter(docs =>
docs._raw.flattenedPath.startsWith(page._raw.flattenedPath + '/')
function Category({ page }: { page: Page }) {
const filtered = pages.filter(
docs =>
docs.file.dirname === page.file.dirname && docs.file.name !== 'index'
)
return (
<Cards>
{pages.map(page => (
{filtered.map(page => (
<Card
key={page._id}
title={page.title}
description={page.description ?? 'No Description'}
href={getPageUrl(page.slug)}
key={page.file.id}
title={page.matter.title}
description={page.matter.description ?? 'No Description'}
href={getPageUrl(page.slugs)}
/>
))}
</Cards>
@@ -108,12 +106,11 @@ export function generateMetadata({ params }: { params: Param }): Metadata {
if (page == null) return {}
const description =
page.description ??
'The headless ui library for building documentation websites'
page.matter.description ?? 'The library for building documentation sites'
const imageParams = new URLSearchParams()
imageParams.set('title', page.title)
if (page.description) imageParams.set('description', page.description)
imageParams.set('title', page.matter.title)
imageParams.set('description', description)
const image = {
alt: 'Banner',
@@ -123,7 +120,7 @@ export function generateMetadata({ params }: { params: Param }): Metadata {
}
return createMetadata({
title: page.title,
title: page.matter.title,
description,
openGraph: {
url: `https://next-docs-zeta.vercel.app/docs/${slugs.join('/')}`,
@@ -136,8 +133,8 @@ export function generateMetadata({ params }: { params: Param }): Metadata {
}
export function generateStaticParams() {
return allDocs.map(docs => {
const [mode, ...slugs] = docs.slug.split('/')
return pages.map(docs => {
const [mode, ...slugs] = docs.slugs
return {
slug: slugs,
-16
View File
@@ -1,16 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/style.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/utils/cn"
}
}
@@ -0,0 +1,3 @@
import { ThemeSwitch as Original } from './ThemeSwitch'
export const ThemeSwitch = (p => <Original {...p} />) as typeof Original
@@ -3,8 +3,11 @@ title: Image Optimization
description: Use Next.js image optimization in your MDX documents
---
Next Docs UI uses the built-in `next/image` component. You can enjoy the
benefits of Next.js Image Optimization without extra configurations.
import Banner from '@/public/banner.png'
Next Docs Zeta supports the built-in `next/image` component by passing `width`
and `height` property automatically. You can enjoy the benefits of Next.js Image
Optimization without extra configurations.
Under the hood, It gets the image size using `rehype-img-size`, then display it
via `next/image`.
@@ -32,4 +35,4 @@ Assume the image is under the `/public` folder.
![banner](/banner.png)
```
![banner](/banner.png)
<Image alt="Banner" src={Banner} />
+3 -3
View File
@@ -3,10 +3,10 @@ title: Quick Start
description: Getting Started with Next Docs UI
---
<Wrapper className="p-0 nd-not-prose">
![Features](/features.png)
import Features from '@/public/features.png'
<Wrapper className="nd-not-prose p-0">
<Image alt="Features" src={Features} />
</Wrapper>
## Overview
+1
View File
@@ -4,6 +4,7 @@ description: Add Theme to Next Docs UI
---
import { blue_theme, full_layout } from '@/components/themes.js'
import { ThemeSwitch } from '@/components/ThemeSwitch_server'
The design system is based on [Shadcn UI](https://ui.shadcn.com), allowing you
to overwrite css variables.
-33
View File
@@ -1,33 +0,0 @@
import { makeSource } from 'contentlayer/source-files'
import { createConfig } from 'next-docs-zeta/contentlayer/configuration'
import { remarkDynamicContent, structure } from 'next-docs-zeta/mdx-plugins'
import rehypeKatex from 'rehype-katex'
import remarkMath from 'remark-math'
const config = createConfig({
docFields: {
index: {
type: 'boolean',
default: false,
required: false
},
preview: {
type: 'string',
required: false
}
},
docsComputedFields: {
structuredData: {
type: 'json',
resolve: page => {
return structure(page.body.raw, [remarkMath])
}
}
}
})
config.mdx!.remarkPlugins!.push(remarkMath)
config.mdx!.remarkPlugins!.push(remarkDynamicContent)
config.mdx!.rehypePlugins!.push(rehypeKatex)
export default makeSource(config)
+26
View File
@@ -0,0 +1,26 @@
import { Wrapper } from '@/components/preview/wrapper'
import type { MDXComponents } from 'mdx/types'
import { Accordion, Accordions } from 'next-docs-ui/components/accordion'
import { Callout } from 'next-docs-ui/components/callout'
import { Tab, Tabs } from 'next-docs-ui/components/tabs'
import { TypeTable } from 'next-docs-ui/components/type-table'
import defaultComponents from 'next-docs-ui/mdx-server'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...defaultComponents,
pre: props => (
<defaultComponents.pre {...props} className="max-h-[400px]" />
),
Image: defaultComponents.img,
Tabs: (p => <Tabs {...p} />) as typeof Tabs,
Tab: (p => <Tab {...p} />) as typeof Tab,
Callout: (p => <Callout {...p} />) as typeof Callout,
TypeTable: (p => <TypeTable {...p} />) as typeof TypeTable,
Accordion: (p => <Accordion {...p} />) as typeof Accordion,
Accordions: (p => <Accordions {...p} />) as typeof Accordions,
Wrapper,
blockquote: props => <Callout className="my-4">{props.children}</Callout>,
...components
}
}
+17
View File
@@ -0,0 +1,17 @@
import type * as Original from 'next-docs-mdx/types'
declare module 'next-docs-mdx/types' {
interface Frontmatter extends Original.Frontmatter {
preview?: string
index?: boolean
}
interface MDXExport extends Original.MDXExport {
frontmatter: Frontmatter
}
interface Page extends Original.Page {
data: MDXExport
matter: Frontmatter
}
}
-17
View File
@@ -1,17 +0,0 @@
const withAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
/** @type {import('next').NextConfig} */
const config = {
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
reactStrictMode: true,
eslint: {
// Replaced by root workspace command
ignoreDuringBuilds: true
}
}
const { withContentlayer } = require('next-contentlayer')
module.exports = withAnalyzer(withContentlayer(config))
+28
View File
@@ -0,0 +1,28 @@
import createBundleAnalyzer from '@next/bundle-analyzer'
import createNextDocs from 'next-docs-mdx/config'
import { remarkDynamicContent } from 'next-docs-zeta/mdx-plugins'
import rehypeKatex from 'rehype-katex'
import remarkMath from 'remark-math'
const withAnalyzer = createBundleAnalyzer({
enabled: process.env.ANALYZE === 'true'
})
/** @type {import('next').NextConfig} */
const config = {
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
reactStrictMode: true,
eslint: {
// Replaced by root workspace command
ignoreDuringBuilds: true
}
}
const withNextDocs = createNextDocs({
options: {
remarkPlugins: [remarkMath, remarkDynamicContent],
rehypePlugins: [rehypeKatex]
}
})
export default withAnalyzer(withNextDocs(config))
+10 -7
View File
@@ -9,20 +9,20 @@
"start": "next start"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.4",
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@next/mdx": "^13.5.6",
"@next/env": "^13.5.6",
"algoliasearch": "^4.20.0",
"autoprefixer": "10.4.16",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"contentlayer": "^0.3.4",
"katex": "^0.16.8",
"lucide-react": "^0.288.0",
"next": "13.5.6",
"next-contentlayer": "^0.3.4",
"next-docs-mdx": "workspace:*",
"next-docs-ui": "workspace:*",
"next-docs-zeta": "workspace:*",
"next-sitemap": "^4.2.3",
"postcss": "8.4.31",
"react": "18.2.0",
"react-dom": "18.2.0",
"rehype-katex": "^6.0.3",
@@ -31,11 +31,14 @@
},
"devDependencies": {
"@next/bundle-analyzer": "^13.4.19",
"@types/mdx": "^2.0.8",
"@types/node": "18.17.5",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"dotenv": "^16.3.1",
"autoprefixer": "10.4.16",
"postcss": "8.4.31",
"tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"webpack": "^5.89.0"
}
}
+2 -1
View File
@@ -4,7 +4,8 @@ module.exports = {
content: [
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./content/**/*.mdx'
'./content/**/*.mdx',
'./mdx-components.tsx'
],
theme: {
container: {
+3 -4
View File
@@ -16,8 +16,7 @@
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"],
"contentlayer/generated": ["./.contentlayer/generated"]
"@/*": ["./*"]
},
"plugins": [
{
@@ -27,10 +26,10 @@
},
"include": [
"next-env.d.ts",
"mdx.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".contentlayer/generated"
".next/types/**/*.ts"
],
"exclude": ["node_modules"]
}
+13 -24
View File
@@ -1,10 +1,14 @@
import path from 'path'
import { readFileSync } from 'fs'
import { resolve } from 'path'
import env from '@next/env'
import algosearch from 'algoliasearch'
import { config } from 'dotenv'
import { sync } from 'next-docs-zeta/search-algolia/server'
import { allDocs } from './.contentlayer/generated/index.mjs'
config({ path: path.resolve(process.cwd(), '.env.local') })
env.loadEnvConfig(process.cwd())
const indexes = JSON.parse(
readFileSync(resolve('./.next/_map_indexes.json')).toString()
)
const client = algosearch(
process.env.ALGOLIA_APP_ID,
@@ -12,30 +16,15 @@ const client = algosearch(
)
sync(client, {
documents: allDocs.map(docs => ({
...docs,
url: getPageUrl(['/docs', ...docs.slug.split('/')]),
documents: indexes.map(docs => ({
_id: docs.id,
title: docs.title,
url: docs.url,
structured: docs.structuredData,
extra_data: {
tag: docs._raw.flattenedPath.startsWith('docs/ui') ? 'ui' : 'headless'
tag: docs.url.startsWith('/docs/ui') ? 'ui' : 'headless'
}
}))
}).then(() => {
console.log('search updated')
})
/**
* @param {Array<string>} slug
* @returns {string}
*/
function getPageUrl(slug) {
const url = slug
.filter(segment => segment != null && segment.length > 0)
.join('/')
if (url.startsWith('//')) {
return url.slice(1)
}
return url
}
+44 -6
View File
@@ -1,12 +1,23 @@
import { allDocs, allMeta } from 'contentlayer/generated'
import { createUtils, loadContext } from 'next-docs-zeta/contentlayer'
import { writeFileSync } from 'fs'
import path from 'path'
import { map } from '@/_map'
import {
createPageUtils,
getPageTreeBuilder,
resolveFiles
} from 'next-docs-mdx/map'
import type { PageTree } from 'next-docs-zeta/server'
import { PHASE_PRODUCTION_BUILD } from 'next/constants'
const ctx = loadContext(allMeta, allDocs)
const resolved = resolveFiles({ map, root: 'docs' })
const uiTree = ctx.builder.build({ root: 'docs/ui' })
export const { getPage, getPageUrl } = createPageUtils(resolved, '/docs', [])
const headlessTree = ctx.builder.build({
const builder = getPageTreeBuilder(resolved, { getUrl: getPageUrl })
const uiTree = builder.build({ root: 'docs/ui' })
const headlessTree = builder.build({
root: 'docs/headless'
})
@@ -18,4 +29,31 @@ export function getTree(mode: 'ui' | 'headless' | string): PageTree {
return headlessTree
}
export const { getPage, getPageUrl } = createUtils(ctx)
export const { pages, metas } = resolved
// Access and export MDX pages data to json file
// So that we can update search indexes after the build
declare global {
// eslint-disable-next-line no-var
var __NEXT_DOCS_INDEX_UPDATED: boolean
}
global.__NEXT_DOCS_INDEX_UPDATED = false
if (
process.env.NEXT_PHASE === PHASE_PRODUCTION_BUILD &&
!global.__NEXT_DOCS_INDEX_UPDATED
) {
const mapPath = path.resolve('./.next/_map_indexes.json')
const indexes = pages.map(page => ({
id: page.file.id,
title: page.matter.title,
description: page.matter.description,
url: getPageUrl(page.slugs, page.file.locale),
structuredData: page.data.structuredData
}))
writeFileSync(mapPath, JSON.stringify(indexes))
global.__NEXT_DOCS_INDEX_UPDATED = true
}
@@ -1,8 +1,5 @@
import { allPages, getPageUrl } from '@/app/source'
import { createSearchAPI, setResponse } from 'next-docs-zeta/search/server'
import { NextResponse } from 'next/server'
setResponse(NextResponse)
import { createSearchAPI } from 'next-docs-zeta/search/server'
export const { GET } = createSearchAPI('advanced', {
indexes: allPages.map(page => ({
+2 -3
View File
@@ -1,11 +1,10 @@
import type { MDXComponents } from 'mdx/types'
import defaultComponents, { default_image_sizes } from 'next-docs-ui/mdx-server'
import NextImage from 'next/image'
import defaultComponents from 'next-docs-ui/mdx-server'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...defaultComponents,
Image: p => <NextImage sizes={default_image_sizes} {...p} />,
Image: p => <defaultComponents.img {...p} />,
...components
}
}
+6 -4
View File
@@ -42,21 +42,23 @@
"yaml": "^2.3.3"
},
"peerDependencies": {
"@next/mdx": "13.x",
"@mdx-js/loader": "2.x",
"@mdx-js/react": "2.x",
"@next/mdx": "13.x",
"next-docs-zeta": "3.0.0"
},
"devDependencies": {
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@mdx-js/mdx": "^2.3.0",
"@next/mdx": "13.5.6",
"@types/mdast": "^3.0.12",
"@types/mdx": "^2.0.8",
"@types/react": "18.2.0",
"next": "13.5.6",
"next-docs-zeta": "workspace:*",
"unified": "^10.1.2",
"@types/mdast": "^3.0.12",
"@next/mdx": "13.5.6",
"next-docs-zeta": "workspace:*"
"webpack": "^5.89.0"
},
"keywords": [
"NextJs",
+4 -5
View File
@@ -3,9 +3,9 @@ import {
type PageTreeBuilder
} from 'next-docs-zeta/build-page-tree'
import type { ReactElement } from 'react'
import type { Meta, Page } from './types'
import type { ResolvedFiles } from './resolve-files'
export type ContextOptions = {
export type BuilderOptions = {
/**
* Get url from slugs and locale, override the default getUrl function
*/
@@ -15,9 +15,8 @@ export type ContextOptions = {
}
export function getPageTreeBuilder(
metas: Meta[],
pages: Page[],
{ getUrl, resolveIcon = () => undefined }: ContextOptions
{ metas, pages }: ResolvedFiles,
{ getUrl, resolveIcon = () => undefined }: BuilderOptions
): PageTreeBuilder {
return createPageTreeBuilder({
metas: metas.map(meta => ({
+14 -12
View File
@@ -14,6 +14,7 @@ import remarkFrontmatter, {
type Options as RemarkFrontmatterOptions
} from 'remark-frontmatter'
import type { PluggableList } from 'unified'
import type { Compiler } from 'webpack'
import remarkMdxExport from './mdx-plugins/remark-exports'
import remarkMdxFrontmatter from './mdx-plugins/remark-frontmatter'
@@ -35,6 +36,18 @@ type NextDocsMDXOptions = NextMDXOptions & {
dataExports?: string[]
}
function Plugin(compiler: Compiler) {
compiler.hooks.beforeCompile.tap(PLUGIN_NAME, () => {
if (firstLoad) {
chokidar.watch('./content').on('all', () => {
buildMap()
})
firstLoad = false
}
})
}
const createNextDocs =
({
loader = options => createNextMDX(options),
@@ -73,18 +86,7 @@ const createNextDocs =
return Object.assign({}, nextConfig, {
webpack: (config, options) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
config.plugins.push((compiler: any) => {
compiler.hooks.initialize.tap(PLUGIN_NAME, () => {
if (firstLoad) {
chokidar.watch('./content').on('all', () => {
buildMap()
})
firstLoad = false
}
})
})
config.plugins.push(Plugin)
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options)
+7 -7
View File
@@ -1,5 +1,5 @@
import type { PageTree } from 'next-docs-zeta/server'
import { getPageTreeBuilder, type ContextOptions } from './build-tree'
import { getPageTreeBuilder, type BuilderOptions } from './build-tree'
import { createPageUtils, type PageUtils } from './page-utils'
import { resolveFiles } from './resolve-files'
import type { Meta, Page } from './types'
@@ -17,7 +17,7 @@ type UtilsOptions<Langs extends string[] | undefined> = {
* @default 'docs'
*/
root: string
} & ContextOptions
} & BuilderOptions
type Utils = PageUtils & {
tree: PageTree
@@ -39,21 +39,21 @@ function fromMap<Langs extends string[] | undefined = undefined>(
languages
}: Partial<UtilsOptions<Langs>> = {}
): Langs extends string[] ? I18nUtils : Utils {
const context = resolveFiles({
const resolved = resolveFiles({
map,
root
})
const pageUtils = createPageUtils(context, baseUrl, languages ?? [])
const pageUtils = createPageUtils(resolved, baseUrl, languages ?? [])
if (getUrl) pageUtils.getPageUrl = getUrl
const builder = getPageTreeBuilder(context.metas, context.pages, {
const builder = getPageTreeBuilder(resolved, {
getUrl: pageUtils.getPageUrl,
resolveIcon
})
return {
...context,
...resolved,
...pageUtils,
tree: (languages == null
? builder.build({ root })
@@ -62,4 +62,4 @@ function fromMap<Langs extends string[] | undefined = undefined>(
}
}
export { fromMap }
export { fromMap, resolveFiles, createPageUtils, getPageTreeBuilder }
+3 -2
View File
@@ -12,7 +12,8 @@ export type ResolvedFiles = {
function parsePath(p: string, prefix = './content'): FileInfo {
const subPath = path.relative(prefix, p)
const parsed = path.parse(subPath)
let flattenedPath = parsed.dir + '/' + parsed.name
const normalizedDirname = parsed.dir.replaceAll('\\', '/')
let flattenedPath = normalizedDirname + '/' + parsed.name
while (flattenedPath.startsWith('/')) {
flattenedPath = flattenedPath.slice(1)
@@ -22,7 +23,7 @@ function parsePath(p: string, prefix = './content'): FileInfo {
return {
id: subPath,
dirname: parsed.dir,
dirname: normalizedDirname,
base: parsed.base,
name: parsed.name,
flattenedPath,
+2 -2
View File
@@ -64,12 +64,12 @@ export interface JsonExport {
icon?: string
}
export type Meta = {
export interface Meta {
file: FileInfo
data: JsonExport
}
export type Page = {
export interface Page {
file: FileInfo
slugs: string[]
data: MDXExport
+11 -3
View File
@@ -7,16 +7,21 @@
"repository": "github:fuma-nama/next-docs",
"license": "MIT",
"files": [
"dist/*"
"dist/*",
"server/*"
],
"exports": {
"./style.css": "./dist/style.css",
"./components/*": {
"import": "./dist/components/*.mjs",
"import": "./dist/components/*.js",
"types": "./dist/components/*.d.ts"
},
"./mdx-server": {
"import": "./server/mdx-server.js",
"types": "./server/mdx-server/d.ts"
},
"./*": {
"import": "./dist/*.mjs",
"import": "./dist/*.js",
"types": "./dist/*.d.ts"
}
},
@@ -25,6 +30,9 @@
"components/*": [
"./dist/components/*.d.ts"
],
"mdx-server": [
"./server/mdx-server.d.ts"
],
"*": [
"./dist/*.d.ts"
]
+22
View File
@@ -0,0 +1,22 @@
import type { SafeLink } from 'next-docs-zeta/link'
import type { HTMLAttributes } from 'react'
import type { Card, Cards, Image, Pre, Table } from '../dist/mdx'
declare const default_image_sizes: string
declare const defaultMdxComponents: {
Card: Card
Cards: Cards
h1: (p: HTMLAttributes<HTMLHeadingElement>) => JSX.Element
h2: (p: HTMLAttributes<HTMLHeadingElement>) => JSX.Element
h3: (p: HTMLAttributes<HTMLHeadingElement>) => JSX.Element
h4: (p: HTMLAttributes<HTMLHeadingElement>) => JSX.Element
h5: (p: HTMLAttributes<HTMLHeadingElement>) => JSX.Element
h6: (p: HTMLAttributes<HTMLHeadingElement>) => JSX.Element
img: Image
a: SafeLink
pre: Pre
table: Table
}
export { defaultMdxComponents as default, default_image_sizes }
@@ -0,0 +1,28 @@
import { jsx } from 'react/jsx-runtime'
import {
Card,
Cards,
default_image_sizes,
Heading,
Image,
Link,
Pre,
Table
} from '../dist/mdx'
const defaultMdxComponents = {
Card: p => /* @__PURE__ */ jsx(Card, p),
Cards: p => /* @__PURE__ */ jsx(Cards, p),
h1: p => /* @__PURE__ */ jsx(Heading, { as: 'h1', ...p }),
h2: p => /* @__PURE__ */ jsx(Heading, { as: 'h2', ...p }),
h3: p => /* @__PURE__ */ jsx(Heading, { as: 'h3', ...p }),
h4: p => /* @__PURE__ */ jsx(Heading, { as: 'h4', ...p }),
h5: p => /* @__PURE__ */ jsx(Heading, { as: 'h5', ...p }),
h6: p => /* @__PURE__ */ jsx(Heading, { as: 'h6', ...p }),
img: p => /* @__PURE__ */ jsx(Image, p),
a: p => /* @__PURE__ */ jsx(Link, p),
pre: p => /* @__PURE__ */ jsx(Pre, p),
table: p => /* @__PURE__ */ jsx(Table, p)
}
export { defaultMdxComponents as default, default_image_sizes }
@@ -12,18 +12,26 @@ export type ImageZoomProps = ImageProps & {
zoomInProps?: ImgHTMLAttributes<HTMLImageElement>
}
export function ImageZoom({ zoomInProps, ...props }: ImageZoomProps) {
function getImageSrc(src: ImageProps['src']): string {
return typeof src === 'string'
? src
: 'default' in src
? src.default.src
: src.src
}
export function ImageZoom({ zoomInProps, children, ...props }: ImageZoomProps) {
return (
<Zoom
zoomMargin={20}
wrapElement="span"
zoomImg={{
src: props.src as string,
src: getImageSrc(props.src),
sizes: undefined,
...zoomInProps
}}
>
<Image sizes={default_image_sizes} {...props} />
{children ?? <Image sizes={default_image_sizes} {...props} />}
</Zoom>
)
}
+2 -2
View File
@@ -126,7 +126,7 @@ function SidebarToggle() {
return (
<SidebarTrigger
aria-label="Toggle Sidebar"
className={cn(itemVariants({ className: 'lg:nd-hidden' }))}
className={cn(itemVariants({ className: 'md:nd-hidden' }))}
>
<MenuIcon />
</SidebarTrigger>
@@ -140,7 +140,7 @@ function DesktopSidebarToggle() {
<button
aria-label="Toggle Sidebar"
onClick={() => setOpen(!open)}
className={cn(itemVariants({ className: 'max-lg:nd-hidden' }))}
className={cn(itemVariants({ className: 'max-md:nd-hidden' }))}
>
{open ? <SidebarCloseIcon /> : <SidebarOpenIcon />}
</button>
@@ -40,18 +40,17 @@ export function Sidebar({ banner, footer }: SidebarProps) {
return (
<Base.SidebarList
minWidth={1024} // lg
minWidth={768} // md
className={cn(
'nd-flex nd-flex-col nd-w-full',
'nd-flex nd-flex-col nd-w-full nd-text-medium md:nd-text-sm md:nd-sticky md:nd-top-16 md:nd-h-body md:nd-transition-all',
open
? 'md:nd-w-[260px]'
: 'md:nd-w-0 md:nd-overflow-hidden md:nd-opacity-0',
'md:nd-sticky md:nd-top-16 md:nd-h-body md:nd-transition-[width,opacity]',
'max-md:nd-fixed max-md:nd-inset-y-0 max-md:nd-right-0 max-md:nd-bg-background max-md:nd-z-40 max-md:nd-pt-16 max-md:data-[open=false]:nd-hidden sm:max-md:nd-max-w-sm sm:max-md:nd-border-l'
)}
>
<ScrollArea className="nd-flex-1 md:nd-w-[260px]">
<div className="nd-flex nd-flex-col nd-pb-10 nd-pt-4 nd-text-medium max-md:nd-px-6 md:nd-pr-4 md:nd-pt-12 md:nd-text-sm">
<div className="nd-flex nd-flex-col nd-pb-10 nd-pt-4 md:nd-pr-4 md:nd-pt-12 max-md:nd-px-body">
{banner}
{tree.children.map((item, i) => (
<Node key={i} item={item} level={1} />
@@ -60,7 +59,7 @@ export function Sidebar({ banner, footer }: SidebarProps) {
</ScrollArea>
<div
className={cn(
'nd-flex nd-flex-row nd-items-center nd-gap-2 nd-border-t nd-py-2 max-md:nd-px-8',
'nd-flex nd-flex-row nd-items-center nd-gap-2 nd-border-t nd-py-2 max-md:nd-px-body',
!footer && 'md:nd-hidden'
)}
>
-26
View File
@@ -1,26 +0,0 @@
import { type HTMLAttributes } from 'react'
import {
Card,
Cards,
default_image_sizes,
Heading,
Link,
Pre,
Table
} from './mdx'
const defaultMdxComponents = {
Card: (p => <Card {...p} />) as typeof Card,
Cards: (p => <Cards {...p} />) as typeof Cards,
h1: (p: HTMLAttributes<HTMLHeadingElement>) => <Heading as="h1" {...p} />,
h2: (p: HTMLAttributes<HTMLHeadingElement>) => <Heading as="h2" {...p} />,
h3: (p: HTMLAttributes<HTMLHeadingElement>) => <Heading as="h3" {...p} />,
h4: (p: HTMLAttributes<HTMLHeadingElement>) => <Heading as="h4" {...p} />,
h5: (p: HTMLAttributes<HTMLHeadingElement>) => <Heading as="h5" {...p} />,
h6: (p: HTMLAttributes<HTMLHeadingElement>) => <Heading as="h6" {...p} />,
a: (p => <Link {...p} />) as typeof Link,
pre: (p => <Pre {...p} />) as typeof Pre,
table: (p => <Table {...p} />) as typeof Table
}
export { defaultMdxComponents as default, default_image_sizes }
+4 -1
View File
@@ -37,12 +37,15 @@ module.exports = {
theme: {
container: {
center: true,
padding: '1.5rem',
padding: '1rem',
screens: {
'2xl': '1400px'
}
},
extend: {
padding: {
body: '1rem'
},
fontSize: {
medium: '15px'
},
+12 -21
View File
@@ -1,24 +1,15 @@
import { defineConfig } from 'tsup'
import tsconfig from './tsconfig.json'
export default defineConfig([
{
entry: [
'./src/components/{type-table,roll-button,image-zoom,files,tabs,accordion,steps,search,inline-toc,callout}.{ts,tsx}',
'./src/components/dialog/{search,search-default,search-algolia}.tsx',
'./src/{i18n,layout,mdx,nav,not-found,page,provider}.{ts,tsx}'
],
external: ['next-docs-zeta'],
format: 'esm',
dts: true,
target: tsconfig.compilerOptions.target as 'es2016'
},
{
entry: ['./src/mdx-server.tsx'],
external: ['next-docs-zeta'],
format: 'esm',
bundle: false,
dts: true,
target: tsconfig.compilerOptions.target as 'es2016'
}
])
export default defineConfig({
entry: [
'./src/components/{type-table,roll-button,image-zoom,files,tabs,accordion,steps,search,inline-toc,callout}.{ts,tsx}',
'./src/components/dialog/{search,search-default,search-algolia}.tsx',
'./src/{i18n,layout,mdx,nav,not-found,page,provider}.{ts,tsx}'
],
outExtension: () => ({ js: '.js' }),
external: ['next-docs-zeta'],
format: 'esm',
dts: true,
target: tsconfig.compilerOptions.target as 'es2016'
})
+15 -7
View File
@@ -26,9 +26,13 @@
"import": "./dist/toc.mjs",
"types": "./dist/toc.d.ts"
},
"./search/*": {
"import": "./dist/search/*.mjs",
"types": "./dist/search/*.d.ts"
"./search/client": {
"import": "./dist/search/client.mjs",
"types": "./dist/search/client.d.ts"
},
"./search/server": {
"import": "./dist/search/server.js",
"types": "./dist/search/server.d.ts"
},
"./server": {
"import": "./dist/server.mjs",
@@ -47,16 +51,20 @@
"types": "./dist/contentlayer/configuration.d.ts"
},
"./middleware": {
"import": "./dist/middleware.mjs",
"import": "./dist/middleware.js",
"types": "./dist/middleware.d.ts"
},
"./mdx-plugins": {
"import": "./dist/mdx-plugins.mjs",
"types": "./dist/mdx-plugins.d.ts"
},
"./search-algolia/*": {
"import": "./dist/search-algolia/*.mjs",
"types": "./dist/search-algolia/*.d.ts"
"./search-algolia/client": {
"import": "./dist/search-algolia/client.mjs",
"types": "./dist/search-algolia/client.d.ts"
},
"./search-algolia/server": {
"import": "./dist/search-algolia/server.js",
"types": "./dist/search-algolia/server.d.ts"
}
},
"typesVersions": {
@@ -1,4 +1,4 @@
import { randomUUID } from 'crypto'
import { randomUUID } from 'node:crypto'
import type { StructuredData } from '@/mdx-plugins/remark-structure'
import type { SearchClient, SearchIndex } from 'algoliasearch'
import type { BaseIndex } from './shared'
+6 -17
View File
@@ -3,17 +3,6 @@ import FlexSearch from 'flexsearch'
import { NextResponse, type NextRequest } from 'next/server'
import type { SortedResult } from './shared'
let Response: typeof NextResponse = NextResponse
/**
* In some circumstances, importing `NextResponse` in external packages can cause problems
*
* You can call `setResponse` to replace the one bundled by Next.js
*/
export function setResponse(_new: typeof NextResponse) {
Response = _new
}
type SearchAPI = {
GET: (
request: NextRequest
@@ -78,7 +67,7 @@ export function createI18nSearchAPI<T extends 'simple' | 'advanced'>(
return map.get(locale)!.GET(request)
}
return Response.json([])
return NextResponse.json([])
}
}
}
@@ -136,7 +125,7 @@ export function initSearchAPI({ indexes, language }: SimpleOptions): SearchAPI {
const { searchParams } = request.nextUrl
const query = searchParams.get('query')
if (query == null) return Response.json([])
if (query == null) return NextResponse.json([])
const results = index.search(query, 5, {
enrich: true,
@@ -150,7 +139,7 @@ export function initSearchAPI({ indexes, language }: SimpleOptions): SearchAPI {
url: page.doc.url
}))
return Response.json(pages ?? [])
return NextResponse.json(pages ?? [])
}
}
}
@@ -243,7 +232,7 @@ export function initSearchAPIAdvanced({
const query = request.nextUrl.searchParams.get('query')
const tag = request.nextUrl.searchParams.get('tag')
if (query == null) return Response.json([])
if (query == null) return NextResponse.json([])
const results = index.search(query, 5, {
enrich: true,
@@ -251,7 +240,7 @@ export function initSearchAPIAdvanced({
limit: 6
})[0]
if (results == null) return Response.json([])
if (results == null) return NextResponse.json([])
const map = new Map<string, SortedResult[]>()
const sortedResult: SortedResult[] = []
@@ -294,7 +283,7 @@ export function initSearchAPIAdvanced({
sortedResult.push(...items)
}
return Response.json(sortedResult)
return NextResponse.json(sortedResult)
}
}
}
+19 -9
View File
@@ -1,21 +1,31 @@
import { defineConfig } from 'tsup'
import tsconfig from './tsconfig.json'
export default defineConfig({
entry: [
'src/{toc,link,breadcrumb,sidebar}.tsx',
'src/{middleware,server,mdx-plugins,build-page-tree}.ts',
'src/{search,search-algolia}/{client,server,shared}.ts',
'src/contentlayer/{index,configuration}.ts'
],
const shared = {
external: [
'@algolia/client-search',
'algoliasearch',
'contentlayer',
'next',
'unified'
],
format: 'esm',
dts: true,
target: tsconfig.compilerOptions.target as 'es2016'
})
}
export default defineConfig([
{
...shared,
entry: [
'src/{toc,link,breadcrumb,sidebar}.tsx',
'src/{server,mdx-plugins,build-page-tree}.ts',
'src/{search,search-algolia}/{client,shared}.ts',
'src/contentlayer/{index,configuration}.ts'
]
},
{
...shared,
entry: ['src/{search,search-algolia}/server.ts', 'src/middleware.ts'],
format: 'cjs'
}
])
+38 -28
View File
@@ -56,24 +56,27 @@ importers:
apps/docs:
dependencies:
'@radix-ui/react-dialog':
specifier: ^1.0.4
version: 1.0.4(@types/[email protected])(@types/[email protected])([email protected])([email protected].0)
'@mdx-js/loader':
specifier: ^2.3.0
version: 2.3.0([email protected].0)
'@mdx-js/react':
specifier: ^2.3.0
version: 2.3.0([email protected])
'@next/env':
specifier: ^13.5.6
version: 13.5.6
'@next/mdx':
specifier: ^13.5.6
version: 13.5.6(@mdx-js/[email protected])(@mdx-js/[email protected])
algoliasearch:
specifier: ^4.20.0
version: 4.20.0
autoprefixer:
specifier: 10.4.16
version: 10.4.16([email protected])
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
clsx:
specifier: ^2.0.0
version: 2.0.0
contentlayer:
specifier: ^0.3.4
version: 0.3.4
katex:
specifier: ^0.16.8
version: 0.16.8
@@ -83,9 +86,9 @@ importers:
next:
specifier: 13.5.6
version: 13.5.6(@babel/[email protected])([email protected])([email protected])
next-contentlayer:
specifier: ^0.3.4
version: 0.3.4([email protected])([email protected])([email protected])([email protected])
next-docs-mdx:
specifier: workspace:*
version: link:../../packages/next-docs-mdx
next-docs-ui:
specifier: workspace:*
version: link:../../packages/next-docs-ui
@@ -95,9 +98,6 @@ importers:
next-sitemap:
specifier: ^4.2.3
version: 4.2.3([email protected])
postcss:
specifier: 8.4.31
version: 8.4.31
react:
specifier: 18.2.0
version: 18.2.0
@@ -117,6 +117,9 @@ importers:
'@next/bundle-analyzer':
specifier: ^13.4.19
version: 13.4.19
'@types/mdx':
specifier: ^2.0.8
version: 2.0.8
'@types/node':
specifier: 18.17.5
version: 18.17.5
@@ -126,15 +129,21 @@ importers:
'@types/react-dom':
specifier: 18.2.1
version: 18.2.1
dotenv:
specifier: ^16.3.1
version: 16.3.1
autoprefixer:
specifier: 10.4.16
version: 10.4.16([email protected])
postcss:
specifier: 8.4.31
version: 8.4.31
tailwindcss:
specifier: 3.3.3
version: 3.3.3
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7([email protected])
webpack:
specifier: ^5.89.0
version: 5.89.0
examples/advanced:
dependencies:
@@ -381,6 +390,9 @@ importers:
'@mdx-js/loader':
specifier: ^2.3.0
version: 2.3.0([email protected])
'@mdx-js/mdx':
specifier: ^2.3.0
version: 2.3.0
'@mdx-js/react':
specifier: ^2.3.0
version: 2.3.0([email protected])
@@ -405,6 +417,9 @@ importers:
unified:
specifier: ^10.1.2
version: 10.1.2
webpack:
specifier: ^5.89.0
version: 5.89.0
packages/next-docs-ui:
dependencies:
@@ -1626,10 +1641,6 @@ packages:
- utf-8-validate
dev: true
/@next/[email protected]:
resolution: {integrity: sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==}
dev: false
/@next/[email protected]:
resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==}
@@ -3607,6 +3618,7 @@ packages:
picocolors: 1.0.0
postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
/[email protected]:
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
@@ -4114,11 +4126,6 @@ packages:
esutils: 2.0.3
dev: true
/[email protected]:
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
engines: {node: '>=12'}
dev: true
/[email protected]:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
dev: true
@@ -4643,6 +4650,7 @@ packages:
/[email protected]:
resolution: {integrity: sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==}
dev: true
/[email protected]:
resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
@@ -6434,7 +6442,7 @@ packages:
next: '*'
dependencies:
'@corex/deepmerge': 4.0.43
'@next/env': 13.4.19
'@next/env': 13.5.6
fast-glob: 3.3.1
minimist: 1.2.8
next: 13.5.6(@babel/[email protected])([email protected])([email protected])
@@ -6527,6 +6535,7 @@ packages:
/[email protected]:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
dev: true
/[email protected]:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
@@ -6922,6 +6931,7 @@ packages:
/[email protected]:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: true
/[email protected]:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+1
View File
@@ -2,6 +2,7 @@
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"cache": false,
"dependsOn": ["^build"],
"outputs": [
".contentlayer/**",