UNPKG

5.42 kBJavaScriptView Raw
1// Copyright (c) 2018 Uber Technologies, Inc.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21import slug from 'slug'
22
23import Home from 'components/Home'
24import Search from 'components/Search'
25
26import mdRoutes from 'mdRoutes'
27import jsRoutes from 'jsRoutes'
28
29import { HOME_PATH } from 'config'
30
31const generatePaths = (children, parentPath, order = []) =>
32 children.map((child, rank) => {
33 const path = `${parentPath}/${child.path || slug(child.name, { lower: true })}`
34 // order represents the rank of each path in all of its parents.
35 // ie the 2nd child of the 3rd child of the 1st tree would be [0, 2, 1]
36 const updatedOrder = [...order, rank]
37 const updatedChild = { ...child, path, order: updatedOrder }
38
39 return child.children
40 ? { ...updatedChild, children: generatePaths(child.children, path, updatedOrder) }
41 : { ...updatedChild, hasToc: true }
42 })
43
44const compareOrders = (route1, route2) => {
45 // this comparison function determines if one route comes after the other
46 // in the table of content.
47 // to do that, it uses the 'order' property computed above.
48 // example: one route is the 2nd child of the 3rd child of the 1st tree - [0, 2, 1]
49 // the other one is the 3rd child of the 2nd child of the 1st tree - [0, 1, 2]
50 // the second route should come first, because it's in the 2nd child not the 3d.
51
52 const minLength = Math.min(route1.order.length, route2.order.length)
53 for (let i = 0; i < minLength; i++) {
54 if (route1.order[i] < route2.order[i]) {
55 return -1
56 }
57 if (route1.order[i] > route2.order[i]) {
58 return 1
59 }
60 }
61 return 0
62}
63
64const getNestedPath = d => (d.children ? getNestedPath(d.children[0]) : d.path)
65
66const reduction = cur =>
67 cur.children
68 ? [{ path: cur.path, redirect: getNestedPath(cur.children[0]) }, ...cur.children.map(reduction)]
69 : cur
70
71export const trees = [...(mdRoutes || []), ...(jsRoutes || [])].reduce(
72 (out, { name, path, children = [], data = [] }) => {
73 out[path] = { name, tree: generatePaths([...children, ...data], path) }
74 return out
75 },
76 {}
77)
78
79const routes = Object.keys(trees)
80 .reduce((out, key) => {
81 const { tree } = trees[key]
82 const reduced = tree.reduce((acc, cur) => acc.concat(flatten(reduction(cur))), [])
83 const final = [...reduced, reduced[0] && { path: key, redirect: reduced[0].path }].filter(
84 d => d
85 )
86 return out.concat(final)
87 }, [])
88 .sort((a, b) => {
89 // routes which are just redirections to documentation pages are pushed towards the bottom.
90 if (b.redirect) {
91 return -1
92 }
93 if (a.redirect) {
94 return 1
95 }
96 // documentation routes are sorted in the same order as in the table of contents.
97 // that order is not guaranteed without sorting them.
98 if (a.markdown && b.markdown) {
99 return compareOrders(a, b)
100 }
101 return 0
102 })
103
104let lastRouteWithContent
105const routesPrevNext = routes.reduce((prev, route, i) => {
106 // this adds to each route with documentation (.markdown property) the reference
107 // of the previous and next routes with documentation
108 prev.push(route)
109 if (route.markdown || route.component) {
110 if (lastRouteWithContent !== undefined) {
111 prev[lastRouteWithContent].next = {
112 name: route.name,
113 path: route.path
114 }
115 prev[i].prev = {
116 name: prev[lastRouteWithContent].name,
117 path: prev[lastRouteWithContent].path
118 }
119 }
120 lastRouteWithContent = i
121 }
122 return prev
123}, [])
124
125const flatRoutes = routesPrevNext.map(route => {
126 // this makes routes that have a redirect point to a route without one
127 // instead of having chain redirects.
128 // we don't use redirects in TOC anymore but these links may exist somewhere in the wild.
129 // will be deprecated in a few versions.
130 if (route.redirect) {
131 const directRoute = routes.find(r => r.path === route.redirect)
132 if (directRoute && directRoute.redirect) {
133 return {
134 ...route,
135 redirect: directRoute.redirect
136 }
137 }
138 }
139 return route
140})
141export default [
142 {
143 path: '/',
144 exact: true,
145 component: Home,
146 redirect: HOME_PATH === '/' ? null : HOME_PATH
147 },
148 {
149 path: '/search',
150 exact: true,
151 component: Search
152 },
153 ...flatRoutes
154]
155
156function flatten(arr) {
157 if (!arr.length) {
158 return arr
159 }
160 return arr.reduce((prev, curr) => prev.concat(Array.isArray(curr) ? flatten(curr) : curr), [])
161}