@mintlify/common 1.0.1022 → 1.0.1024
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mdx/plugins/remark/remarkMermaid.js +12 -5
- package/dist/navigation/decoratedNavigationConfig.d.ts +2 -0
- package/dist/navigation/decoratedNavigationConfig.js +204 -0
- package/dist/navigation/filterDivisions.d.ts +65 -0
- package/dist/navigation/filterDivisions.js +697 -0
- package/dist/navigation/filterDivisions.test.d.ts +1 -0
- package/dist/navigation/filterDivisions.test.js +670 -0
- package/dist/navigation/getScopedNavForPath.test.d.ts +1 -0
- package/dist/navigation/getScopedNavForPath.test.js +135 -0
- package/dist/navigation/getVersionOrLanguageFromPath.d.ts +12 -0
- package/dist/navigation/getVersionOrLanguageFromPath.js +71 -0
- package/dist/navigation/getVersionOrLanguageFromPath.test.d.ts +1 -0
- package/dist/navigation/getVersionOrLanguageFromPath.test.js +133 -0
- package/dist/navigation/index.d.ts +5 -0
- package/dist/navigation/index.js +5 -0
- package/dist/navigation/isCommonPage.d.ts +8 -0
- package/dist/navigation/isCommonPage.js +36 -0
- package/dist/navigation/isCommonPage.test.d.ts +1 -0
- package/dist/navigation/isCommonPage.test.js +56 -0
- package/dist/navigation/pathsDifferByOneSegment.test.d.ts +1 -0
- package/dist/navigation/pathsDifferByOneSegment.test.js +91 -0
- package/dist/navigation/scopeNavToPath.d.ts +18 -0
- package/dist/navigation/scopeNavToPath.js +455 -0
- package/dist/navigation/scopeNavToPath.test.d.ts +1 -0
- package/dist/navigation/scopeNavToPath.test.js +117 -0
- package/dist/navigation/scopeOffPathDivisions.test.d.ts +1 -0
- package/dist/navigation/scopeOffPathDivisions.test.js +852 -0
- package/dist/navigation/scopeProductNav.test.d.ts +1 -0
- package/dist/navigation/scopeProductNav.test.js +288 -0
- package/dist/navigation/updateFirstHrefInDivision.d.ts +29 -0
- package/dist/navigation/updateFirstHrefInDivision.js +383 -0
- package/dist/paths/isEqualIgnoringLeadingSlash.d.ts +1 -0
- package/dist/paths/isEqualIgnoringLeadingSlash.js +21 -0
- package/dist/paths/isEqualIgnoringLeadingSlash.test.d.ts +1 -0
- package/dist/paths/isEqualIgnoringLeadingSlash.test.js +43 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import { divisions, } from '@mintlify/validation';
|
|
2
|
+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
3
|
+
import { readObjectArrayProperty } from '../divisions/iterateObjectArrayProperty.js';
|
|
4
|
+
import { optionallyAddLeadingSlash } from '../fs/optionallySlash.js';
|
|
5
|
+
import { isAbsoluteUrl } from '../isAbsoluteUrl.js';
|
|
6
|
+
import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
|
|
7
|
+
import { checkNavAccess } from './checkNavAccess.js';
|
|
8
|
+
import { getFirstPageFromNavigation } from './getFirstPageFromNavigation.js';
|
|
9
|
+
import { isPage } from './isPage.js';
|
|
10
|
+
import { updateFirstHrefInDivision } from './updateFirstHrefInDivision.js';
|
|
11
|
+
const DIVISION_NAME_MAP = {
|
|
12
|
+
tabs: 'tab',
|
|
13
|
+
anchors: 'anchor',
|
|
14
|
+
versions: 'version',
|
|
15
|
+
languages: 'language',
|
|
16
|
+
dropdowns: 'dropdown',
|
|
17
|
+
menu: 'item',
|
|
18
|
+
products: 'product',
|
|
19
|
+
};
|
|
20
|
+
export const isActiveDivision = (item) => 'isActive' in item && item.isActive === true;
|
|
21
|
+
/**
|
|
22
|
+
* Reorders product items so that the item matching `currentProduct` is tried first,
|
|
23
|
+
* preserving the relative order of remaining items.
|
|
24
|
+
* Only meaningful when called with product division configs.
|
|
25
|
+
*/
|
|
26
|
+
function reorderForCurrentProduct(items, currentProduct) {
|
|
27
|
+
if (!currentProduct)
|
|
28
|
+
return items;
|
|
29
|
+
const idx = items.findIndex((item) => 'product' in item && item.product === currentProduct);
|
|
30
|
+
if (idx <= 0)
|
|
31
|
+
return items;
|
|
32
|
+
const match = items[idx];
|
|
33
|
+
if (!match)
|
|
34
|
+
return items;
|
|
35
|
+
return [match, ...items.slice(0, idx), ...items.slice(idx + 1)];
|
|
36
|
+
}
|
|
37
|
+
export const filterDivisions = ({ currentPath, currentVersion, currentLanguage, currentProduct, decoratedNav, userGroups, shouldUseDivisionMatch = true, cache, isPreview = false, }) => {
|
|
38
|
+
var _a, _b, _c;
|
|
39
|
+
let navigationDivisions = {
|
|
40
|
+
tabs: [],
|
|
41
|
+
anchors: [],
|
|
42
|
+
versions: [],
|
|
43
|
+
languages: [],
|
|
44
|
+
dropdowns: [],
|
|
45
|
+
menu: [],
|
|
46
|
+
products: [],
|
|
47
|
+
};
|
|
48
|
+
const firstHrefInVersion = new Map();
|
|
49
|
+
const firstHrefInLanguage = new Map();
|
|
50
|
+
function findPageInNavigation({ currentPath, entry, parentGroups, isPreview = false, ancestorDivisionNames = [], }) {
|
|
51
|
+
if (typeof entry !== 'object') {
|
|
52
|
+
return { page: undefined, groupsOrPages: undefined };
|
|
53
|
+
}
|
|
54
|
+
if (isPage(entry)) {
|
|
55
|
+
return checkNavAccess(entry, userGroups, isPreview) &&
|
|
56
|
+
isEqualIgnoringLeadingSlash(entry.href, currentPath)
|
|
57
|
+
? {
|
|
58
|
+
page: entry,
|
|
59
|
+
groupsOrPages: parentGroups,
|
|
60
|
+
}
|
|
61
|
+
: { page: undefined, groupsOrPages: undefined };
|
|
62
|
+
}
|
|
63
|
+
if ('pages' in entry) {
|
|
64
|
+
if ('root' in entry && typeof entry.root === 'object') {
|
|
65
|
+
const filteredParentGroups = parentGroups.length
|
|
66
|
+
? parentGroups
|
|
67
|
+
: entry.pages
|
|
68
|
+
.map((page) => filterInaccessiblePagesRecursive(page, userGroups, isPreview))
|
|
69
|
+
.filter((page) => page !== undefined);
|
|
70
|
+
const { page: foundPage, groupsOrPages } = findPageInNavigation({
|
|
71
|
+
currentPath,
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- root is a DecoratedNavigationPage
|
|
73
|
+
entry: entry.root,
|
|
74
|
+
parentGroups: filteredParentGroups,
|
|
75
|
+
isPreview,
|
|
76
|
+
ancestorDivisionNames,
|
|
77
|
+
});
|
|
78
|
+
if (foundPage) {
|
|
79
|
+
return { page: foundPage, groupsOrPages };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const page of entry.pages) {
|
|
83
|
+
if (typeof page === 'object') {
|
|
84
|
+
const filteredPages = entry.pages
|
|
85
|
+
.map((page) => filterInaccessiblePagesRecursive(page, userGroups, isPreview))
|
|
86
|
+
.filter((page) => page !== undefined);
|
|
87
|
+
const { page: foundPage, groupsOrPages } = findPageInNavigation({
|
|
88
|
+
currentPath,
|
|
89
|
+
entry: page,
|
|
90
|
+
parentGroups: parentGroups.length
|
|
91
|
+
? parentGroups
|
|
92
|
+
: filteredPages,
|
|
93
|
+
isPreview,
|
|
94
|
+
ancestorDivisionNames,
|
|
95
|
+
});
|
|
96
|
+
if (foundPage) {
|
|
97
|
+
return { page: foundPage, groupsOrPages };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if ('groups' in entry) {
|
|
103
|
+
const newParentGroups = filterInaccessiblePages(entry.groups, userGroups, isPreview);
|
|
104
|
+
for (const group of newParentGroups) {
|
|
105
|
+
const { page, groupsOrPages } = findPageInNavigation({
|
|
106
|
+
currentPath,
|
|
107
|
+
entry: group,
|
|
108
|
+
parentGroups: newParentGroups,
|
|
109
|
+
isPreview,
|
|
110
|
+
ancestorDivisionNames,
|
|
111
|
+
});
|
|
112
|
+
if (page) {
|
|
113
|
+
return { page, groupsOrPages };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const key of divisions) {
|
|
118
|
+
const subDivisions = readObjectArrayProperty(entry, key);
|
|
119
|
+
if (!subDivisions)
|
|
120
|
+
continue;
|
|
121
|
+
const iterationOrder = key === 'products' ? reorderForCurrentProduct(subDivisions, currentProduct) : subDivisions;
|
|
122
|
+
for (const subDivision of iterationOrder) {
|
|
123
|
+
if (shouldUseDivisionMatch &&
|
|
124
|
+
shouldSkipDivisionMatch({
|
|
125
|
+
subDivision,
|
|
126
|
+
key,
|
|
127
|
+
currentVersion,
|
|
128
|
+
currentLanguage,
|
|
129
|
+
subDivisions,
|
|
130
|
+
}))
|
|
131
|
+
continue;
|
|
132
|
+
const subDivisionName = findDivisionName(subDivision);
|
|
133
|
+
const newAncestorNames = subDivisionName
|
|
134
|
+
? [...ancestorDivisionNames, subDivisionName]
|
|
135
|
+
: ancestorDivisionNames;
|
|
136
|
+
const { page, groupsOrPages, menuDivision } = findPageInNavigation({
|
|
137
|
+
currentPath,
|
|
138
|
+
entry: subDivision,
|
|
139
|
+
parentGroups,
|
|
140
|
+
isPreview,
|
|
141
|
+
ancestorDivisionNames: newAncestorNames,
|
|
142
|
+
});
|
|
143
|
+
if (page) {
|
|
144
|
+
const filteredSubDivisions = subDivisions
|
|
145
|
+
.filter((division) => isDivisionAccessible(division, userGroups, isPreview))
|
|
146
|
+
.map((item) => {
|
|
147
|
+
const isActive = compareDivisions(item, subDivision);
|
|
148
|
+
if ('hidden' in item && item.hidden && !isActive)
|
|
149
|
+
return undefined;
|
|
150
|
+
return buildDivisionItem({
|
|
151
|
+
item,
|
|
152
|
+
isActive,
|
|
153
|
+
cache,
|
|
154
|
+
currentVersion,
|
|
155
|
+
currentLanguage,
|
|
156
|
+
userGroups,
|
|
157
|
+
divisionKey: key,
|
|
158
|
+
menuDivision,
|
|
159
|
+
isPreview,
|
|
160
|
+
parentDivisionNames: ancestorDivisionNames,
|
|
161
|
+
});
|
|
162
|
+
})
|
|
163
|
+
.filter(Boolean);
|
|
164
|
+
if ('global' in subDivision) {
|
|
165
|
+
upsertGlobalDivisions(navigationDivisions, subDivision.global);
|
|
166
|
+
}
|
|
167
|
+
if (filteredSubDivisions.length) {
|
|
168
|
+
navigationDivisions[key].push(...filteredSubDivisions);
|
|
169
|
+
return {
|
|
170
|
+
page,
|
|
171
|
+
groupsOrPages,
|
|
172
|
+
menuDivision: key === 'menu' ? filteredSubDivisions : undefined,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { page: undefined, groupsOrPages: undefined };
|
|
179
|
+
}
|
|
180
|
+
let groups = [];
|
|
181
|
+
const { page: page, groupsOrPages: filteredGroupsOrPages } = findPageInNavigation({
|
|
182
|
+
currentPath: currentPath === '/' || currentPath === ''
|
|
183
|
+
? ((_a = getFirstPageFromNavigation(decoratedNav)) === null || _a === void 0 ? void 0 : _a.href) || ''
|
|
184
|
+
: currentPath,
|
|
185
|
+
entry: decoratedNav,
|
|
186
|
+
parentGroups: [],
|
|
187
|
+
isPreview,
|
|
188
|
+
});
|
|
189
|
+
if (!page && decoratedNav) {
|
|
190
|
+
const { divisions, groupsOrPages } = findMostRelevantDivisions(currentPath, decoratedNav, currentVersion, currentLanguage, currentProduct, userGroups, shouldUseDivisionMatch, cache, isPreview);
|
|
191
|
+
navigationDivisions = divisions;
|
|
192
|
+
groups = groupsOrPages;
|
|
193
|
+
}
|
|
194
|
+
if (filteredGroupsOrPages && filteredGroupsOrPages.length)
|
|
195
|
+
groups = filteredGroupsOrPages;
|
|
196
|
+
if (navigationDivisions.versions.length) {
|
|
197
|
+
updateFirstHrefInDivision({
|
|
198
|
+
type: 'version',
|
|
199
|
+
currentPath,
|
|
200
|
+
entry: decoratedNav,
|
|
201
|
+
parentGroups: [],
|
|
202
|
+
nearestDivisionValue: undefined,
|
|
203
|
+
isActiveGroup: false,
|
|
204
|
+
inActiveDivision: false,
|
|
205
|
+
currentDivisionValue: (_b = navigationDivisions.versions.find((version) => version.isActive)) === null || _b === void 0 ? void 0 : _b.name,
|
|
206
|
+
allDivisionValues: navigationDivisions.versions.map((version) => version.name),
|
|
207
|
+
firstHrefInDivision: firstHrefInVersion,
|
|
208
|
+
navigationDivisions,
|
|
209
|
+
isHiddenPage: page === undefined,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (navigationDivisions.languages.length) {
|
|
213
|
+
updateFirstHrefInDivision({
|
|
214
|
+
type: 'language',
|
|
215
|
+
currentPath,
|
|
216
|
+
entry: decoratedNav,
|
|
217
|
+
parentGroups: [],
|
|
218
|
+
nearestDivisionValue: undefined,
|
|
219
|
+
isActiveGroup: false,
|
|
220
|
+
inActiveDivision: false,
|
|
221
|
+
currentDivisionValue: (_c = navigationDivisions.languages.find((language) => language.isActive)) === null || _c === void 0 ? void 0 : _c.language,
|
|
222
|
+
allDivisionValues: navigationDivisions.languages.map((language) => language.language),
|
|
223
|
+
firstHrefInDivision: firstHrefInLanguage,
|
|
224
|
+
navigationDivisions,
|
|
225
|
+
isHiddenPage: page === undefined,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (decoratedNav && 'global' in decoratedNav && decoratedNav.global) {
|
|
229
|
+
upsertGlobalDivisions(navigationDivisions, decoratedNav.global);
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
tabs: navigationDivisions.tabs,
|
|
233
|
+
anchors: navigationDivisions.anchors,
|
|
234
|
+
versions: navigationDivisions.versions,
|
|
235
|
+
languages: navigationDivisions.languages,
|
|
236
|
+
dropdowns: navigationDivisions.dropdowns,
|
|
237
|
+
products: navigationDivisions.products,
|
|
238
|
+
groupsOrPages: groups,
|
|
239
|
+
firstHrefInVersion,
|
|
240
|
+
firstHrefInLanguage,
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
function upsertGlobalDivisions(navigationDivisions, globalConfig) {
|
|
244
|
+
if (!globalConfig)
|
|
245
|
+
return;
|
|
246
|
+
Object.entries(navigationDivisions).forEach(([key, items]) => {
|
|
247
|
+
if (key in globalConfig) {
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
|
|
249
|
+
const globalItems = globalConfig[key];
|
|
250
|
+
if (globalItems) {
|
|
251
|
+
const filteredGlobalItems = globalItems.filter((item) => !item.hidden);
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
|
+
items.push(...filteredGlobalItems.map((item) => (Object.assign(Object.assign({ name: findDivisionName(item) }, item), { href: item.href, isActive: false, isGlobal: true }))));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function buildDivisionItem(param) {
|
|
259
|
+
var _a;
|
|
260
|
+
const { item, isActive, cache, currentVersion, currentLanguage, userGroups, divisionKey, menuDivision, isPreview = false, parentDivisionNames = [], } = param;
|
|
261
|
+
const { dropdownCache } = cache;
|
|
262
|
+
let href = '';
|
|
263
|
+
// TODO: support this for other divisions (versions, languages, etc.)
|
|
264
|
+
// check if items's subdivisions are dropdowns
|
|
265
|
+
if (dropdownCache.size && 'dropdowns' in item) {
|
|
266
|
+
const dropdowns = item.dropdowns;
|
|
267
|
+
const itemName = findDivisionName(item);
|
|
268
|
+
const parentContext = [...parentDivisionNames, itemName].filter(Boolean).join(':');
|
|
269
|
+
const dropdownKey = generateCacheKey(dropdowns, parentContext || undefined);
|
|
270
|
+
const dropdownHref = dropdownCache.get(dropdownKey);
|
|
271
|
+
if (!!dropdownKey && dropdownHref) {
|
|
272
|
+
href = optionallyAddLeadingSlash(dropdownHref);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const page = (_a = getFirstPageFromCurrentDivisions(item, currentVersion, currentLanguage, userGroups !== null && userGroups !== void 0 ? userGroups : new Set(), isPreview)) !== null && _a !== void 0 ? _a : getFirstPageFromNavigation(item, userGroups, true, isPreview);
|
|
276
|
+
const keysToDrop = [...divisions, 'groups', 'pages'];
|
|
277
|
+
const divisionItem = Object.fromEntries(Object.entries(item).filter(([key]) => !keysToDrop.includes(key)));
|
|
278
|
+
// if href is absolute url, use it, otherwise use the href from the first page
|
|
279
|
+
if (!href) {
|
|
280
|
+
const isUserDefinedHref = 'href' in item && isAbsoluteUrl(item.href);
|
|
281
|
+
href = isUserDefinedHref ? item.href : (page === null || page === void 0 ? void 0 : page.href) || '/';
|
|
282
|
+
// when users switch languages, we should stay on the same page
|
|
283
|
+
// instead of switching to the first page in the new language
|
|
284
|
+
if (!isUserDefinedHref && divisionKey === 'languages') {
|
|
285
|
+
href = '';
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const baseResult = Object.assign(Object.assign({ name: findDivisionName(item) }, divisionItem), { href: href, isActive, isGlobal: false });
|
|
289
|
+
// handle dropdowns for tabs and products with menu
|
|
290
|
+
if ((divisionKey === 'tabs' || divisionKey === 'products') && 'menu' in item) {
|
|
291
|
+
if (isActive && (menuDivision === null || menuDivision === void 0 ? void 0 : menuDivision.length)) {
|
|
292
|
+
return Object.assign(Object.assign({}, baseResult), { menu: menuDivision });
|
|
293
|
+
}
|
|
294
|
+
// non-active menu divisions
|
|
295
|
+
const parentDivisionName = findDivisionName(item);
|
|
296
|
+
const nonActiveMenu = item.menu
|
|
297
|
+
.filter((menuItem) => isDivisionAccessible(menuItem, userGroups || new Set(), isPreview))
|
|
298
|
+
.map((menuItem) => {
|
|
299
|
+
if (menuItem.hidden)
|
|
300
|
+
return undefined;
|
|
301
|
+
return buildDivisionItem({
|
|
302
|
+
item: menuItem,
|
|
303
|
+
isActive: false, // menu items are not active by default
|
|
304
|
+
cache,
|
|
305
|
+
currentVersion,
|
|
306
|
+
currentLanguage,
|
|
307
|
+
userGroups,
|
|
308
|
+
divisionKey: 'menu',
|
|
309
|
+
isPreview,
|
|
310
|
+
parentDivisionNames: [
|
|
311
|
+
...parentDivisionNames,
|
|
312
|
+
...(parentDivisionName ? [parentDivisionName] : []),
|
|
313
|
+
],
|
|
314
|
+
});
|
|
315
|
+
})
|
|
316
|
+
.filter((menuItem) => menuItem !== undefined);
|
|
317
|
+
return Object.assign(Object.assign({}, baseResult), { menu: nonActiveMenu });
|
|
318
|
+
}
|
|
319
|
+
return baseResult;
|
|
320
|
+
}
|
|
321
|
+
function getFirstPageFromCurrentDivisions(item, currentVersion, currentLanguage, userGroups, isPreview = false) {
|
|
322
|
+
var _a, _b;
|
|
323
|
+
if ('versions' in item && currentVersion) {
|
|
324
|
+
const selectedVersion = item.versions.find((version) => version.version === currentVersion && isDivisionAccessible(version, userGroups, isPreview));
|
|
325
|
+
if (selectedVersion) {
|
|
326
|
+
const selectedVersionNavigation = getNavigationFromDivision(selectedVersion);
|
|
327
|
+
if (!selectedVersionNavigation)
|
|
328
|
+
return undefined;
|
|
329
|
+
return ((_a = getFirstPageFromCurrentDivisions(selectedVersionNavigation, currentVersion, currentLanguage, userGroups, isPreview)) !== null && _a !== void 0 ? _a : getFirstPageFromNavigation(selectedVersionNavigation, userGroups, true, isPreview));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if ('languages' in item && currentLanguage) {
|
|
333
|
+
const selectedLanguage = item.languages.find((language) => language.language === currentLanguage &&
|
|
334
|
+
isDivisionAccessible(language, userGroups, isPreview));
|
|
335
|
+
if (selectedLanguage) {
|
|
336
|
+
const selectedLanguageNavigation = getNavigationFromDivision(selectedLanguage);
|
|
337
|
+
if (!selectedLanguageNavigation)
|
|
338
|
+
return undefined;
|
|
339
|
+
return ((_b = getFirstPageFromCurrentDivisions(selectedLanguageNavigation, currentVersion, currentLanguage, userGroups, isPreview)) !== null && _b !== void 0 ? _b : getFirstPageFromNavigation(selectedLanguageNavigation, userGroups, true, isPreview));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
function getNavigationFromDivision(item) {
|
|
345
|
+
for (const key of Object.keys(DIVISION_NAME_MAP)) {
|
|
346
|
+
if (key in item) {
|
|
347
|
+
return {
|
|
348
|
+
[key]: item[key],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if ('groups' in item)
|
|
353
|
+
return { groups: item.groups };
|
|
354
|
+
if ('pages' in item)
|
|
355
|
+
return { pages: item.pages };
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
function findDivisionName(node) {
|
|
359
|
+
if ('name' in node && typeof node.name === 'string')
|
|
360
|
+
return node.name;
|
|
361
|
+
const divisionName = Object.values(DIVISION_NAME_MAP).find((value) => value in node);
|
|
362
|
+
if (divisionName) {
|
|
363
|
+
const name = node[divisionName];
|
|
364
|
+
if (typeof name === 'string')
|
|
365
|
+
return name;
|
|
366
|
+
}
|
|
367
|
+
return '';
|
|
368
|
+
}
|
|
369
|
+
function filterInaccessiblePages(groups, userGroups, isPreview = false) {
|
|
370
|
+
if (!Array.isArray(groups) || !groups.length)
|
|
371
|
+
return [];
|
|
372
|
+
return groups
|
|
373
|
+
.map((group) => filterInaccessiblePagesRecursive(group, userGroups, isPreview))
|
|
374
|
+
.filter((group) => group !== undefined && !group.hidden);
|
|
375
|
+
}
|
|
376
|
+
function filterInaccessiblePagesRecursive(nav, userGroups, isPreview = false) {
|
|
377
|
+
if (typeof nav !== 'object')
|
|
378
|
+
return undefined;
|
|
379
|
+
if (isPage(nav)) {
|
|
380
|
+
return checkNavAccess(nav, userGroups, isPreview) ? nav : undefined;
|
|
381
|
+
}
|
|
382
|
+
if (nav.hidden)
|
|
383
|
+
return undefined;
|
|
384
|
+
const newNav = Object.assign({}, nav);
|
|
385
|
+
if ('pages' in nav) {
|
|
386
|
+
const filteredPages = nav.pages
|
|
387
|
+
.map((page) => filterInaccessiblePagesRecursive(page, userGroups, isPreview))
|
|
388
|
+
.filter((page) => page !== undefined);
|
|
389
|
+
let hasAccessibleRoot = false;
|
|
390
|
+
if ('root' in nav && typeof nav.root === 'object') {
|
|
391
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- root is a DecoratedNavigationPage
|
|
392
|
+
const rootPage = nav.root;
|
|
393
|
+
if (!filterInaccessiblePagesRecursive(rootPage, userGroups, isPreview)) {
|
|
394
|
+
newNav.root = undefined;
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
hasAccessibleRoot = true;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (filteredPages.length === 0 && !hasAccessibleRoot)
|
|
401
|
+
return undefined;
|
|
402
|
+
newNav.pages = filteredPages;
|
|
403
|
+
}
|
|
404
|
+
return newNav;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Return true if checkNavAccess passes on some page in provided entry and also groups or pages are not empty
|
|
408
|
+
*/
|
|
409
|
+
function isDivisionAccessible(entry, userGroups, isPreview = false) {
|
|
410
|
+
if (isPreview)
|
|
411
|
+
return true;
|
|
412
|
+
if (typeof entry !== 'object')
|
|
413
|
+
return false;
|
|
414
|
+
if (isPage(entry)) {
|
|
415
|
+
return checkNavAccess(entry, userGroups, isPreview);
|
|
416
|
+
}
|
|
417
|
+
if ('pages' in entry) {
|
|
418
|
+
if ('root' in entry && typeof entry.root === 'object') {
|
|
419
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- root is a DecoratedNavigationPage
|
|
420
|
+
const rootPage = entry.root;
|
|
421
|
+
if (isDivisionAccessible(rootPage, userGroups, isPreview)) {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (entry.pages.length === 0)
|
|
426
|
+
return false;
|
|
427
|
+
let hasAccessiblePage = false;
|
|
428
|
+
for (const page of entry.pages) {
|
|
429
|
+
if (typeof page === 'object') {
|
|
430
|
+
const isAccessible = isDivisionAccessible(page, userGroups, isPreview);
|
|
431
|
+
if (isAccessible) {
|
|
432
|
+
hasAccessiblePage = true;
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (!hasAccessiblePage)
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
if ('groups' in entry) {
|
|
441
|
+
if (entry.groups.length === 0)
|
|
442
|
+
return false;
|
|
443
|
+
if (entry.groups.every((group) => group.hidden))
|
|
444
|
+
return false;
|
|
445
|
+
let hasAccessibleGroup = false;
|
|
446
|
+
for (const group of entry.groups) {
|
|
447
|
+
const isAccessible = isDivisionAccessible(group, userGroups, isPreview);
|
|
448
|
+
if (isAccessible) {
|
|
449
|
+
hasAccessibleGroup = true;
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (!hasAccessibleGroup)
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
for (const key of divisions) {
|
|
457
|
+
const subDivisions = readObjectArrayProperty(entry, key);
|
|
458
|
+
if (!subDivisions)
|
|
459
|
+
continue;
|
|
460
|
+
if (subDivisions.every((item) => item.hidden))
|
|
461
|
+
return false;
|
|
462
|
+
if (!subDivisions.some((subDivision) => isDivisionAccessible(subDivision, userGroups, isPreview)))
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* This function will find all most relevant divisions and groups or pages
|
|
469
|
+
* for the current path if the current path directs to a page that is not listed in the navigation.
|
|
470
|
+
*
|
|
471
|
+
* Logic:
|
|
472
|
+
* 1. find the most relevant groups or pages
|
|
473
|
+
* 2. find the most relevant divisions based on the most relevant groups or pages
|
|
474
|
+
* 3. if there is no most relevant divisions, default to the first division
|
|
475
|
+
*
|
|
476
|
+
* Example:
|
|
477
|
+
* if the current path is 1-1-3, it should return all groups under "anchor" and
|
|
478
|
+
* return all divisions on the top level
|
|
479
|
+
* - divisions
|
|
480
|
+
* - [division_name]: "anchor"
|
|
481
|
+
* - groups:
|
|
482
|
+
* - group: "1"
|
|
483
|
+
* - pages: ["1-1", "1-2"]
|
|
484
|
+
*
|
|
485
|
+
* - group: "1-1"
|
|
486
|
+
* - pages: ["1-1-1", "1-1-2"]
|
|
487
|
+
*
|
|
488
|
+
* - group: "2"
|
|
489
|
+
* - pages: ["2-1", "2-2"]
|
|
490
|
+
*/
|
|
491
|
+
function findMostRelevantDivisions(currentPath, decoratedNav, currentVersion, currentLanguage, currentProduct, userGroups, shouldUseDivisionMatch, cache, isPreview = false) {
|
|
492
|
+
const navigationDivisions = {
|
|
493
|
+
tabs: [],
|
|
494
|
+
anchors: [],
|
|
495
|
+
versions: [],
|
|
496
|
+
languages: [],
|
|
497
|
+
dropdowns: [],
|
|
498
|
+
menu: [],
|
|
499
|
+
products: [],
|
|
500
|
+
};
|
|
501
|
+
let mostSpecificGroups;
|
|
502
|
+
let maxMatchedPrefix;
|
|
503
|
+
let isPageAccessible = true;
|
|
504
|
+
function findMostRelevantGroupsHelper(entry, parentGroups) {
|
|
505
|
+
if (typeof entry !== 'object')
|
|
506
|
+
return;
|
|
507
|
+
if (isPage(entry)) {
|
|
508
|
+
if (!isEqualIgnoringLeadingSlash(currentPath, entry.href) &&
|
|
509
|
+
checkNavAccess(entry, userGroups, isPreview)) {
|
|
510
|
+
const matchedPrefix = findMaxMatchingPrefix(currentPath, entry.href);
|
|
511
|
+
const isNewMaxPrefix = matchedPrefix.length > ((maxMatchedPrefix === null || maxMatchedPrefix === void 0 ? void 0 : maxMatchedPrefix.length) || 0);
|
|
512
|
+
const isSameMaxPrefix = matchedPrefix === maxMatchedPrefix;
|
|
513
|
+
if (isNewMaxPrefix) {
|
|
514
|
+
maxMatchedPrefix = matchedPrefix;
|
|
515
|
+
mostSpecificGroups = parentGroups;
|
|
516
|
+
}
|
|
517
|
+
else if (isSameMaxPrefix && maxMatchedPrefix === '/' && !mostSpecificGroups) {
|
|
518
|
+
mostSpecificGroups = parentGroups;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else if (isEqualIgnoringLeadingSlash(currentPath, entry.href)) {
|
|
522
|
+
isPageAccessible = checkNavAccess(entry, userGroups, isPreview, false);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if ('pages' in entry && Array.isArray(entry.pages)) {
|
|
526
|
+
const resolvedParentGroups = parentGroups.length
|
|
527
|
+
? parentGroups
|
|
528
|
+
: entry.pages
|
|
529
|
+
.map((page) => filterInaccessiblePagesRecursive(page, userGroups, isPreview))
|
|
530
|
+
.filter((page) => page !== undefined);
|
|
531
|
+
if ('root' in entry && typeof entry.root === 'object') {
|
|
532
|
+
findMostRelevantGroupsHelper(
|
|
533
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- root is a DecoratedNavigationPage
|
|
534
|
+
entry.root, resolvedParentGroups);
|
|
535
|
+
}
|
|
536
|
+
for (const page of entry.pages) {
|
|
537
|
+
if (typeof page === 'object') {
|
|
538
|
+
findMostRelevantGroupsHelper(page, resolvedParentGroups);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if ('groups' in entry && !isPage(entry)) {
|
|
543
|
+
const newParentGroups = filterInaccessiblePages(entry.groups, userGroups, isPreview);
|
|
544
|
+
for (const group of entry.groups) {
|
|
545
|
+
findMostRelevantGroupsHelper(group, newParentGroups);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
for (const key of divisions) {
|
|
549
|
+
const subDivisions = readObjectArrayProperty(entry, key);
|
|
550
|
+
if (!subDivisions)
|
|
551
|
+
continue;
|
|
552
|
+
for (const subDivision of subDivisions) {
|
|
553
|
+
if (shouldUseDivisionMatch &&
|
|
554
|
+
shouldSkipDivisionMatch({
|
|
555
|
+
subDivision,
|
|
556
|
+
key,
|
|
557
|
+
currentVersion,
|
|
558
|
+
currentLanguage,
|
|
559
|
+
subDivisions,
|
|
560
|
+
}))
|
|
561
|
+
continue;
|
|
562
|
+
findMostRelevantGroupsHelper(subDivision, parentGroups);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
findMostRelevantGroupsHelper(decoratedNav, []);
|
|
567
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
568
|
+
mostSpecificGroups = isPageAccessible ? mostSpecificGroups : [];
|
|
569
|
+
if (mostSpecificGroups) {
|
|
570
|
+
const isStrictlyEqual = (entry) => {
|
|
571
|
+
return (JSON.stringify(structuredClone(mostSpecificGroups)) ===
|
|
572
|
+
JSON.stringify(structuredClone(entry)));
|
|
573
|
+
};
|
|
574
|
+
// look for divisions based on the most specific groups
|
|
575
|
+
function findMostRelevantDivisionsHelper(entry, parentGroups, ancestorDivisionNames = []) {
|
|
576
|
+
if (typeof entry !== 'object')
|
|
577
|
+
return false;
|
|
578
|
+
if ('pages' in entry && Array.isArray(entry.pages)) {
|
|
579
|
+
return isStrictlyEqual(parentGroups.length
|
|
580
|
+
? parentGroups
|
|
581
|
+
: entry.pages
|
|
582
|
+
.map((page) => filterInaccessiblePagesRecursive(page, userGroups, isPreview))
|
|
583
|
+
.filter((page) => page !== undefined));
|
|
584
|
+
}
|
|
585
|
+
if ('groups' in entry && !isPage(entry)) {
|
|
586
|
+
const newParentGroups = filterInaccessiblePages(entry.groups, userGroups, isPreview);
|
|
587
|
+
return isStrictlyEqual(newParentGroups);
|
|
588
|
+
}
|
|
589
|
+
for (const key of divisions) {
|
|
590
|
+
const subDivisions = readObjectArrayProperty(entry, key);
|
|
591
|
+
if (!subDivisions)
|
|
592
|
+
continue;
|
|
593
|
+
const iterationOrder = key === 'products'
|
|
594
|
+
? reorderForCurrentProduct(subDivisions, currentProduct)
|
|
595
|
+
: subDivisions;
|
|
596
|
+
for (const subDivision of iterationOrder) {
|
|
597
|
+
if (shouldUseDivisionMatch &&
|
|
598
|
+
shouldSkipDivisionMatch({
|
|
599
|
+
subDivision,
|
|
600
|
+
key,
|
|
601
|
+
currentVersion,
|
|
602
|
+
currentLanguage,
|
|
603
|
+
subDivisions,
|
|
604
|
+
}))
|
|
605
|
+
continue;
|
|
606
|
+
const subDivisionName = findDivisionName(subDivision);
|
|
607
|
+
const newAncestorNames = subDivisionName
|
|
608
|
+
? [...ancestorDivisionNames, subDivisionName]
|
|
609
|
+
: ancestorDivisionNames;
|
|
610
|
+
const founded = findMostRelevantDivisionsHelper(subDivision, parentGroups, newAncestorNames);
|
|
611
|
+
/**
|
|
612
|
+
* display divisions if
|
|
613
|
+
* - the page is not accessible and we need to display all accessible divisions
|
|
614
|
+
* OR
|
|
615
|
+
* - the specific group has been found in the division
|
|
616
|
+
*/
|
|
617
|
+
if (founded || !isPageAccessible) {
|
|
618
|
+
const filteredSubDivisions = subDivisions
|
|
619
|
+
.filter((division) => isDivisionAccessible(division, userGroups, isPreview))
|
|
620
|
+
.map((item) => {
|
|
621
|
+
const isActive = compareDivisions(item, subDivision) && isPageAccessible === true;
|
|
622
|
+
if (item.hidden && !isActive)
|
|
623
|
+
return undefined;
|
|
624
|
+
return buildDivisionItem({
|
|
625
|
+
item,
|
|
626
|
+
isActive,
|
|
627
|
+
cache,
|
|
628
|
+
currentVersion,
|
|
629
|
+
currentLanguage,
|
|
630
|
+
userGroups,
|
|
631
|
+
divisionKey: key,
|
|
632
|
+
isPreview,
|
|
633
|
+
parentDivisionNames: ancestorDivisionNames,
|
|
634
|
+
});
|
|
635
|
+
})
|
|
636
|
+
.filter(Boolean);
|
|
637
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
638
|
+
navigationDivisions[key].push(...filteredSubDivisions);
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
findMostRelevantDivisionsHelper(decoratedNav, [], []);
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
divisions: navigationDivisions,
|
|
649
|
+
groupsOrPages: mostSpecificGroups || [],
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function findMaxMatchingPrefix(path1, path2) {
|
|
653
|
+
const minLength = Math.min(path1.length, path2.length);
|
|
654
|
+
let matchingLength = 0;
|
|
655
|
+
for (let i = 0; i < minLength; i++) {
|
|
656
|
+
if (path1[i] !== path2[i]) {
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
matchingLength++;
|
|
660
|
+
}
|
|
661
|
+
return path1.substring(0, matchingLength);
|
|
662
|
+
}
|
|
663
|
+
const compareDivisions = (item, subDivision) => {
|
|
664
|
+
return JSON.stringify(structuredClone(item)) === JSON.stringify(structuredClone(subDivision));
|
|
665
|
+
};
|
|
666
|
+
function shouldSkipDivisionMatch({ subDivision, key, currentVersion, currentLanguage, subDivisions, }) {
|
|
667
|
+
const isVersionsConfig = key === 'versions' && !!currentVersion;
|
|
668
|
+
const isLanguagesConfig = key === 'languages' && !!currentLanguage;
|
|
669
|
+
if (isVersionsConfig) {
|
|
670
|
+
const versionConfig = subDivision;
|
|
671
|
+
const availableVersions = subDivisions.map((d) => d.version);
|
|
672
|
+
return versionConfig.version !== currentVersion && availableVersions.includes(currentVersion);
|
|
673
|
+
}
|
|
674
|
+
if (isLanguagesConfig) {
|
|
675
|
+
const languageConfig = subDivision;
|
|
676
|
+
const availableLanguages = subDivisions.map((d) => d.language);
|
|
677
|
+
return (languageConfig.language !== currentLanguage && availableLanguages.includes(currentLanguage));
|
|
678
|
+
}
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* TODO: we should add cache for versions and languages in the future
|
|
683
|
+
* Generate a cache key for the dropdowns
|
|
684
|
+
* @param dropdowns - the dropdowns to generate a cache key for
|
|
685
|
+
* @param parentContext - optional parent context to make the key unique across different parent divisions
|
|
686
|
+
* @returns the cache key
|
|
687
|
+
*/
|
|
688
|
+
export function generateCacheKey(divisions, parentContext) {
|
|
689
|
+
const firstDivision = divisions[0];
|
|
690
|
+
if (!firstDivision)
|
|
691
|
+
return '';
|
|
692
|
+
const divisionName = Object.values(DIVISION_NAME_MAP).find((value) => value in firstDivision);
|
|
693
|
+
if (!divisionName)
|
|
694
|
+
return '';
|
|
695
|
+
const key = divisions.reduce((acc, division) => acc + division[divisionName], '');
|
|
696
|
+
return parentContext ? `${parentContext}:${key}` : key;
|
|
697
|
+
}
|