From 3b88f6b28d9a5bf5548fbbaa0762b598f39dcd30 Mon Sep 17 00:00:00 2001 From: nickfrosty Date: Thu, 26 Oct 2023 15:57:45 -0400 Subject: [PATCH] feat: added next prev record support --- src/pages/api/content/[[...slug]].ts | 58 ++++++++++++++++------------ src/types/index.ts | 12 +++--- src/utils/navItem.ts | 19 +++++++++ 3 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/pages/api/content/[[...slug]].ts b/src/pages/api/content/[[...slug]].ts index ba951fc1e..f364429af 100644 --- a/src/pages/api/content/[[...slug]].ts +++ b/src/pages/api/content/[[...slug]].ts @@ -3,8 +3,11 @@ * based on the provided url `slug` */ -import { SimpleRecordGroupName } from "@/types"; -import { computeNavItem } from "@/utils/navItem"; +import { NavItem, SimpleRecordGroupName } from "@/types"; +import { + generateFlatNavItemListing, + generateNavItemListing, +} from "@/utils/navItem"; import { allDeveloperGuides, allDeveloperResources, @@ -42,51 +45,58 @@ export default function handler( if (!records) return res.status(404).json({ notFound: true }); // define the formatted href value to search for + // note: this effectively enforces that only href's that start with "/developers" are supported const href = `${ slug[0].toLocaleLowerCase() == "docs" ? "" : "/developers" }/${slug.join("/")}`; - // note: this effectively enforces that only href's that start with "/developers" are supported + // create a flat listing of all the nav items in order to locate the next, current, and prev records + const flatNavItems = generateFlatNavItemListing( + generateNavItemListing(records), + ); - // init the record to be returned - let record; - - // locate the correct record requested (via the url param) - for (let i = 0; i < records.length; i++) { - // @ts-ignore - const navItem = computeNavItem(records[i]); + // initialize the NavItem record trackers + let current: NavItem | null = null; + let next: NavItem | null = null; + let prev: NavItem | null = null; + for (let i = 0; i < flatNavItems.length; i++) { // skip incorrect routes if ( - navItem.href != href && - navItem.href != `/${href}` && - records[i]?.altRoutes?.filter(route => route == href)?.[0] != href + flatNavItems[i].href != href && + flatNavItems[i].href != `/${href}` && + flatNavItems[i]?.altRoutes?.filter(route => route == href)?.[0] != href ) { continue; } - // set the requested record's data (weaving in the computed nav item data) - record = Object.assign(navItem, records[i]); - - /** - * todo: support next/prev type records - * note: this will likely require processing the nav records? - */ + current = flatNavItems[i]; + if (flatNavItems.length >= i - 1) prev = flatNavItems[i - 1]; + if (flatNavItems.length >= i + 1) next = flatNavItems[i + 1]; // break out of the loop and stop processing break; } + if (!current) return res.status(404).json({ notFound: true }); + + // locate full content record + let record = records.find( + item => + item._raw.sourceFilePath.toLowerCase() == current?.path?.toLowerCase(), + ); if (!record) return res.status(404).json({ notFound: true }); // remove the html formatted content (since it is undesired data to send over the wire) - // @ts-ignore - record.body = record.body.raw.trim(); + if (typeof record.body.raw !== "undefined") { + // @ts-ignore + record.body = record.body.raw.trim(); + } // todo: preprocess the body content? (if desired in the future) // todo: support sending related content records back to the client - // finally, return the json formatted listing of NavItems - return res.status(200).json(record); + // finally, return the json formatted listing of NavItems (with the next and prev records) + return res.status(200).json(Object.assign(current, record, { next, prev })); } diff --git a/src/types/index.ts b/src/types/index.ts index 07465e0cf..46f68a2e3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -15,7 +15,11 @@ export type SupportedDocTypes = Exclude; * @dev when adding new group names, ensure the desired support is added in all * other places the type `SimpleRecordGroupName` is used (e.g. api routes) */ -export type SimpleRecordGroupName = "docs" | "guides" | "resources" | "workshops"; +export type SimpleRecordGroupName = + | "docs" + | "guides" + | "resources" + | "workshops"; type NavItemBase = { id: String; @@ -24,10 +28,8 @@ type NavItemBase = { href?: String; sidebarSortOrder?: number; metaOnly?: boolean; - /** - * - */ - items?: Array; + /** List of alternate routes that should redirect to this same document */ + altRoutes?: string[] | undefined; }; export type NavItem = NavItemBase & { diff --git a/src/utils/navItem.ts b/src/utils/navItem.ts index fb023a629..66d179540 100644 --- a/src/utils/navItem.ts +++ b/src/utils/navItem.ts @@ -101,6 +101,24 @@ export function generateNavItemListing( return sortNavItems(navItems); } +/** + * Create a flat listing of all nav items provided + * + * note: normally, the provided `navItems` should be preprocessed by `generateNavItemListing` + */ +export function generateFlatNavItemListing( + navItems: Array, +): Array { + return navItems.flatMap(({ items, ...node }: NavItem) => { + if (typeof items !== "undefined") { + return [node as NavItem] + .concat(items) + .flatMap(children => generateFlatNavItemListing([children])); + } + return node; + }); +} + /** * */ @@ -175,6 +193,7 @@ export function computeNavItem( label: doc?.sidebarLabel || doc?.title, sidebarSortOrder: doc?.sidebarSortOrder, metaOnly: doc?.metaOnly, + altRoutes: doc.altRoutes, }; // compute an id based on the doc's path