-
Notifications
You must be signed in to change notification settings - Fork 386
/
Copy pathgetRouteMeta.tsx
139 lines (121 loc) · 3.32 KB
/
getRouteMeta.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
/**
* While Next.js provides file-based routing, we still need to construct
* a sidebar for navigation and provide each markdown page
* previous/next links and titles. To do this, we construct a nested
* route object that is infinitely nestable.
*/
export type RouteTag =
| 'foundation'
| 'intermediate'
| 'advanced'
| 'experimental'
| 'deprecated';
export interface RouteItem {
/** Page title (for the sidebar) */
title: string;
/** Optional version flag for heading */
version?: 'canary' | 'major';
/** Optional page description for heading */
description?: string;
/* Additional meta info for page tagging */
tags?: RouteTag[];
/** Path to page */
path?: string;
/** Whether the entry is a heading */
heading?: boolean;
/** List of sub-routes */
routes?: RouteItem[];
/** Adds a section header above the route item */
hasSectionHeader?: boolean;
/** Title of section header */
sectionHeader?: string;
/** Whether it should be omitted in breadcrumbs */
skipBreadcrumb?: boolean;
}
export interface Routes {
/** List of routes */
routes: RouteItem[];
}
/** Routing metadata about a given route and it's siblings and parent */
export interface RouteMeta {
/** The previous route */
prevRoute?: RouteItem;
/** The next route */
nextRoute?: RouteItem;
/** The current route */
route?: RouteItem;
/** Trail of parent routes */
breadcrumbs?: RouteItem[];
/** Order in the section */
order?: number;
}
type TraversalContext = RouteMeta & {
currentIndex: number;
};
export function getRouteMeta(cleanedPath: string, routeTree: RouteItem) {
const breadcrumbs = getBreadcrumbs(cleanedPath, routeTree);
const ctx: TraversalContext = {
currentIndex: 0,
};
buildRouteMeta(cleanedPath, routeTree, ctx);
const {currentIndex: _, ...meta} = ctx;
return {
...meta,
breadcrumbs: breadcrumbs.length > 0 ? breadcrumbs : [routeTree],
};
}
// Performs a depth-first search to find the current route and its previous/next route
function buildRouteMeta(
searchPath: string,
currentRoute: RouteItem,
ctx: TraversalContext
) {
ctx.currentIndex++;
const {routes} = currentRoute;
if (ctx.route && !ctx.nextRoute) {
ctx.nextRoute = currentRoute;
}
if (currentRoute.path === searchPath) {
ctx.route = currentRoute;
ctx.order = ctx.currentIndex;
// If we've found a deeper match, reset the previously stored next route.
// TODO: this only works reliably if deeper matches are first in the tree.
// We should revamp all of this to be more explicit.
ctx.nextRoute = undefined;
}
if (!ctx.route) {
ctx.prevRoute = currentRoute;
}
if (!routes) {
return;
}
for (const route of routes) {
buildRouteMeta(searchPath, route, ctx);
}
}
// iterates the route tree from the current route to find its ancestors for breadcrumbs
function getBreadcrumbs(
path: string,
currentRoute: RouteItem,
breadcrumbs: RouteItem[] = []
): RouteItem[] {
if (currentRoute.path === path) {
return breadcrumbs;
}
if (!currentRoute.routes) {
return [];
}
for (const route of currentRoute.routes) {
const childRoute = getBreadcrumbs(path, route, [
...breadcrumbs,
currentRoute,
]);
if (childRoute?.length) {
return childRoute;
}
}
return [];
}