diff --git a/packages/chronicle/src/lib/source.ts b/packages/chronicle/src/lib/source.ts index 581e3e44..5078a2c9 100644 --- a/packages/chronicle/src/lib/source.ts +++ b/packages/chronicle/src/lib/source.ts @@ -118,41 +118,60 @@ export function invalidate() { cachedNavMap = null; } -function getOrder(node: Node, orderMap: Map): number | undefined { - if (node.type === 'page') return orderMap.get(node.url); - if (node.type === 'folder' && node.index) return orderMap.get(node.index.url); +function getOrder(node: Node, pageOrderMap: Map, folderOrderMap: Map): number | undefined { + if (node.type === 'page') return pageOrderMap.get(node.url); + if (node.type === 'folder') { + if (node.index) { + const fromMeta = folderOrderMap.get(node.index.url); + if (fromMeta !== undefined) return fromMeta; + return pageOrderMap.get(node.index.url); + } + } return undefined; } -function sortNodes(nodes: Node[], orderMap: Map): Node[] { +function sortNodes(nodes: Node[], pageOrderMap: Map, folderOrderMap: Map): Node[] { return [...nodes] .map(n => n.type === 'folder' - ? ({ ...n, children: sortNodes(n.children, orderMap) } as Folder) + ? ({ ...n, children: sortNodes(n.children, pageOrderMap, folderOrderMap) } as Folder) : n ) .sort( (a, b) => - (getOrder(a, orderMap) ?? Number.MAX_SAFE_INTEGER) - - (getOrder(b, orderMap) ?? Number.MAX_SAFE_INTEGER) + (getOrder(a, pageOrderMap, folderOrderMap) ?? Number.MAX_SAFE_INTEGER) - + (getOrder(b, pageOrderMap, folderOrderMap) ?? Number.MAX_SAFE_INTEGER) ); } -function sortTreeByOrder(tree: Root, pages: { url: string; data: unknown }[]): Root { - const orderMap = new Map(); +function buildFolderOrderMap(metaFiles: { path: string; data: Record }[]): Map { + const map = new Map(); + for (const meta of metaFiles) { + const order = meta.data.order as number | undefined; + if (order === undefined) continue; + const folderUrl = '/' + meta.path.replace(/\/meta\.json$/, ''); + map.set(folderUrl, order); + } + return map; +} + +function sortTreeByOrder(tree: Root, pages: { url: string; data: unknown }[], metaFiles: { path: string; data: Record }[]): Root { + const pageOrderMap = new Map(); for (const page of pages) { const d = page.data as Record; const order = d.order as number | undefined; - if (order !== undefined) orderMap.set(page.url, order); - if (page.url === '/') orderMap.set('/', order ?? 0); + if (order !== undefined) pageOrderMap.set(page.url, order); + if (page.url === '/') pageOrderMap.set('/', order ?? 0); } - return { ...tree, children: sortNodes(tree.children, orderMap) }; + const folderOrderMap = buildFolderOrderMap(metaFiles); + return { ...tree, children: sortNodes(tree.children, pageOrderMap, folderOrderMap) }; } export async function getPageTree(): Promise { if (cachedTree) return cachedTree; const s = await getSource(); - cachedTree = sortTreeByOrder(s.pageTree as Root, s.getPages()); + const metaFiles = buildFiles().filter(f => f.type === 'meta') as { path: string; data: Record }[]; + cachedTree = sortTreeByOrder(s.pageTree as Root, s.getPages(), metaFiles); return cachedTree; } diff --git a/packages/chronicle/src/pages/DocsPage.tsx b/packages/chronicle/src/pages/DocsPage.tsx index 0492324a..768e3e0d 100644 --- a/packages/chronicle/src/pages/DocsPage.tsx +++ b/packages/chronicle/src/pages/DocsPage.tsx @@ -16,6 +16,18 @@ function getFirstPageUrl(nodes: Node[]): string | null { return null; } +function findFolderFirstPage(nodes: Node[], pathname: string): string | null { + for (const node of nodes) { + if (node.type === 'folder') { + const folderUrl = node.index?.url; + if (folderUrl === pathname) return getFirstPageUrl(node.children); + const found = findFolderFirstPage(node.children, pathname); + if (found) return found; + } + } + return null; +} + interface DocsPageProps { slug: string[]; } @@ -24,11 +36,18 @@ export function DocsPage({ slug }: DocsPageProps) { const { config, tree, page, isLoading, errorStatus } = usePageContext(); if (errorStatus === 404) { - const isContentRoot = config.content?.some(c => slug.length === 1 && slug[0] === c.dir); + const pathname = `/${slug.join('/')}`; + const contentConfig = config.content?.find(c => c.dir === slug[0]); + const isContentRoot = slug.length === 1 && slug[0] === contentConfig?.dir; + if (contentConfig?.index_page) { + return ; + } if (isContentRoot) { const firstUrl = getFirstPageUrl(tree.children); if (firstUrl) return ; } + const folderFirstUrl = findFolderFirstPage(tree.children, pathname); + if (folderFirstUrl) return ; return ; } if (errorStatus) return ; diff --git a/packages/chronicle/src/types/config.ts b/packages/chronicle/src/types/config.ts index b99628e7..23272462 100644 --- a/packages/chronicle/src/types/config.ts +++ b/packages/chronicle/src/types/config.ts @@ -86,6 +86,7 @@ const contentEntrySchema = z.object({ label: z.string().min(1), description: z.string().optional(), icon: z.string().optional(), + index_page: z.string().optional(), }) // Variants map to Apsara Badge color prop.