@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.
Files changed (39) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/mdx/plugins/remark/remarkMermaid.js +12 -5
  4. package/dist/navigation/decoratedNavigationConfig.d.ts +2 -0
  5. package/dist/navigation/decoratedNavigationConfig.js +204 -0
  6. package/dist/navigation/filterDivisions.d.ts +65 -0
  7. package/dist/navigation/filterDivisions.js +697 -0
  8. package/dist/navigation/filterDivisions.test.d.ts +1 -0
  9. package/dist/navigation/filterDivisions.test.js +670 -0
  10. package/dist/navigation/getScopedNavForPath.test.d.ts +1 -0
  11. package/dist/navigation/getScopedNavForPath.test.js +135 -0
  12. package/dist/navigation/getVersionOrLanguageFromPath.d.ts +12 -0
  13. package/dist/navigation/getVersionOrLanguageFromPath.js +71 -0
  14. package/dist/navigation/getVersionOrLanguageFromPath.test.d.ts +1 -0
  15. package/dist/navigation/getVersionOrLanguageFromPath.test.js +133 -0
  16. package/dist/navigation/index.d.ts +5 -0
  17. package/dist/navigation/index.js +5 -0
  18. package/dist/navigation/isCommonPage.d.ts +8 -0
  19. package/dist/navigation/isCommonPage.js +36 -0
  20. package/dist/navigation/isCommonPage.test.d.ts +1 -0
  21. package/dist/navigation/isCommonPage.test.js +56 -0
  22. package/dist/navigation/pathsDifferByOneSegment.test.d.ts +1 -0
  23. package/dist/navigation/pathsDifferByOneSegment.test.js +91 -0
  24. package/dist/navigation/scopeNavToPath.d.ts +18 -0
  25. package/dist/navigation/scopeNavToPath.js +455 -0
  26. package/dist/navigation/scopeNavToPath.test.d.ts +1 -0
  27. package/dist/navigation/scopeNavToPath.test.js +117 -0
  28. package/dist/navigation/scopeOffPathDivisions.test.d.ts +1 -0
  29. package/dist/navigation/scopeOffPathDivisions.test.js +852 -0
  30. package/dist/navigation/scopeProductNav.test.d.ts +1 -0
  31. package/dist/navigation/scopeProductNav.test.js +288 -0
  32. package/dist/navigation/updateFirstHrefInDivision.d.ts +29 -0
  33. package/dist/navigation/updateFirstHrefInDivision.js +383 -0
  34. package/dist/paths/isEqualIgnoringLeadingSlash.d.ts +1 -0
  35. package/dist/paths/isEqualIgnoringLeadingSlash.js +21 -0
  36. package/dist/paths/isEqualIgnoringLeadingSlash.test.d.ts +1 -0
  37. package/dist/paths/isEqualIgnoringLeadingSlash.test.js +43 -0
  38. package/dist/tsconfig.build.tsbuildinfo +1 -1
  39. 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
+ }