import fs from 'fs'; import parse from '../lib/markdown.mjs'; import MDXPage from '../components/MDXPage'; import clientPromise from '@/lib/mddb'; import Layout from 'components/Layout'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/router.js'; import { NavGroup, NavItem, collectHeadings } from '@portaljs/core'; import { GetStaticProps, GetStaticPropsResult } from 'next'; import { CustomAppProps } from './_app.jsx'; import computeFields from '@/lib/computeFields'; import { getAuthorsDetails } from '@/lib/getAuthorsDetails'; export default function Page({ source, meta, sidebarTree }) { source = JSON.parse(source); const router = useRouter(); const [tableOfContents, setTableOfContents] = useState([]); useEffect(() => { const headingNodes = document.querySelectorAll( 'h2,h3' ) as NodeListOf; const toc = collectHeadings(headingNodes); setTableOfContents(toc ?? []); }, [router.asPath]); // update table of contents on route change with next/link return ( ); } interface SlugPageProps extends CustomAppProps { source: any; } export const getStaticProps: GetStaticProps = async ({ params, }): Promise> => { const urlPath = params?.slug ? (params.slug as string[]).join('/') : '/'; const mddb = await clientPromise; const dbFile = await mddb.getFileByUrl(urlPath); const filePath = dbFile!.file_path; const frontMatter = dbFile!.metadata ?? {}; // Temporary, so that blogs work properly if (dbFile.metadata.filetype === 'blog') { frontMatter.layout = 'blog'; frontMatter.authorsDetails = await getAuthorsDetails( dbFile.metadata.authors ); } // Temporary, docs pages should present the LHS sidebar if (dbFile.url_path.startsWith('docs')) { frontMatter.showSidebar = true; frontMatter.sidebarTreeFile = 'content/assets/sidebar.json'; } const source = fs.readFileSync(filePath, { encoding: 'utf-8' }); const { mdxSource } = await parse(source, 'mdx', {}); // TODO temporary replacement for contentlayer's computedFields const frontMatterWithComputedFields = await computeFields({ frontMatter, urlPath, filePath, source, }); let sidebarTree: Array = []; if (frontMatterWithComputedFields?.showSidebar) { let sidebarTreeFile = frontMatterWithComputedFields?.sidebarTreeFile; // Added this file funcionality so that we can control // which items appear in the sidebar and the order via // a json file if (sidebarTreeFile) { const tree = fs.readFileSync(sidebarTreeFile, { encoding: 'utf-8' }); sidebarTree = JSON.parse(tree); } else { const allPages = await mddb.getFiles({ extensions: ['md', 'mdx'] }); const pages = allPages.filter((p) => !p.metadata?.isDraft); pages.forEach((page) => { addPageToSitemap(page, sidebarTree); }); } } return { props: { source: JSON.stringify(mdxSource), meta: frontMatterWithComputedFields, sidebarTree, }, }; }; export async function getStaticPaths() { const mddb = await clientPromise; let allDocuments = await mddb.getFiles({ extensions: ['md', 'mdx'] }); const paths = allDocuments .filter((page) => page.metadata?.isDraft !== true) .map((page) => { const parts = page.url_path!.split('/'); return { params: { slug: parts } }; }); return { paths, fallback: false, }; } function capitalize(str: string) { return str.charAt(0).toUpperCase() + str.slice(1); } /* function addPageToGroup(page: MddbFile, sitemap: Array) { */ function addPageToSitemap(page: any, sitemap: Array) { const urlParts = page.url_path!.split('/').filter((part) => part); // don't add home page to the sitemap if (urlParts.length === 0) return; // top level, root pages if (urlParts.length === 1) { sitemap.push({ name: page.metadata?.title || urlParts[0], href: page.url_path, }); } else { // /blog/blogtest const nestingLevel = urlParts.length - 1; // 1 let currArray: Array = sitemap; for (let level = 0; level <= nestingLevel; level++) { if (level === nestingLevel) { currArray.push({ name: urlParts[level], href: page.url_path, }); continue; } const matchingGroup = currArray .filter(isNavGroup) .find( (group) => group.path !== undefined && page.url_path.startsWith(group.path) ); if (!matchingGroup) { const newGroup: NavGroup = { name: capitalize(urlParts[level]), path: urlParts.slice(0, level + 1).join('/'), level, children: [], }; currArray.push(newGroup); currArray = newGroup.children; } else { currArray = matchingGroup.children; } } } } function isNavGroup(item: NavItem | NavGroup): item is NavGroup { return (item as NavGroup).children !== undefined; }