diff --git a/apps/docs/next.config.js b/apps/docs/next.config.js index f9d343147..76e8307c1 100644 --- a/apps/docs/next.config.js +++ b/apps/docs/next.config.js @@ -5,6 +5,15 @@ const nextConfig = { scrollRestoration: true, }, transpilePackages: ['@tldraw/utils'], + async redirects() { + return [ + { + source: '/:sectionId/ucg/:articleId', + destination: '/:sectionId/:articleId', + permanent: true, + }, + ] + }, } module.exports = nextConfig diff --git a/apps/docs/pages/[sectionId]/[categoryId]/[articleId].tsx b/apps/docs/pages/[sectionId]/[childId]/[articleId].tsx similarity index 80% rename from apps/docs/pages/[sectionId]/[categoryId]/[articleId].tsx rename to apps/docs/pages/[sectionId]/[childId]/[articleId].tsx index 69771946c..dfe814aec 100644 --- a/apps/docs/pages/[sectionId]/[categoryId]/[articleId].tsx +++ b/apps/docs/pages/[sectionId]/[childId]/[articleId].tsx @@ -18,7 +18,8 @@ import { GetStaticPaths, GetStaticProps } from 'next' import { MDXRemoteSerializeResult } from 'next-mdx-remote' import { useTheme } from 'next-themes' -interface Props { +export type ArticleProps = { + type: 'article' sidebar: SidebarContentList section: Section category: Category @@ -34,7 +35,7 @@ export default function ArticlePage({ article, links, sidebar, -}: Props) { +}: ArticleProps) { const theme = useTheme() return ( <> @@ -55,12 +56,13 @@ export default function ArticlePage({ export const getStaticPaths: GetStaticPaths = async () => { const sections = await getSections() - const paths: { params: { sectionId: string; categoryId: string; articleId: string } }[] = [] + const paths: { params: { sectionId: string; childId: string; articleId: string } }[] = [] for (const section of sections) { for (const category of section.categories) { + if (category.id === 'ucg') continue for (const articleId of category.articleIds) { - paths.push({ params: { sectionId: section.id, categoryId: category.id, articleId } }) + paths.push({ params: { sectionId: section.id, childId: category.id, articleId } }) } } } @@ -68,9 +70,9 @@ export const getStaticPaths: GetStaticPaths = async () => { return { paths, fallback: false } } -export const getStaticProps: GetStaticProps = async (ctx) => { +export const getStaticProps: GetStaticProps = async (ctx) => { const sectionId = ctx.params?.sectionId?.toString() as string - const categoryId = ctx.params?.categoryId?.toString() as string + const categoryId = ctx.params?.childId?.toString() as string const articleId = ctx.params?.articleId?.toString() if (!articleId) throw Error() @@ -81,5 +83,15 @@ export const getStaticProps: GetStaticProps = async (ctx) => { const links = await getLinks(articleId) const mdxSource = await getArticleSource(articleId) - return { props: { article, section, category, sidebar, links, mdxSource } } + return { + props: { + type: 'article', + article, + section, + category, + sidebar, + links, + mdxSource, + }, + } } diff --git a/apps/docs/pages/[sectionId]/[categoryId]/index.tsx b/apps/docs/pages/[sectionId]/[childId]/index.tsx similarity index 61% rename from apps/docs/pages/[sectionId]/[categoryId]/index.tsx rename to apps/docs/pages/[sectionId]/[childId]/index.tsx index 0c434aecd..467b47d5e 100644 --- a/apps/docs/pages/[sectionId]/[categoryId]/index.tsx +++ b/apps/docs/pages/[sectionId]/[childId]/index.tsx @@ -2,11 +2,14 @@ import { Breadcrumb } from '@/components/Breadcrumb' import { Mdx } from '@/components/Mdx' import { MetaHead } from '@/components/MetaHead' import { Sidebar } from '@/components/Sidebar' +import ArticlePage from '@/pages/[sectionId]/[childId]/[articleId]' import { Article, Category, Section, SidebarContentList } from '@/types/content-types' import { + getArticle, getArticleSource, getArticles, getCategory, + getLinks, getSection, getSections, } from '@/utils/content' @@ -15,8 +18,10 @@ import { GetStaticPaths, GetStaticProps } from 'next' import { MDXRemoteSerializeResult } from 'next-mdx-remote' import { useTheme } from 'next-themes' import Link from 'next/link' +import { ArticleProps } from './[articleId]' -type Props = { +type CategoryProps = { + type: 'category' sidebar: SidebarContentList section: Section category: Category @@ -24,15 +29,17 @@ type Props = { mdxSource: MDXRemoteSerializeResult | null } -export default function CategoryListPage({ - sidebar, - mdxSource, - articles, - section, - category, -}: Props) { +type ChildProps = CategoryProps | ArticleProps + +export default function CategoryListPage(props: ChildProps) { const theme = useTheme() + if (props.type === 'article') { + return + } + + const { sidebar, section, category, articles, mdxSource } = props + const ungrouped: Article[] = [] const groupedArticles = Object.fromEntries( category.groups.map((group) => [group.id, { group, articles: [] as Article[] }]) @@ -92,12 +99,18 @@ export default function CategoryListPage({ export const getStaticPaths: GetStaticPaths = async () => { const sections = await getSections() - const paths: { params: { sectionId: string; categoryId: string } }[] = [] + const paths: { params: { sectionId: string; childId: string } }[] = [] for (const section of sections) { if (section.categories) { for (const category of section.categories) { - paths.push({ params: { sectionId: section.id, categoryId: category.id } }) + paths.push({ params: { sectionId: section.id, childId: category.id } }) + + // Add paths for uncategorized articles as well + if (category.id !== 'ucg') continue + for (const articleId of category.articleIds) { + paths.push({ params: { sectionId: section.id, childId: articleId } }) + } } } } @@ -105,23 +118,53 @@ export const getStaticPaths: GetStaticPaths = async () => { return { paths, fallback: false } } -export const getStaticProps: GetStaticProps = async (ctx) => { +export const getStaticProps: GetStaticProps = async (ctx) => { const sectionId = ctx.params?.sectionId?.toString() as string - const categoryId = ctx.params?.categoryId?.toString() - if (!categoryId || !sectionId) throw Error() - - const sidebar = await getSidebarContentList({ - sectionId, - categoryId, - }) + const childId = ctx.params?.childId?.toString() + if (!childId || !sectionId) throw Error() const articles = await getArticles() const section = await getSection(sectionId) + + // If the path goes to an uncategorized article, show the article page + if (!section.categories.some((c) => c.id === childId)) { + const categoryId = 'ucg' + const articleId = childId + const sidebar = await getSidebarContentList({ sectionId, categoryId, articleId }) + const category = await getCategory(sectionId, categoryId) + const article = await getArticle(articleId) + const links = await getLinks(articleId) + const mdxSource = await getArticleSource(articleId) + return { + props: { + type: 'article', + sidebar, + section, + category, + article, + links, + mdxSource, + }, + } + } + + // Otherwise, show the category page + const categoryId = childId + const sidebar = await getSidebarContentList({ sectionId, categoryId }) const category = await getCategory(sectionId, categoryId) const categoryArticles = category.articleIds.map((id) => articles[id]) const article = articles[categoryId + '_index'] ?? null const mdxSource = article ? await getArticleSource(categoryId + '_index') : null - return { props: { sidebar, section, category, articles: categoryArticles, mdxSource } } + return { + props: { + type: 'category', + sidebar, + section, + category, + articles: categoryArticles, + mdxSource, + }, + } }