Very Popular
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171import { merge } from "../core/utils.ts";
import type { Data, Searcher, Site } from "../core.ts";
export interface Options { /** The helper name */ name: string;
/** The default order for the children */ order?: string;}
export const defaults: Options = { name: "nav",};
/** Register the plugin to enable the `search` helpers */export default function (userOptions?: Partial<Options>) { const options = merge(defaults, userOptions);
return (site: Site) => { const nav = new Nav(site.searcher); site.data(options.name, nav); site.addEventListener("beforeUpdate", () => nav.deleteCache()); };}
/** Search helper */export class Nav { #cache = new Map<string, NavData>(); #search: Searcher;
constructor(searcher: Searcher) { this.#search = searcher; }
/** Clear the cache (used after a change in watch mode) */ deleteCache() { this.#cache.clear(); }
menu(url?: "/", query?: string, sort?: string): NavData; menu(url: string, query?: string, sort?: string): NavData | undefined; menu(url = "/", query?: string, sort?: string): NavData | undefined { const id = JSON.stringify([query, sort]); let nav = this.#cache.get(id);
if (!nav) { nav = this.#buildNav(query, sort); this.#cache.set(id, nav); }
const parts = url.split("/").filter((part) => part !== ""); return searchData(parts, nav); }
breadcrumb(url: string, query?: string, sort?: string): NavData[] { let nav = this.menu(url, query, sort); const breadcrumb: NavData[] = [];
while (nav) { breadcrumb.unshift(nav); nav = nav.parent; }
return breadcrumb; }
/* Build the entire navigation tree */ #buildNav(query?: string, sort?: string): NavData { const nav: TempNavData = { slug: "", };
const dataPages = this.#search.pages(query, sort);
for (const data of dataPages) { const url = data.page?.outputPath;
if (!url) { continue; }
const parts = url.split("/").filter((part) => part !== ""); let part = parts.shift(); let current = nav;
while (part) { if (part === "index.html") { current.data = data; break; }
if (!current.children) { current.children = {}; }
if (!current.children[part]) { current = current.children[part] = { slug: part, parent: current, }; } else { current = current.children[part]; }
if (parts.length === 0) { current.data = data; break; }
part = parts.shift(); } }
return convert(nav); }}
export interface TempNavData { slug: string; data?: Data; children?: Record<string, TempNavData>; parent?: TempNavData;}
export interface NavData { slug: string; data?: Data; children?: NavData[]; parent?: NavData;}
function searchData(parts: string[], menu: NavData): NavData | undefined { const part = parts.shift();
if (!part) { return menu; }
if (menu.children?.length) { for (const child of menu.children) { if (child.slug === part) { return searchData(parts, child); } } }}
// Convert TempNavData to NavDatafunction convert(temp: TempNavData, parent?: NavData, order?: string): NavData { const data: NavData = { slug: temp.slug, data: temp.data, parent, };
data.children = temp.children ? Object.values(temp.children) .map((child) => convert(child, data, order)) .sort((a, b) => { if (!order) { return a.slug < b.slug ? -1 : 1; } return a.data?.[order] < b.data?.[order] ? -1 : 1; }) : undefined;
return data;}