Skip to content

Commit 198dbcc

Browse files
author
Dimitri POSTOLOV
authored
[v3] improve toc for remote content (#2375)
1 parent d0dd848 commit 198dbcc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1388
-926
lines changed

.changeset/slow-countries-sell.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'nextra-theme-docs': major
3+
'nextra': major
4+
---
5+
6+
use toc with JSX elements for remote content

docs/pages/about.mdx

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ Nextra is powered by these incredible open source projects:
2626
- https://pnpm.io
2727
- https://tailwindcss.com
2828
- https://github.com/pacocoursey/next-themes
29-
- https://github.com/hashicorp/next-mdx-remote
3029
- https://github.com/antfu/shikiji
3130
- https://github.com/nextapps-de/flexsearch
3231
- https://github.com/atomiks/rehype-pretty-code

packages/nextra-theme-blog/__test__/__fixture__/pageMap.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import type { BlogPageOpts } from '../../src'
1+
import type { PageOpts } from 'nextra'
2+
import type { NextraBlogTheme } from '../../src'
23

3-
export const indexOpts: BlogPageOpts = {
4+
export const indexOpts: PageOpts = {
45
filePath: 'index.mdx',
56
frontMatter: {
67
type: 'page',
@@ -61,7 +62,7 @@ export const indexOpts: BlogPageOpts = {
6162
title: 'Nextra'
6263
}
6364

64-
export const postsOpts: BlogPageOpts = {
65+
export const postsOpts: PageOpts = {
6566
filePath: 'index.md',
6667
frontMatter: {
6768
type: 'posts',
@@ -120,7 +121,7 @@ export const postsOpts: BlogPageOpts = {
120121
title: 'Random Thoughts'
121122
}
122123

123-
export const articleOpts: BlogPageOpts = {
124+
export const articleOpts: PageOpts = {
124125
filePath: 'aaron-swartz-a-programmable-web.mdx',
125126
frontMatter: {
126127
title: 'Notes on A Programmable Web by Aaron Swartz',
@@ -182,7 +183,7 @@ export const articleOpts: BlogPageOpts = {
182183
title: 'Notes on A Programmable Web by Aaron Swartz'
183184
}
184185

185-
export const config = {
186+
export const config: NextraBlogTheme = {
186187
readMore: 'Read More →',
187188
darkMode: true
188189
}

packages/nextra-theme-blog/src/article-layout.tsx

-19
This file was deleted.

packages/nextra-theme-blog/src/basic-layout.tsx

-28
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createContext, useContext } from 'react'
2+
import type { LayoutProps } from './types'
3+
4+
const BlogContext = createContext<LayoutProps | null>(null)
5+
6+
export const BlogProvider = BlogContext.Provider
7+
8+
export const useBlogContext = () => {
9+
const value = useContext(BlogContext)
10+
if (!value) {
11+
throw new Error('useBlogContext must be used within a BlogProvider')
12+
}
13+
return value
14+
}

packages/nextra-theme-blog/src/blog-context.tsx

-33
This file was deleted.

packages/nextra-theme-blog/src/index.tsx

+66-31
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,85 @@
11
import { ThemeProvider } from 'next-themes'
2+
import Head from 'next/head'
23
import type { NextraThemeLayoutProps } from 'nextra'
3-
import type { ReactElement, ReactNode } from 'react'
4-
import { ArticleLayout } from './article-layout'
4+
import { MDXProvider } from 'nextra/mdx'
5+
import { useRef } from 'react'
56
import { BlogProvider } from './blog-context'
67
import { DEFAULT_THEME } from './constants'
7-
import { PageLayout } from './page-layout'
8+
import { components, HeadingContext } from './mdx-theme'
9+
import Meta from './meta'
10+
import Nav from './nav'
811
import { PostsLayout } from './posts-layout'
9-
import type { LayoutProps } from './types'
12+
import type { BlogFrontMatter } from './types'
13+
import { isValidDate } from './utils/date'
1014

11-
const layoutMap = {
12-
post: ArticleLayout,
13-
page: PageLayout,
14-
posts: PostsLayout,
15-
tag: PostsLayout
16-
}
15+
const layoutSet = new Set(['post', 'page', 'posts', 'tag'])
1716

18-
const BlogLayout = ({
19-
config,
17+
export default function NextraLayout({
2018
children,
21-
opts
22-
}: LayoutProps & { children: ReactNode }): ReactElement => {
23-
const type = opts.frontMatter.type || 'post'
24-
const Layout = layoutMap[type]
25-
if (!Layout) {
19+
pageOpts,
20+
themeConfig
21+
}: NextraThemeLayoutProps<BlogFrontMatter>) {
22+
const config = { ...DEFAULT_THEME, ...themeConfig }
23+
24+
const ref = useRef<HTMLHeadingElement>(null)
25+
26+
const { title: pageTitle, frontMatter } = pageOpts
27+
28+
frontMatter.type ||= 'post'
29+
30+
const { type, date } = frontMatter
31+
if (!layoutSet.has(type)) {
2632
throw new Error(
2733
`nextra-theme-blog does not support the layout type "${type}" It only supports "post", "page", "posts" and "tag"`
2834
)
2935
}
30-
return (
31-
<BlogProvider opts={opts} config={config}>
32-
<Layout>{children}</Layout>
33-
</BlogProvider>
34-
)
35-
}
3636

37-
export default function Layout({
38-
children,
39-
...context
40-
}: NextraThemeLayoutProps) {
41-
const extendedConfig = { ...DEFAULT_THEME, ...context.themeConfig }
37+
if (date && !isValidDate(date)) {
38+
throw new Error(
39+
`Invalid date "${date}". Provide date in "YYYY/M/D", "YYYY/M/D H:m", "YYYY-MM-DD", "[YYYY-MM-DD]T[HH:mm]" or "[YYYY-MM-DD]T[HH:mm:ss.SSS]Z" format.`
40+
)
41+
}
42+
43+
const title = `${pageTitle}${config.titleSuffix || ''}`
44+
45+
const Footer = {
46+
post: () => (
47+
<>
48+
{config.postFooter}
49+
{config.comments}
50+
</>
51+
),
52+
posts: PostsLayout,
53+
tag: PostsLayout,
54+
page: null
55+
}[type]
4256

4357
return (
4458
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
45-
<BlogLayout config={extendedConfig} opts={context.pageOpts}>
46-
{children}
47-
</BlogLayout>
59+
<Head>
60+
<title>{title}</title>
61+
{config.head?.({ title, meta: frontMatter })}
62+
</Head>
63+
<BlogProvider value={{ config, opts: pageOpts }}>
64+
<article
65+
className="_container _prose max-md:_prose-sm dark:_prose-dark"
66+
dir="ltr"
67+
>
68+
<HeadingContext.Provider value={ref}>
69+
{pageOpts.hasJsxInH1 ? <h1 ref={ref} /> : null}
70+
{pageOpts.hasJsxInH1 ? null : <h1>{pageTitle}</h1>}
71+
{type === 'post' ? <Meta /> : <Nav />}
72+
73+
<MDXProvider components={{ ...components, ...config.components }}>
74+
{children}
75+
</MDXProvider>
76+
77+
{Footer && <Footer />}
78+
</HeadingContext.Provider>
79+
80+
{config.footer}
81+
</article>
82+
</BlogProvider>
4883
</ThemeProvider>
4984
)
5085
}

packages/nextra-theme-blog/src/mdx-theme.tsx

+18-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Link from 'next/link'
22
import { Code, Pre, Table, Td, Th, Tr } from 'nextra/components'
3-
import { MDXProvider } from 'nextra/mdx'
43
import type { Components } from 'nextra/mdx'
54
import type { ComponentProps, ReactElement, ReactNode, RefObject } from 'react'
65
import {
@@ -76,34 +75,22 @@ const A = ({ children, href = '', ...props }: ComponentProps<'a'>) => {
7675
)
7776
}
7877

79-
const useComponents = (): Components => {
80-
const { config } = useBlogContext()
81-
return {
82-
h1: H1,
83-
h2: props => <HeadingLink tag="h2" {...props} />,
84-
h3: props => <HeadingLink tag="h3" {...props} />,
85-
h4: props => <HeadingLink tag="h4" {...props} />,
86-
h5: props => <HeadingLink tag="h5" {...props} />,
87-
h6: props => <HeadingLink tag="h6" {...props} />,
88-
a: A,
89-
pre: ({ children, ...props }) => (
90-
<div className="_not-prose">
91-
<Pre {...props}>{children}</Pre>
92-
</div>
93-
),
94-
tr: Tr,
95-
th: Th,
96-
td: Td,
97-
table: props => <Table className="_not-prose" {...props} />,
98-
code: Code,
99-
...config.components
100-
}
101-
}
102-
103-
export const MDXTheme = ({
104-
children
105-
}: {
106-
children: ReactNode
107-
}): ReactElement => {
108-
return <MDXProvider components={useComponents()}>{children}</MDXProvider>
78+
export const components: Components = {
79+
h1: H1,
80+
h2: props => <HeadingLink tag="h2" {...props} />,
81+
h3: props => <HeadingLink tag="h3" {...props} />,
82+
h4: props => <HeadingLink tag="h4" {...props} />,
83+
h5: props => <HeadingLink tag="h5" {...props} />,
84+
h6: props => <HeadingLink tag="h6" {...props} />,
85+
a: A,
86+
pre: ({ children, ...props }) => (
87+
<div className="_not-prose">
88+
<Pre {...props}>{children}</Pre>
89+
</div>
90+
),
91+
tr: Tr,
92+
th: Th,
93+
td: Td,
94+
table: props => <Table className="_not-prose" {...props} />,
95+
code: Code
10996
}

packages/nextra-theme-blog/src/page-layout.tsx

-13
This file was deleted.

packages/nextra-theme-blog/src/posts-layout.tsx

+3-16
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
import Link from 'next/link'
22
import { useRouter } from 'next/router'
3-
import type { ReactElement, ReactNode } from 'react'
4-
import { BasicLayout } from './basic-layout'
3+
import type { ReactElement } from 'react'
54
import { useBlogContext } from './blog-context'
6-
import { MDXTheme } from './mdx-theme'
7-
import Nav from './nav'
85
import { collectPostsAndNavs } from './utils/collect'
96
import getTags from './utils/get-tags'
107

11-
export function PostsLayout({
12-
children
13-
}: {
14-
children: ReactNode
15-
}): ReactElement {
8+
export function PostsLayout(): ReactElement {
169
const { config, opts } = useBlogContext()
1710
const { posts } = collectPostsAndNavs({ config, opts })
1811
const router = useRouter()
@@ -63,11 +56,5 @@ export function PostsLayout({
6356
</div>
6457
)
6558
})
66-
return (
67-
<BasicLayout>
68-
<Nav />
69-
<MDXTheme>{children}</MDXTheme>
70-
{postList}
71-
</BasicLayout>
72-
)
59+
return <>{postList}</>
7360
}

packages/nextra-theme-blog/src/types.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ export interface NextraBlogTheme {
2525
titleSuffix?: string
2626
}
2727

28-
export type BlogPageOpts = PageOpts<BlogFrontMatter>
29-
3028
export type BlogFrontMatter = {
3129
author?: string
3230
back?: string
@@ -39,5 +37,5 @@ export type BlogFrontMatter = {
3937

4038
export interface LayoutProps {
4139
config: NextraBlogTheme
42-
opts: BlogPageOpts
40+
opts: PageOpts<BlogFrontMatter>
4341
}

0 commit comments

Comments
 (0)