@mintlify/common 1.0.1170 → 1.0.1171
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/navigation/scopeNavToPath.js +72 -25
- package/dist/navigation/scopeOffPathDivisions.test.js +427 -3
- package/dist/navigation/scopePrecomputedTargets.test.d.ts +1 -0
- package/dist/navigation/scopePrecomputedTargets.test.js +175 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
1
12
|
import { locales } from '@mintlify/models';
|
|
2
13
|
import { generatePathToLanguageDict, generatePathToVersionDict } from '../divisions/index.js';
|
|
3
14
|
import { optionallyRemoveLeadingSlash } from '../fs/optionallySlash.js';
|
|
@@ -157,7 +168,10 @@ function divisionValue(division, key) {
|
|
|
157
168
|
function resolveStubTarget(division, key, ctx, usePrecomputedTarget) {
|
|
158
169
|
const targets = key === 'versions' ? ctx.versionTargets : ctx.languageTargets;
|
|
159
170
|
const value = divisionValue(division, key);
|
|
160
|
-
const
|
|
171
|
+
const precomputed = usePrecomputedTarget ? targets === null || targets === void 0 ? void 0 : targets.get(value) : undefined;
|
|
172
|
+
const resolved = precomputed && divisionContainsPath(division, precomputed.href) ? precomputed : undefined;
|
|
173
|
+
if (precomputed && !resolved)
|
|
174
|
+
return resolveVisibleTarget(division, ctx);
|
|
161
175
|
const fallback = pickStubTarget(division, ctx, DIVISION_TYPE_BY_KEY[key]);
|
|
162
176
|
// the jump-map walk records targets without access checks, so a precomputed
|
|
163
177
|
// entry only supplies the switch destination; hidden/groups still come from
|
|
@@ -204,19 +218,22 @@ function simpleDivisionLabel(division, key) {
|
|
|
204
218
|
// client's stateful dropdownCache hrefs, and the active product is client
|
|
205
219
|
// session state — divisions containing any of them stay whole
|
|
206
220
|
const STATEFUL_DIVISION_KEYS = new Set(['versions', 'languages', 'dropdowns', 'products']);
|
|
207
|
-
function
|
|
221
|
+
function containsDivision(node, keys) {
|
|
208
222
|
if (Array.isArray(node))
|
|
209
|
-
return node.some(
|
|
223
|
+
return node.some((entry) => containsDivision(entry, keys));
|
|
210
224
|
if (!isNavNode(node))
|
|
211
225
|
return false;
|
|
212
226
|
for (const [key, value] of Object.entries(node)) {
|
|
213
|
-
if (
|
|
227
|
+
if (keys.has(key) && Array.isArray(value))
|
|
214
228
|
return true;
|
|
215
|
-
if ((Array.isArray(value) || isNavNode(value)) &&
|
|
229
|
+
if ((Array.isArray(value) || isNavNode(value)) && containsDivision(value, keys))
|
|
216
230
|
return true;
|
|
217
231
|
}
|
|
218
232
|
return false;
|
|
219
233
|
}
|
|
234
|
+
const containsStatefulDivision = (node) => containsDivision(node, STATEFUL_DIVISION_KEYS);
|
|
235
|
+
const PRODUCT_KEYS = new Set(['products']);
|
|
236
|
+
const containsProducts = (node) => containsDivision(node, PRODUCT_KEYS);
|
|
220
237
|
// mirrors buildDivisionItem's anonymous href resolution (first page in
|
|
221
238
|
// getFirstPageFromNavigation order that is neither hidden nor group-gated);
|
|
222
239
|
// falls back to the first raw candidate so a division with no anonymously
|
|
@@ -244,6 +261,40 @@ function hasViewerVisiblePage(node, ctx) {
|
|
|
244
261
|
node, (_a = ctx.userGroups) !== null && _a !== void 0 ? _a : new Set(), true, ctx.isPreview === true) !== undefined);
|
|
245
262
|
}
|
|
246
263
|
const MENU_STUBBABLE_KEYS = new Set(['pages', 'groups']);
|
|
264
|
+
const NODE_LABEL_KEYS = ['version', 'language', 'dropdown', 'tab', 'anchor', 'item'];
|
|
265
|
+
function nodeLabel(node) {
|
|
266
|
+
for (const key of NODE_LABEL_KEYS) {
|
|
267
|
+
if (typeof node[key] === 'string')
|
|
268
|
+
return node[key];
|
|
269
|
+
}
|
|
270
|
+
return '';
|
|
271
|
+
}
|
|
272
|
+
const isContainerKey = (key) => CONTAINER_KEYS.some((containerKey) => containerKey === key);
|
|
273
|
+
function stubPageList(key, entries, label, ctx) {
|
|
274
|
+
const target = resolveVisibleTarget({ [key]: entries }, ctx);
|
|
275
|
+
if (!target)
|
|
276
|
+
return entries;
|
|
277
|
+
const page = target.isRaw ? target.href : makeStubPage(label, target);
|
|
278
|
+
return key === 'pages' ? [page] : [{ group: label, pages: [page] }];
|
|
279
|
+
}
|
|
280
|
+
function skeletonStub(node, ctx) {
|
|
281
|
+
if (typeof node.href === 'string')
|
|
282
|
+
return node;
|
|
283
|
+
const label = nodeLabel(node);
|
|
284
|
+
const stub = {};
|
|
285
|
+
for (const [key, value] of Object.entries(node)) {
|
|
286
|
+
if (Array.isArray(value) && isContainerKey(key)) {
|
|
287
|
+
stub[key] = value.map((entry) => (isNavNode(entry) ? skeletonStub(entry, ctx) : entry));
|
|
288
|
+
}
|
|
289
|
+
else if (Array.isArray(value) && (key === 'pages' || key === 'groups')) {
|
|
290
|
+
stub[key] = stubPageList(key, value, label, ctx);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
stub[key] = value;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return stub;
|
|
297
|
+
}
|
|
247
298
|
// inactive tabs/products still render their full hover menu, so menu items
|
|
248
299
|
// survive stubbing with their metadata and a single resolved page each
|
|
249
300
|
function stubMenuItem(item, ctx) {
|
|
@@ -252,9 +303,10 @@ function stubMenuItem(item, ctx) {
|
|
|
252
303
|
if (typeof item.href === 'string')
|
|
253
304
|
return item;
|
|
254
305
|
const subtreeKeys = Object.keys(item).filter((key) => STUB_STRIPPED_KEYS.has(key));
|
|
255
|
-
if (!subtreeKeys.length ||
|
|
306
|
+
if (!subtreeKeys.length || containsProducts(item))
|
|
256
307
|
return scopeNode(item, ctx);
|
|
257
|
-
|
|
308
|
+
if (subtreeKeys.some((key) => !MENU_STUBBABLE_KEYS.has(key)))
|
|
309
|
+
return skeletonStub(item, ctx);
|
|
258
310
|
const target = resolveVisibleTarget(item, ctx);
|
|
259
311
|
if (!target)
|
|
260
312
|
return item;
|
|
@@ -272,7 +324,8 @@ function stubMenuItem(item, ctx) {
|
|
|
272
324
|
function stubSimpleDivision(division, key, ctx) {
|
|
273
325
|
if (typeof division.href === 'string')
|
|
274
326
|
return division;
|
|
275
|
-
|
|
327
|
+
const { menu } = division, rest = __rest(division, ["menu"]);
|
|
328
|
+
if (containsStatefulDivision(rest) || containsProducts(menu))
|
|
276
329
|
return scopeNode(division, ctx);
|
|
277
330
|
const target = resolveVisibleTarget(division, ctx);
|
|
278
331
|
if (!target)
|
|
@@ -291,36 +344,30 @@ function stubSimpleDivision(division, key, ctx) {
|
|
|
291
344
|
: [makeStubPage(simpleDivisionLabel(division, key), target)];
|
|
292
345
|
return stub;
|
|
293
346
|
}
|
|
294
|
-
function
|
|
295
|
-
const
|
|
347
|
+
function scopeSiblingArray(entries, ctx, stub) {
|
|
348
|
+
const containsPath = entries.map((entry) => isNavNode(entry) && divisionContainsPath(entry, ctx.currentPath));
|
|
296
349
|
// when the path-containing division is invisible to the viewer (hidden-only
|
|
297
350
|
// or group-gated), the client's active-division fallback lands on a sibling
|
|
298
351
|
// instead — that sibling must keep its real subtree, so nothing is stubbed
|
|
299
|
-
const
|
|
300
|
-
const
|
|
352
|
+
const allContainingVisible = entries.every((entry, index) => !containsPath[index] || (isNavNode(entry) && hasViewerVisiblePage(entry, ctx)));
|
|
353
|
+
const canStub = containsPath.some(Boolean) && allContainingVisible;
|
|
301
354
|
return entries.map((entry, index) => {
|
|
302
355
|
if (!isNavNode(entry))
|
|
303
356
|
return entry;
|
|
304
357
|
// without a path match the active division is unknowable, so nothing is stubbed
|
|
305
|
-
if (!
|
|
358
|
+
if (!canStub || containsPath[index])
|
|
306
359
|
return scopeNode(entry, ctx);
|
|
307
|
-
return
|
|
360
|
+
return stub(entry);
|
|
308
361
|
});
|
|
309
362
|
}
|
|
363
|
+
function scopeSimpleDivisionArray(entries, key, ctx) {
|
|
364
|
+
return scopeSiblingArray(entries, ctx, (entry) => stubSimpleDivision(entry, key, ctx));
|
|
365
|
+
}
|
|
310
366
|
// the same collapse applied to stubbed tabs' menus, but inside the active
|
|
311
|
-
// tab/anchor:
|
|
367
|
+
// tab/anchor: items containing the path keep their subtree (they feed the
|
|
312
368
|
// sidebar), siblings survive as hover-menu metadata plus one resolved page
|
|
313
369
|
function scopeMenuArray(entries, ctx) {
|
|
314
|
-
|
|
315
|
-
const activeEntry = activeIndex === -1 ? undefined : entries[activeIndex];
|
|
316
|
-
const activeVisible = isNavNode(activeEntry) && hasViewerVisiblePage(activeEntry, ctx);
|
|
317
|
-
return entries.map((entry, index) => {
|
|
318
|
-
if (!isNavNode(entry))
|
|
319
|
-
return entry;
|
|
320
|
-
if (!activeVisible || index === activeIndex)
|
|
321
|
-
return scopeNode(entry, ctx);
|
|
322
|
-
return stubMenuItem(entry, ctx);
|
|
323
|
-
});
|
|
370
|
+
return scopeSiblingArray(entries, ctx, (entry) => stubMenuItem(entry, ctx));
|
|
324
371
|
}
|
|
325
372
|
function scopeDivisionArray(entries, key, ctx) {
|
|
326
373
|
if (ctx.skipVersionLanguageStubs) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { locales } from '@mintlify/models';
|
|
1
2
|
import { filterDivisions } from './filterDivisions.js';
|
|
2
3
|
import { getAllPathsInDecoratedNav } from './getAllPathsInDecoratedNav.js';
|
|
3
|
-
import { getScopedNavForPath } from './scopeNavToPath.js';
|
|
4
|
+
import { getScopedNavForPath, scopeDocsConfigNavToPath } from './scopeNavToPath.js';
|
|
4
5
|
const multiTabNav = {
|
|
5
6
|
tabs: [
|
|
6
7
|
{
|
|
@@ -459,6 +460,247 @@ const hiddenMenuTabNav = {
|
|
|
459
460
|
},
|
|
460
461
|
],
|
|
461
462
|
};
|
|
463
|
+
const autosummary = (base, names) => names.map((name) => ({ title: name, href: `/${base}/_autosummary/${name}` }));
|
|
464
|
+
const productMenuNav = {
|
|
465
|
+
tabs: [
|
|
466
|
+
{
|
|
467
|
+
tab: 'Documentation',
|
|
468
|
+
menu: [
|
|
469
|
+
{
|
|
470
|
+
item: 'Home',
|
|
471
|
+
pages: [{ title: 'Home', href: '/home' }],
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
item: 'Engine',
|
|
475
|
+
versions: [
|
|
476
|
+
{
|
|
477
|
+
version: 'v3',
|
|
478
|
+
default: true,
|
|
479
|
+
dropdowns: [
|
|
480
|
+
{
|
|
481
|
+
dropdown: 'Overview',
|
|
482
|
+
groups: [
|
|
483
|
+
{
|
|
484
|
+
group: 'Start',
|
|
485
|
+
pages: [
|
|
486
|
+
{ title: 'Engine', href: '/engine/v3/overview' },
|
|
487
|
+
{ title: 'Install', href: '/engine/v3/install' },
|
|
488
|
+
],
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
dropdown: 'Python API',
|
|
494
|
+
groups: [
|
|
495
|
+
{
|
|
496
|
+
group: 'Engine',
|
|
497
|
+
pages: autosummary('engine/v3/py', ['Engine.a', 'Engine.b']),
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
group: 'Part',
|
|
501
|
+
pages: autosummary('engine/v3/py', ['Part.a', 'Part.b']),
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
version: 'v2',
|
|
509
|
+
groups: [
|
|
510
|
+
{
|
|
511
|
+
group: 'Overview',
|
|
512
|
+
pages: [
|
|
513
|
+
{ title: 'Engine v2', href: '/engine/v2/overview' },
|
|
514
|
+
...autosummary('engine/v2', ['Engine.a', 'Engine.b']),
|
|
515
|
+
],
|
|
516
|
+
},
|
|
517
|
+
],
|
|
518
|
+
},
|
|
519
|
+
],
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
item: 'Studio',
|
|
523
|
+
versions: [
|
|
524
|
+
{
|
|
525
|
+
version: 'v3',
|
|
526
|
+
default: true,
|
|
527
|
+
dropdowns: [
|
|
528
|
+
{
|
|
529
|
+
dropdown: 'Overview',
|
|
530
|
+
groups: [
|
|
531
|
+
{
|
|
532
|
+
group: 'Start',
|
|
533
|
+
pages: [
|
|
534
|
+
{
|
|
535
|
+
title: 'Draft',
|
|
536
|
+
href: '/studio/v3/draft',
|
|
537
|
+
hidden: true,
|
|
538
|
+
},
|
|
539
|
+
{ title: 'Studio', href: '/studio/v3/overview' },
|
|
540
|
+
],
|
|
541
|
+
},
|
|
542
|
+
],
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
dropdown: 'API',
|
|
546
|
+
groups: [
|
|
547
|
+
{
|
|
548
|
+
group: 'Model',
|
|
549
|
+
pages: [
|
|
550
|
+
{
|
|
551
|
+
title: 'Internal',
|
|
552
|
+
href: '/studio/v3/api/internal',
|
|
553
|
+
groups: ['staff'],
|
|
554
|
+
},
|
|
555
|
+
...autosummary('studio/v3/api', ['Model.a', 'Model.b']),
|
|
556
|
+
],
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
},
|
|
560
|
+
],
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
version: 'Legacy',
|
|
564
|
+
hidden: true,
|
|
565
|
+
pages: [{ title: 'Legacy', href: '/studio/legacy/overview' }],
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
item: 'Audio',
|
|
571
|
+
dropdowns: [
|
|
572
|
+
{
|
|
573
|
+
dropdown: 'Overview',
|
|
574
|
+
pages: [{ title: 'Audio', href: '/audio/overview' }],
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
dropdown: 'API',
|
|
578
|
+
pages: [
|
|
579
|
+
{ title: 'API', href: '/audio/api/overview' },
|
|
580
|
+
...autosummary('audio/api', ['Clip.a', 'Clip.b']),
|
|
581
|
+
],
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
item: 'Internal',
|
|
587
|
+
versions: [
|
|
588
|
+
{
|
|
589
|
+
version: 'v3',
|
|
590
|
+
pages: [
|
|
591
|
+
{
|
|
592
|
+
title: 'Runbook',
|
|
593
|
+
href: '/internal/runbook',
|
|
594
|
+
groups: ['staff'],
|
|
595
|
+
},
|
|
596
|
+
],
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
tab: 'Knowledge base',
|
|
604
|
+
pages: [{ title: 'Blog', href: '/blog' }],
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
};
|
|
608
|
+
const page = (href) => ({ title: href, href });
|
|
609
|
+
const versionedItem = (item, v3, v2, hidden) => (Object.assign(Object.assign({ item }, (hidden ? { hidden } : {})), { versions: [
|
|
610
|
+
{ version: 'v3', default: true, groups: [{ group: `${item} v3`, pages: v3.map(page) }] },
|
|
611
|
+
{ version: 'v2', groups: [{ group: `${item} v2`, pages: v2.map(page) }] },
|
|
612
|
+
] }));
|
|
613
|
+
const crossListedMenuNav = {
|
|
614
|
+
tabs: [
|
|
615
|
+
{
|
|
616
|
+
tab: 'Docs',
|
|
617
|
+
menu: [
|
|
618
|
+
{ item: 'Home', pages: [page('/home')] },
|
|
619
|
+
versionedItem('Studio', ['/studio/v3/a', '/studio/v3/b'], ['/studio/v2/a', '/shared']),
|
|
620
|
+
versionedItem('Engine', ['/engine/v3/a', '/shared', '/engine/v3/c'], ['/engine/v2/a']),
|
|
621
|
+
versionedItem('Solver', ['/solver/v3/a'], ['/solver/v2/a']),
|
|
622
|
+
],
|
|
623
|
+
},
|
|
624
|
+
{ tab: 'Other', pages: [page('/other')] },
|
|
625
|
+
],
|
|
626
|
+
};
|
|
627
|
+
const crossListedHiddenMenuNav = {
|
|
628
|
+
tabs: [
|
|
629
|
+
{
|
|
630
|
+
tab: 'Docs',
|
|
631
|
+
menu: [
|
|
632
|
+
versionedItem('Studio', ['/studio/v3/a'], ['/studio/v2/a', '/shared'], true),
|
|
633
|
+
versionedItem('Engine', ['/engine/v3/a', '/shared'], ['/engine/v2/a']),
|
|
634
|
+
versionedItem('Solver', ['/solver/v3/a'], ['/solver/v2/a']),
|
|
635
|
+
],
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
};
|
|
639
|
+
const crossListedLanguageMenuNav = {
|
|
640
|
+
tabs: [
|
|
641
|
+
{
|
|
642
|
+
tab: 'Docs',
|
|
643
|
+
menu: ['Studio', 'Engine', 'Solver'].map((item) => ({
|
|
644
|
+
item,
|
|
645
|
+
languages: [
|
|
646
|
+
{
|
|
647
|
+
language: 'en',
|
|
648
|
+
default: true,
|
|
649
|
+
groups: [
|
|
650
|
+
{
|
|
651
|
+
group: `${item} en`,
|
|
652
|
+
pages: [
|
|
653
|
+
page(`/${item.toLowerCase()}/en`),
|
|
654
|
+
...(item === 'Engine' ? [page('/shared')] : []),
|
|
655
|
+
],
|
|
656
|
+
},
|
|
657
|
+
],
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
language: 'fr',
|
|
661
|
+
groups: [
|
|
662
|
+
{
|
|
663
|
+
group: `${item} fr`,
|
|
664
|
+
pages: [
|
|
665
|
+
page(`/${item.toLowerCase()}/fr`),
|
|
666
|
+
...(item === 'Studio' ? [page('/shared')] : []),
|
|
667
|
+
],
|
|
668
|
+
},
|
|
669
|
+
],
|
|
670
|
+
},
|
|
671
|
+
],
|
|
672
|
+
})),
|
|
673
|
+
},
|
|
674
|
+
],
|
|
675
|
+
};
|
|
676
|
+
const crossListedPlainMenuNav = {
|
|
677
|
+
tabs: [
|
|
678
|
+
{
|
|
679
|
+
tab: 'Docs',
|
|
680
|
+
menu: [
|
|
681
|
+
{ item: 'Guides', groups: [{ group: 'Guides', pages: [page('/guides/a'), page('/faq')] }] },
|
|
682
|
+
{
|
|
683
|
+
item: 'Reference',
|
|
684
|
+
groups: [{ group: 'Reference', pages: [page('/ref/a'), page('/faq')] }],
|
|
685
|
+
},
|
|
686
|
+
{ item: 'Other', pages: [page('/other/a')] },
|
|
687
|
+
],
|
|
688
|
+
},
|
|
689
|
+
],
|
|
690
|
+
};
|
|
691
|
+
const crossListedTabsNav = {
|
|
692
|
+
tabs: [
|
|
693
|
+
{
|
|
694
|
+
tab: 'Studio',
|
|
695
|
+
menu: [versionedItem('Studio', ['/studio/v3/a'], ['/studio/v2/a', '/shared'])],
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
tab: 'Engine',
|
|
699
|
+
menu: [versionedItem('Engine', ['/engine/v3/a', '/shared'], ['/engine/v2/a'])],
|
|
700
|
+
},
|
|
701
|
+
{ tab: 'Solver', menu: [versionedItem('Solver', ['/solver/v3/a'], ['/solver/v2/a'])] },
|
|
702
|
+
],
|
|
703
|
+
};
|
|
462
704
|
function filterWith(decoratedNav, currentPath, currentVersion, currentLanguage, isPreview = false, userGroups = new Set()) {
|
|
463
705
|
return filterDivisions({
|
|
464
706
|
currentPath,
|
|
@@ -502,6 +744,60 @@ function assertChromeInvariantForEveryPath(nav, isPreview = false, userGroups =
|
|
|
502
744
|
expect(mapHrefs(rescoped.firstHrefInLanguage), currentPath).toEqual(mapHrefs(full.firstHrefInLanguage));
|
|
503
745
|
}
|
|
504
746
|
}
|
|
747
|
+
const toLocale = (value) => locales.find((locale) => locale === value);
|
|
748
|
+
function sessionValuesContaining(node, href, key, current) {
|
|
749
|
+
const found = new Set();
|
|
750
|
+
if (Array.isArray(node)) {
|
|
751
|
+
for (const entry of node)
|
|
752
|
+
sessionValuesContaining(entry, href, key, current).forEach((v) => found.add(v));
|
|
753
|
+
return found;
|
|
754
|
+
}
|
|
755
|
+
if (typeof node !== 'object' || node === null)
|
|
756
|
+
return found;
|
|
757
|
+
const value = key in node ? Reflect.get(node, key) : undefined;
|
|
758
|
+
const scope = typeof value === 'string' ? value : current;
|
|
759
|
+
if (Reflect.get(node, 'href') === href)
|
|
760
|
+
found.add(scope);
|
|
761
|
+
for (const child of Object.values(node)) {
|
|
762
|
+
if (typeof child === 'object' && child !== null) {
|
|
763
|
+
sessionValuesContaining(child, href, key, scope).forEach((v) => found.add(v));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return found;
|
|
767
|
+
}
|
|
768
|
+
function activeChrome(result) {
|
|
769
|
+
return {
|
|
770
|
+
tabs: result.tabs.map(({ name, isActive, menu }) => ({
|
|
771
|
+
name,
|
|
772
|
+
isActive,
|
|
773
|
+
menu: menu === null || menu === void 0 ? void 0 : menu.map(({ name: item, isActive: active }) => ({ item, active })),
|
|
774
|
+
})),
|
|
775
|
+
versions: result.versions.map(({ name, isActive }) => ({ name, isActive })),
|
|
776
|
+
languages: result.languages.map(({ name, isActive }) => ({ name, isActive })),
|
|
777
|
+
dropdowns: result.dropdowns.map(({ name, isActive }) => ({ name, isActive })),
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function assertChromeInvariantForEverySession(nav, key, isPreview = false, userGroups = new Set()) {
|
|
781
|
+
for (const path of getAllPathsInDecoratedNav(nav)) {
|
|
782
|
+
const currentPath = `/${path.replace(/^\//, '')}`;
|
|
783
|
+
const scoped = getScopedNavForPath({ decoratedNav: nav, currentPath, userGroups, isPreview });
|
|
784
|
+
const isCrossListed = JSON.stringify(nav).split(`"href":"${currentPath}"`).length > 2;
|
|
785
|
+
for (const value of sessionValuesContaining(nav, currentPath, key)) {
|
|
786
|
+
const version = key === 'version' ? value : scoped.currentVersion;
|
|
787
|
+
const language = key === 'language' ? toLocale(value) : scoped.currentLanguage;
|
|
788
|
+
const label = `${currentPath} ${key}=${value !== null && value !== void 0 ? value : '-'}`;
|
|
789
|
+
const full = filterWith(nav, currentPath, version, language, isPreview, userGroups);
|
|
790
|
+
const rescoped = filterWith(scoped.scopedNav, currentPath, version, language, isPreview, userGroups);
|
|
791
|
+
expect(rescoped.groupsOrPages, label).toEqual(full.groupsOrPages);
|
|
792
|
+
if (isCrossListed) {
|
|
793
|
+
expect(activeChrome(rescoped), label).toEqual(activeChrome(full));
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
expect(chromeSummary(rescoped), label).toEqual(chromeSummary(full));
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
505
801
|
function tabByName(nav, name) {
|
|
506
802
|
if (!('tabs' in nav))
|
|
507
803
|
throw new Error('nav has no tabs');
|
|
@@ -834,7 +1130,7 @@ describe('active-tab menu scoping', () => {
|
|
|
834
1130
|
pages: [{ title: 'Get started', href: '/db/intro' }],
|
|
835
1131
|
});
|
|
836
1132
|
});
|
|
837
|
-
it('
|
|
1133
|
+
it('reduces menu items containing versions to a skeleton with one page per version', () => {
|
|
838
1134
|
const { scopedNav } = getScopedNavForPath({
|
|
839
1135
|
decoratedNav: menuWithVersionsTabNav,
|
|
840
1136
|
currentPath: '/api/webhooks/events',
|
|
@@ -855,7 +1151,7 @@ describe('active-tab menu scoping', () => {
|
|
|
855
1151
|
{
|
|
856
1152
|
version: 'v2',
|
|
857
1153
|
default: true,
|
|
858
|
-
pages: [{ title: '
|
|
1154
|
+
pages: [{ title: 'v2', href: '/api/v2/users' }],
|
|
859
1155
|
},
|
|
860
1156
|
{
|
|
861
1157
|
version: 'v1',
|
|
@@ -864,6 +1160,134 @@ describe('active-tab menu scoping', () => {
|
|
|
864
1160
|
],
|
|
865
1161
|
});
|
|
866
1162
|
});
|
|
1163
|
+
it('filters identically for every path in a product menu with versions and dropdowns', () => {
|
|
1164
|
+
assertChromeInvariantForEveryPath(productMenuNav);
|
|
1165
|
+
assertChromeInvariantForEveryPath(productMenuNav, false, new Set(['staff']));
|
|
1166
|
+
assertChromeInvariantForEveryPath(productMenuNav, true);
|
|
1167
|
+
});
|
|
1168
|
+
it('resolves off-path menu hrefs identically for every session version and dropdown', () => {
|
|
1169
|
+
const documentation = tabByName(productMenuNav, 'Documentation');
|
|
1170
|
+
if (!('menu' in documentation))
|
|
1171
|
+
throw new Error('Documentation tab has no menu');
|
|
1172
|
+
const itemPaths = new Map(documentation.menu.map((item) => [
|
|
1173
|
+
item.item,
|
|
1174
|
+
getAllPathsInDecoratedNav(item),
|
|
1175
|
+
]));
|
|
1176
|
+
const versions = [undefined, 'v3', 'v2', 'Legacy'];
|
|
1177
|
+
const dropdownCaches = [
|
|
1178
|
+
new Map(),
|
|
1179
|
+
new Map([['Documentation:Audio:OverviewAPI', 'audio/api/_autosummary/Clip.b']]),
|
|
1180
|
+
];
|
|
1181
|
+
const gatedPaths = ['internal/runbook', 'studio/v3/api/internal'];
|
|
1182
|
+
const paths = getAllPathsInDecoratedNav(productMenuNav).filter((path) => !gatedPaths.includes(path.replace(/^\//, '')));
|
|
1183
|
+
for (const path of paths) {
|
|
1184
|
+
const currentPath = `/${path.replace(/^\//, '')}`;
|
|
1185
|
+
const { scopedNav } = getScopedNavForPath({
|
|
1186
|
+
decoratedNav: productMenuNav,
|
|
1187
|
+
currentPath,
|
|
1188
|
+
});
|
|
1189
|
+
const isOffPath = (name) => { var _a; return !((_a = itemPaths.get(name)) === null || _a === void 0 ? void 0 : _a.includes(path)); };
|
|
1190
|
+
for (const version of versions) {
|
|
1191
|
+
for (const dropdownCache of dropdownCaches) {
|
|
1192
|
+
const menuHrefs = (nav) => filterDivisions({
|
|
1193
|
+
currentPath,
|
|
1194
|
+
decoratedNav: nav,
|
|
1195
|
+
userGroups: new Set(),
|
|
1196
|
+
currentVersion: version,
|
|
1197
|
+
currentLanguage: undefined,
|
|
1198
|
+
shouldUseDivisionMatch: true,
|
|
1199
|
+
cache: { dropdownCache: new Map(dropdownCache) },
|
|
1200
|
+
}).tabs.map((tab) => { var _a; return (_a = tab.menu) === null || _a === void 0 ? void 0 : _a.filter((item) => isOffPath(item.name)).map((item) => [item.name, item.href]); });
|
|
1201
|
+
expect(menuHrefs(scopedNav), `${currentPath} ${version}`).toEqual(menuHrefs(productMenuNav));
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
describe('pages cross-listed in several siblings', () => {
|
|
1207
|
+
it('filters identically for every session version across versioned menu items', () => {
|
|
1208
|
+
assertChromeInvariantForEverySession(crossListedMenuNav, 'version');
|
|
1209
|
+
});
|
|
1210
|
+
it('filters identically for every session language across menu items', () => {
|
|
1211
|
+
assertChromeInvariantForEverySession(crossListedLanguageMenuNav, 'language');
|
|
1212
|
+
});
|
|
1213
|
+
it('filters identically across plain menu items and across tabs', () => {
|
|
1214
|
+
assertChromeInvariantForEverySession(crossListedPlainMenuNav, 'version');
|
|
1215
|
+
assertChromeInvariantForEverySession(crossListedTabsNav, 'version');
|
|
1216
|
+
});
|
|
1217
|
+
it('filters identically when a containing menu item is hidden, for every viewer', () => {
|
|
1218
|
+
assertChromeInvariantForEverySession(crossListedHiddenMenuNav, 'version');
|
|
1219
|
+
assertChromeInvariantForEverySession(crossListedHiddenMenuNav, 'version', true);
|
|
1220
|
+
});
|
|
1221
|
+
it('keeps every menu item containing the page and skeletons the rest', () => {
|
|
1222
|
+
const { scopedNav } = getScopedNavForPath({
|
|
1223
|
+
decoratedNav: crossListedMenuNav,
|
|
1224
|
+
currentPath: '/shared',
|
|
1225
|
+
});
|
|
1226
|
+
const menu = tabByName(scopedNav, 'Docs');
|
|
1227
|
+
if (!('menu' in menu))
|
|
1228
|
+
throw new Error('Docs tab has no menu');
|
|
1229
|
+
const hrefs = (name) => getAllPathsInDecoratedNav(menu.menu.find((item) => item.item === name));
|
|
1230
|
+
expect(hrefs('Studio')).toContain('shared');
|
|
1231
|
+
expect(hrefs('Engine')).toEqual(expect.arrayContaining(['engine/v3/a', 'shared', 'engine/v3/c']));
|
|
1232
|
+
expect(hrefs('Solver')).toEqual(['solver/v3/a', 'solver/v2/a']);
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
it('keeps openapi, asyncapi, and global on skeleton-stubbed menu items', () => {
|
|
1236
|
+
const global = { anchors: [{ anchor: 'Status', href: 'https://status.example.com' }] };
|
|
1237
|
+
const docsConfig = {
|
|
1238
|
+
$schema: 'https://mintlify.com/docs.json',
|
|
1239
|
+
name: 'Docs',
|
|
1240
|
+
theme: 'mint',
|
|
1241
|
+
colors: { primary: '#000000' },
|
|
1242
|
+
navigation: {
|
|
1243
|
+
tabs: [
|
|
1244
|
+
{
|
|
1245
|
+
tab: 'Docs',
|
|
1246
|
+
menu: [
|
|
1247
|
+
{ item: 'Home', pages: ['home'] },
|
|
1248
|
+
{
|
|
1249
|
+
item: 'API',
|
|
1250
|
+
openapi: 'api/openapi.json',
|
|
1251
|
+
asyncapi: 'api/asyncapi.yaml',
|
|
1252
|
+
global,
|
|
1253
|
+
versions: [
|
|
1254
|
+
{ version: 'v2', pages: ['api/v2/users'] },
|
|
1255
|
+
{ version: 'v1', pages: ['api/v1/users'] },
|
|
1256
|
+
],
|
|
1257
|
+
},
|
|
1258
|
+
],
|
|
1259
|
+
},
|
|
1260
|
+
],
|
|
1261
|
+
},
|
|
1262
|
+
};
|
|
1263
|
+
const { navigation } = scopeDocsConfigNavToPath(docsConfig, '/home');
|
|
1264
|
+
if (!('tabs' in navigation))
|
|
1265
|
+
throw new Error('nav has no tabs');
|
|
1266
|
+
const docs = navigation.tabs.find((tab) => tab.tab === 'Docs');
|
|
1267
|
+
if (!docs || !('menu' in docs))
|
|
1268
|
+
throw new Error('Docs tab has no menu');
|
|
1269
|
+
expect(docs.menu.find((item) => item.item === 'API')).toEqual({
|
|
1270
|
+
item: 'API',
|
|
1271
|
+
openapi: 'api/openapi.json',
|
|
1272
|
+
asyncapi: 'api/asyncapi.yaml',
|
|
1273
|
+
global,
|
|
1274
|
+
versions: [
|
|
1275
|
+
{ version: 'v2', pages: ['api/v2/users'] },
|
|
1276
|
+
{ version: 'v1', pages: ['api/v1/users'] },
|
|
1277
|
+
],
|
|
1278
|
+
});
|
|
1279
|
+
});
|
|
1280
|
+
it('keeps no off-path api pages in a product menu', () => {
|
|
1281
|
+
const { scopedNav } = getScopedNavForPath({
|
|
1282
|
+
decoratedNav: productMenuNav,
|
|
1283
|
+
currentPath: '/home',
|
|
1284
|
+
});
|
|
1285
|
+
const paths = getAllPathsInDecoratedNav(scopedNav);
|
|
1286
|
+
expect(paths.filter((path) => path.includes('_autosummary'))).toEqual([
|
|
1287
|
+
'engine/v3/py/_autosummary/Engine.a',
|
|
1288
|
+
'studio/v3/api/_autosummary/Model.a',
|
|
1289
|
+
]);
|
|
1290
|
+
});
|
|
867
1291
|
it('carries hidden and groups onto off-path menu item stubs', () => {
|
|
868
1292
|
const { scopedNav } = getScopedNavForPath({
|
|
869
1293
|
decoratedNav: hiddenMenuTabNav,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|