@mintlify/common 1.0.1023 → 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/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,135 @@
|
|
|
1
|
+
import { decoratedNavigationConfig } from './decoratedNavigationConfig.js';
|
|
2
|
+
import { filterDivisions } from './filterDivisions.js';
|
|
3
|
+
import { getAllPathsInDecoratedNav } from './getAllPathsInDecoratedNav.js';
|
|
4
|
+
import { getScopedNavForPath } from './scopeNavToPath.js';
|
|
5
|
+
const multiDivisionNav = {
|
|
6
|
+
versions: [
|
|
7
|
+
{
|
|
8
|
+
version: 'v2',
|
|
9
|
+
default: true,
|
|
10
|
+
languages: [
|
|
11
|
+
{
|
|
12
|
+
language: 'en',
|
|
13
|
+
groups: [
|
|
14
|
+
{
|
|
15
|
+
group: 'Guides',
|
|
16
|
+
pages: [
|
|
17
|
+
{ title: 'Overview', href: '/en/guides/overview' },
|
|
18
|
+
{ title: 'New in v2', href: '/en/guides/new-in-v2' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
group: 'Reference',
|
|
23
|
+
pages: [{ title: 'API', href: '/en/reference/api' }],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
language: 'es',
|
|
29
|
+
groups: [
|
|
30
|
+
{
|
|
31
|
+
group: 'Guides',
|
|
32
|
+
pages: [
|
|
33
|
+
{ title: 'Overview', href: '/es/guides/overview' },
|
|
34
|
+
{ title: 'New in v2', href: '/es/guides/new-in-v2' },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
version: 'v1',
|
|
43
|
+
languages: [
|
|
44
|
+
{
|
|
45
|
+
language: 'en',
|
|
46
|
+
groups: [
|
|
47
|
+
{
|
|
48
|
+
group: 'Guides',
|
|
49
|
+
pages: [{ title: 'Overview', href: '/v1/en/guides/overview' }],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
language: 'es',
|
|
55
|
+
groups: [
|
|
56
|
+
{
|
|
57
|
+
group: 'Guides',
|
|
58
|
+
pages: [{ title: 'Overview', href: '/v1/es/guides/overview' }],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
function filterWith(decoratedNav, currentPath, currentVersion, currentLanguage) {
|
|
67
|
+
return filterDivisions({
|
|
68
|
+
currentPath,
|
|
69
|
+
decoratedNav,
|
|
70
|
+
userGroups: new Set(),
|
|
71
|
+
currentVersion,
|
|
72
|
+
currentLanguage,
|
|
73
|
+
shouldUseDivisionMatch: true,
|
|
74
|
+
cache: { dropdownCache: new Map() },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function divisionSummary(result) {
|
|
78
|
+
return {
|
|
79
|
+
tabs: result.tabs.map(({ name, isActive }) => ({ name, isActive })),
|
|
80
|
+
anchors: result.anchors.map(({ name, isActive }) => ({ name, isActive })),
|
|
81
|
+
versions: result.versions.map(({ name, isActive }) => ({ name, isActive })),
|
|
82
|
+
languages: result.languages.map(({ name, isActive }) => ({ name, isActive })),
|
|
83
|
+
dropdowns: result.dropdowns.map(({ name, isActive }) => ({ name, isActive })),
|
|
84
|
+
products: result.products.map(({ name, isActive }) => ({ name, isActive })),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function mapHrefs(map) {
|
|
88
|
+
return new Map([...map.entries()].map(([key, value]) => [key, value.href]));
|
|
89
|
+
}
|
|
90
|
+
function assertInvariantForEveryPath(nav) {
|
|
91
|
+
const paths = getAllPathsInDecoratedNav(nav);
|
|
92
|
+
expect(paths.length).toBeGreaterThan(0);
|
|
93
|
+
for (const path of paths) {
|
|
94
|
+
const currentPath = `/${path.replace(/^\//, '')}`;
|
|
95
|
+
const scoped = getScopedNavForPath({ decoratedNav: nav, currentPath });
|
|
96
|
+
const full = filterWith(nav, currentPath, scoped.currentVersion, scoped.currentLanguage);
|
|
97
|
+
const rescoped = filterWith(scoped.scopedNav, currentPath, scoped.currentVersion, scoped.currentLanguage);
|
|
98
|
+
expect(rescoped.groupsOrPages, currentPath).toEqual(full.groupsOrPages);
|
|
99
|
+
expect(divisionSummary(rescoped), currentPath).toEqual(divisionSummary(full));
|
|
100
|
+
expect(mapHrefs(rescoped.firstHrefInVersion), currentPath).toEqual(mapHrefs(full.firstHrefInVersion));
|
|
101
|
+
expect(mapHrefs(rescoped.firstHrefInLanguage), currentPath).toEqual(mapHrefs(full.firstHrefInLanguage));
|
|
102
|
+
const derived = getScopedNavForPath({ decoratedNav: scoped.scopedNav, currentPath });
|
|
103
|
+
expect(derived.currentVersion, currentPath).toBe(scoped.currentVersion);
|
|
104
|
+
expect(derived.currentLanguage, currentPath).toBe(scoped.currentLanguage);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
describe(getScopedNavForPath, () => {
|
|
108
|
+
it('filters identically to the full nav for every path in the versioned fixture', () => {
|
|
109
|
+
assertInvariantForEveryPath(decoratedNavigationConfig);
|
|
110
|
+
});
|
|
111
|
+
it('filters identically to the full nav for every path in a versioned multilang nav', () => {
|
|
112
|
+
assertInvariantForEveryPath(multiDivisionNav);
|
|
113
|
+
});
|
|
114
|
+
it('bakes the group-level fallback target into sibling version stubs', () => {
|
|
115
|
+
var _a, _b;
|
|
116
|
+
const scoped = getScopedNavForPath({
|
|
117
|
+
decoratedNav: multiDivisionNav,
|
|
118
|
+
currentPath: '/en/guides/new-in-v2',
|
|
119
|
+
});
|
|
120
|
+
expect(scoped.currentVersion).toBe('v2');
|
|
121
|
+
expect(scoped.currentLanguage).toBe('en');
|
|
122
|
+
expect((_a = scoped.firstHrefInVersion.get('v1')) === null || _a === void 0 ? void 0 : _a.href).toBe('/v1/en/guides/overview');
|
|
123
|
+
const rescoped = filterWith(scoped.scopedNav, '/en/guides/new-in-v2', 'v2', 'en');
|
|
124
|
+
expect((_b = rescoped.firstHrefInVersion.get('v1')) === null || _b === void 0 ? void 0 : _b.href).toBe('/v1/en/guides/overview');
|
|
125
|
+
});
|
|
126
|
+
it('drops sibling division page trees from the scoped nav', () => {
|
|
127
|
+
const scoped = getScopedNavForPath({
|
|
128
|
+
decoratedNav: multiDivisionNav,
|
|
129
|
+
currentPath: '/en/guides/overview',
|
|
130
|
+
});
|
|
131
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
132
|
+
expect(serialized).not.toContain('/v1/es/guides/overview');
|
|
133
|
+
expect(serialized).toContain('/en/guides/new-in-v2');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LocaleType } from '@mintlify/models';
|
|
2
|
+
import { DecoratedNavigationConfig } from '@mintlify/validation';
|
|
3
|
+
/**
|
|
4
|
+
* Determine which division contains a given page.
|
|
5
|
+
*
|
|
6
|
+
* @param type The type of division to get
|
|
7
|
+
* @param decoratedNav The decorated navigation. Each page entry must have an `href` field that is prefixed with a `/`
|
|
8
|
+
* @param path The current path of the page, prefixed with a `/`
|
|
9
|
+
* @param defaultValue The default division value to use if the current page occurs multiple times in different divisions
|
|
10
|
+
* @param initialDivisionValue The initial division value to use if the current page is unversioned or unlocalized
|
|
11
|
+
*/
|
|
12
|
+
export declare function getVersionOrLanguageFromPath<T extends 'version' | 'language'>(type: T, entry: DecoratedNavigationConfig, path: string, defaultValue: T extends 'version' ? string | undefined : LocaleType | undefined, initialDivisionValue: T extends 'version' ? string | undefined : LocaleType | undefined): T extends 'version' ? string : LocaleType | undefined;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { divisions, } from '@mintlify/validation';
|
|
2
|
+
import { readObjectArrayProperty } from '../divisions/iterateObjectArrayProperty.js';
|
|
3
|
+
import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
|
|
4
|
+
import { isCommonPage } from './isCommonPage.js';
|
|
5
|
+
import { isPage } from './isPage.js';
|
|
6
|
+
/**
|
|
7
|
+
* Determine which division contains a given page.
|
|
8
|
+
*
|
|
9
|
+
* @param type The type of division to get
|
|
10
|
+
* @param decoratedNav The decorated navigation. Each page entry must have an `href` field that is prefixed with a `/`
|
|
11
|
+
* @param path The current path of the page, prefixed with a `/`
|
|
12
|
+
* @param defaultValue The default division value to use if the current page occurs multiple times in different divisions
|
|
13
|
+
* @param initialDivisionValue The initial division value to use if the current page is unversioned or unlocalized
|
|
14
|
+
*/
|
|
15
|
+
export function getVersionOrLanguageFromPath(type, entry, path, defaultValue, initialDivisionValue) {
|
|
16
|
+
const result = getDivisionFromPathHelper(type, entry, entry, path, defaultValue, undefined);
|
|
17
|
+
const backupResult = defaultValue || initialDivisionValue;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
|
|
19
|
+
return (result || backupResult);
|
|
20
|
+
}
|
|
21
|
+
function getDivisionFromPathHelper(type, entry, parentEntry, path, defaultValue, nearestDivisionValue) {
|
|
22
|
+
var _a, _b;
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
|
|
24
|
+
const undefinedResult = (type === 'version' ? '' : undefined);
|
|
25
|
+
if (typeof entry !== 'object')
|
|
26
|
+
return undefinedResult;
|
|
27
|
+
if (isPage(entry)) {
|
|
28
|
+
if (isEqualIgnoringLeadingSlash(entry.href, path)) {
|
|
29
|
+
const isCommon = isCommonPage(parentEntry, path);
|
|
30
|
+
// logic: if this pages was shared in multiple divisions, we should use the default one,
|
|
31
|
+
// if the page was not shared and only used in one division, we should use the nearest division
|
|
32
|
+
if (isCommon && defaultValue && defaultValue !== nearestDivisionValue)
|
|
33
|
+
return undefinedResult;
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
|
|
35
|
+
return (type === 'version' ? ((_a = entry.version) !== null && _a !== void 0 ? _a : nearestDivisionValue) : nearestDivisionValue);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
return undefinedResult;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const key of [...divisions, 'groups', 'pages']) {
|
|
42
|
+
let subDivisions = (_b = readObjectArrayProperty(entry, key)) !== null && _b !== void 0 ? _b : [];
|
|
43
|
+
if (key === 'pages' && 'root' in entry && typeof entry.root === 'object') {
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- root is a DecoratedNavigationPage
|
|
45
|
+
subDivisions = [entry.root, ...subDivisions];
|
|
46
|
+
}
|
|
47
|
+
if (subDivisions.length > 0) {
|
|
48
|
+
for (const subDivision of subDivisions) {
|
|
49
|
+
let divisionValue;
|
|
50
|
+
if (type === 'version') {
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
|
|
52
|
+
divisionValue = ('version' in subDivision
|
|
53
|
+
? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
|
|
54
|
+
subDivision.version
|
|
55
|
+
: undefined);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
|
|
59
|
+
divisionValue = ('language' in subDivision
|
|
60
|
+
? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TODO: Please fix this violation when you can!
|
|
61
|
+
subDivision.language
|
|
62
|
+
: undefined);
|
|
63
|
+
}
|
|
64
|
+
const divisionFromSubDivision = getDivisionFromPathHelper(type, subDivision, parentEntry, path, defaultValue, divisionValue !== null && divisionValue !== void 0 ? divisionValue : nearestDivisionValue);
|
|
65
|
+
if (divisionFromSubDivision && divisionFromSubDivision !== undefinedResult)
|
|
66
|
+
return divisionFromSubDivision;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return undefinedResult;
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { getVersionOrLanguageFromPath } from './getVersionOrLanguageFromPath.js';
|
|
2
|
+
describe(getVersionOrLanguageFromPath, () => {
|
|
3
|
+
it('should return no version if user has not set versioning', () => {
|
|
4
|
+
expect(getVersionOrLanguageFromPath('version', {
|
|
5
|
+
groups: [
|
|
6
|
+
{
|
|
7
|
+
group: 'Getting started',
|
|
8
|
+
pages: [{ title: '', href: '/welcome' }],
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
}, '/random-page', '', 'v1')).toEqual('v1');
|
|
12
|
+
});
|
|
13
|
+
it('should return default version if path has no match in navigation', () => {
|
|
14
|
+
expect(getVersionOrLanguageFromPath('version', {
|
|
15
|
+
groups: [
|
|
16
|
+
{
|
|
17
|
+
group: 'Getting started',
|
|
18
|
+
pages: [{ title: '', href: '/welcome' }],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}, '/welcome', '', '')).toEqual('');
|
|
22
|
+
});
|
|
23
|
+
it('should return correct version if path points to a page with a specified version', () => {
|
|
24
|
+
expect(getVersionOrLanguageFromPath('version', {
|
|
25
|
+
versions: [
|
|
26
|
+
{
|
|
27
|
+
version: 'v1',
|
|
28
|
+
pages: [
|
|
29
|
+
{
|
|
30
|
+
title: 'API V1',
|
|
31
|
+
href: '/v1/api/hello',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: 'API V1',
|
|
35
|
+
href: '/v1/api/world',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
version: 'v2',
|
|
41
|
+
pages: [
|
|
42
|
+
{
|
|
43
|
+
title: 'API V2',
|
|
44
|
+
href: '/v2/api/hello',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: 'API V2',
|
|
48
|
+
href: '/v2/api/world',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
}, '/v2/api/hello', '', 'v1')).toEqual('v2');
|
|
54
|
+
});
|
|
55
|
+
it('should return correct version if there are nested pages groups and the wanted page is inside the nested group', () => {
|
|
56
|
+
expect(getVersionOrLanguageFromPath('version', {
|
|
57
|
+
versions: [
|
|
58
|
+
{
|
|
59
|
+
version: 'v1',
|
|
60
|
+
pages: [
|
|
61
|
+
{
|
|
62
|
+
title: '',
|
|
63
|
+
href: '/v1/api/hello-world',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
group: 'API V1 Nested',
|
|
67
|
+
pages: [{ title: '', href: '/v1/api/hello-world-nested' }],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
version: 'v2',
|
|
73
|
+
pages: [{ title: '', href: '/v2/api/hello-world' }],
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
}, '/v1/api/hello-world-nested', '', 'v2')).toEqual('v1');
|
|
77
|
+
});
|
|
78
|
+
it('should return correct version if there are nested pages groups and the wanted page is outside of the nested group', () => {
|
|
79
|
+
expect(getVersionOrLanguageFromPath('version', {
|
|
80
|
+
versions: [
|
|
81
|
+
{
|
|
82
|
+
version: 'v1',
|
|
83
|
+
pages: [
|
|
84
|
+
{ title: '', href: '/v1/hello/world' },
|
|
85
|
+
{
|
|
86
|
+
group: 'API V1 Nested',
|
|
87
|
+
pages: [{ title: '', href: '/v1/api/hello-world-nested' }],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
version: 'v2',
|
|
93
|
+
pages: [{ title: '', href: '/v2/hello/world' }],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
}, '/v1/hello/world', '', 'v2')).toEqual('v1');
|
|
97
|
+
});
|
|
98
|
+
it('should return correct version for a root page in a group', () => {
|
|
99
|
+
expect(getVersionOrLanguageFromPath('version', {
|
|
100
|
+
versions: [
|
|
101
|
+
{
|
|
102
|
+
version: 'v1',
|
|
103
|
+
groups: [
|
|
104
|
+
{
|
|
105
|
+
group: 'API V1',
|
|
106
|
+
root: { title: 'V1 Root', href: '/v1/root' },
|
|
107
|
+
pages: [{ title: '', href: '/v1/api/hello' }],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
version: 'v2',
|
|
113
|
+
pages: [{ title: '', href: '/v2/api/hello' }],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
}, '/v1/root', '', 'v2')).toEqual('v1');
|
|
117
|
+
});
|
|
118
|
+
it('should return correct version if default version is set and there are shared pages', () => {
|
|
119
|
+
expect(getVersionOrLanguageFromPath('version', {
|
|
120
|
+
versions: [
|
|
121
|
+
{
|
|
122
|
+
version: 'v1',
|
|
123
|
+
pages: [{ title: '', href: '/hello/world' }],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
version: 'v2',
|
|
127
|
+
default: true,
|
|
128
|
+
pages: [{ title: '', href: '/hello/world' }],
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
}, '/hello/world', 'v2', 'v1')).toEqual('v2');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -6,5 +6,10 @@ export * from './generatePathToBreadcrumbsMapForDocsConfig.js';
|
|
|
6
6
|
export * from './getAllPathsInDocsNav.js';
|
|
7
7
|
export * from './getAllPathsInDecoratedNav.js';
|
|
8
8
|
export * from './checkNavAccess.js';
|
|
9
|
+
export * from './filterDivisions.js';
|
|
10
|
+
export * from './updateFirstHrefInDivision.js';
|
|
11
|
+
export * from './scopeNavToPath.js';
|
|
12
|
+
export * from './getVersionOrLanguageFromPath.js';
|
|
13
|
+
export * from './isCommonPage.js';
|
|
9
14
|
export * from './getFirstPageFromNavConfig.js';
|
|
10
15
|
export * from './prioritize-default-division-item.js';
|
package/dist/navigation/index.js
CHANGED
|
@@ -6,5 +6,10 @@ export * from './generatePathToBreadcrumbsMapForDocsConfig.js';
|
|
|
6
6
|
export * from './getAllPathsInDocsNav.js';
|
|
7
7
|
export * from './getAllPathsInDecoratedNav.js';
|
|
8
8
|
export * from './checkNavAccess.js';
|
|
9
|
+
export * from './filterDivisions.js';
|
|
10
|
+
export * from './updateFirstHrefInDivision.js';
|
|
11
|
+
export * from './scopeNavToPath.js';
|
|
12
|
+
export * from './getVersionOrLanguageFromPath.js';
|
|
13
|
+
export * from './isCommonPage.js';
|
|
9
14
|
export * from './getFirstPageFromNavConfig.js';
|
|
10
15
|
export * from './prioritize-default-division-item.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DecoratedNavigationConfig } from '@mintlify/validation';
|
|
2
|
+
/**
|
|
3
|
+
* Check if the page is common in multiple divisions
|
|
4
|
+
* @param entry - The navigation config entry
|
|
5
|
+
* @param path - The path to check
|
|
6
|
+
* @returns True if the page is common in multiple divisions, false otherwise
|
|
7
|
+
*/
|
|
8
|
+
export declare function isCommonPage(entry: DecoratedNavigationConfig, path: string): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { divisions } from '@mintlify/validation';
|
|
2
|
+
import { readObjectArrayProperty } from '../divisions/iterateObjectArrayProperty.js';
|
|
3
|
+
import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
|
|
4
|
+
import { isPage } from './isPage.js';
|
|
5
|
+
/**
|
|
6
|
+
* Check if the page is common in multiple divisions
|
|
7
|
+
* @param entry - The navigation config entry
|
|
8
|
+
* @param path - The path to check
|
|
9
|
+
* @returns True if the page is common in multiple divisions, false otherwise
|
|
10
|
+
*/
|
|
11
|
+
export function isCommonPage(entry, path) {
|
|
12
|
+
const occurrences = { value: 0 };
|
|
13
|
+
isCommonPageRecursive(entry, path, occurrences);
|
|
14
|
+
return occurrences.value > 1;
|
|
15
|
+
}
|
|
16
|
+
function isCommonPageRecursive(entry, path, occurrences) {
|
|
17
|
+
if (typeof entry !== 'object')
|
|
18
|
+
return;
|
|
19
|
+
if (isPage(entry)) {
|
|
20
|
+
if (isEqualIgnoringLeadingSlash(entry.href, path)) {
|
|
21
|
+
occurrences.value++;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
for (const key of [...divisions, 'groups', 'pages']) {
|
|
25
|
+
if (key === 'pages' && 'root' in entry && typeof entry.root === 'object') {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- root is a DecoratedNavigationPage
|
|
27
|
+
isCommonPageRecursive(entry.root, path, occurrences);
|
|
28
|
+
}
|
|
29
|
+
const subDivisions = readObjectArrayProperty(entry, key);
|
|
30
|
+
if (subDivisions) {
|
|
31
|
+
for (const subDivision of subDivisions) {
|
|
32
|
+
isCommonPageRecursive(subDivision, path, occurrences);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isCommonPage } from './isCommonPage.js';
|
|
2
|
+
describe(isCommonPage, () => {
|
|
3
|
+
it('should return true if the page is common in multiple divisions', () => {
|
|
4
|
+
expect(isCommonPage({
|
|
5
|
+
tabs: [
|
|
6
|
+
{
|
|
7
|
+
tab: 'Getting started',
|
|
8
|
+
pages: [{ title: '', href: '/welcome' }],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
tab: 'Components',
|
|
12
|
+
pages: [{ title: '', href: '/welcome' }],
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
}, '/welcome')).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it('should return true if the page is common in nested divisions', () => {
|
|
18
|
+
expect(isCommonPage({
|
|
19
|
+
versions: [
|
|
20
|
+
{
|
|
21
|
+
version: 'v1',
|
|
22
|
+
pages: [{ title: '', href: '/welcome' }],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
version: 'v2',
|
|
26
|
+
tabs: [{ tab: 'Getting started', pages: [{ title: '', href: '/welcome' }] }],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}, '/welcome')).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
it('should return true if a root page is common across divisions', () => {
|
|
32
|
+
expect(isCommonPage({
|
|
33
|
+
tabs: [
|
|
34
|
+
{
|
|
35
|
+
tab: 'Getting started',
|
|
36
|
+
groups: [
|
|
37
|
+
{
|
|
38
|
+
group: 'Group 1',
|
|
39
|
+
root: { title: '', href: '/welcome' },
|
|
40
|
+
pages: [{ title: '', href: '/other' }],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
tab: 'Components',
|
|
46
|
+
pages: [{ title: '', href: '/welcome' }],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}, '/welcome')).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it('should return false if the page is not common in multiple divisions', () => {
|
|
52
|
+
expect(isCommonPage({
|
|
53
|
+
tabs: [{ tab: 'Getting started', pages: [{ title: '', href: '/welcome' }] }],
|
|
54
|
+
}, '/welcome')).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { pathsDifferByOneSegment } from './updateFirstHrefInDivision.js';
|
|
2
|
+
describe('pathsDifferByOneSegment', () => {
|
|
3
|
+
describe('language type', () => {
|
|
4
|
+
it('returns true for paths that differ only by locale prefix', () => {
|
|
5
|
+
expect(pathsDifferByOneSegment('/ja/getting-started', '/getting-started', 'language')).toBe(true);
|
|
6
|
+
expect(pathsDifferByOneSegment('/getting-started', '/ja/getting-started', 'language')).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
it('returns true for paths with different locale prefixes', () => {
|
|
9
|
+
expect(pathsDifferByOneSegment('/ja/getting-started', '/es/getting-started', 'language')).toBe(true);
|
|
10
|
+
expect(pathsDifferByOneSegment('/fr/docs/intro', '/de/docs/intro', 'language')).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
it('returns false for paths with different content', () => {
|
|
13
|
+
expect(pathsDifferByOneSegment('/ja/getting-started', '/ja/quickstart', 'language')).toBe(false);
|
|
14
|
+
expect(pathsDifferByOneSegment('/getting-started', '/quickstart', 'language')).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
it('returns false for paths with same locale', () => {
|
|
17
|
+
expect(pathsDifferByOneSegment('/ja/getting-started', '/ja/getting-started', 'language')).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
it('returns false when neither path has a locale', () => {
|
|
20
|
+
expect(pathsDifferByOneSegment('/getting-started', '/getting-started', 'language')).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
describe('index page handling', () => {
|
|
23
|
+
it('treats /path/index and /path as equivalent', () => {
|
|
24
|
+
expect(pathsDifferByOneSegment('/ja/guides/getting-started/index', '/guides/getting-started', 'language')).toBe(true);
|
|
25
|
+
expect(pathsDifferByOneSegment('/guides/getting-started', '/ja/guides/getting-started/index', 'language')).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it('treats /locale/path/index and /path/index as equivalent', () => {
|
|
28
|
+
expect(pathsDifferByOneSegment('/ja/guides/getting-started/index', '/guides/getting-started/index', 'language')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('treats /locale1/path/index and /locale2/path as equivalent', () => {
|
|
31
|
+
expect(pathsDifferByOneSegment('/ja/guides/getting-started/index', '/es/guides/getting-started', 'language')).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it('handles deeply nested index pages', () => {
|
|
34
|
+
expect(pathsDifferByOneSegment('/ja/guides/api/reference/index', '/guides/api/reference', 'language')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('does not treat index as special when not the last segment', () => {
|
|
37
|
+
expect(pathsDifferByOneSegment('/ja/index/getting-started', '/index/getting-started', 'language')).toBe(true);
|
|
38
|
+
expect(pathsDifferByOneSegment('/ja/index/getting-started', '/getting-started', 'language')).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('with docs base path', () => {
|
|
42
|
+
it('handles docs prefix correctly', () => {
|
|
43
|
+
expect(pathsDifferByOneSegment('/docs/ja/getting-started', '/docs/getting-started', 'language')).toBe(true);
|
|
44
|
+
expect(pathsDifferByOneSegment('/docs/ja/getting-started', '/docs/es/getting-started', 'language')).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('returns false when docs prefix differs', () => {
|
|
47
|
+
expect(pathsDifferByOneSegment('/docs/ja/getting-started', '/ja/getting-started', 'language')).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it('handles docs prefix with index pages', () => {
|
|
50
|
+
expect(pathsDifferByOneSegment('/docs/ja/guides/index', '/docs/guides', 'language')).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
it('handles locale-first docs paths with version segments', () => {
|
|
53
|
+
expect(pathsDifferByOneSegment('/es/docs/2025_12/getting-started', '/docs/2025_12/getting-started', 'language')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it('returns false for locale-first docs paths when version segment differs', () => {
|
|
56
|
+
expect(pathsDifferByOneSegment('/es/docs/2025_12/getting-started', '/docs/getting-started', 'language')).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('version type', () => {
|
|
61
|
+
it('returns true for paths that differ by one segment (not last)', () => {
|
|
62
|
+
expect(pathsDifferByOneSegment('/v1/getting-started', '/v2/getting-started', 'version')).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it('returns true for root-vs-prefixed paths with same content', () => {
|
|
65
|
+
expect(pathsDifferByOneSegment('/2025-09/getting-started', '/getting-started', 'version')).toBe(true);
|
|
66
|
+
expect(pathsDifferByOneSegment('/getting-started', '/2025-09/getting-started', 'version')).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it('returns true for docs-prefixed root-vs-prefixed paths with same content', () => {
|
|
69
|
+
expect(pathsDifferByOneSegment('/docs/2025-09/getting-started', '/docs/getting-started', 'version')).toBe(true);
|
|
70
|
+
expect(pathsDifferByOneSegment('/docs/2025-09/guides/overview', '/docs/guides/overview', 'version')).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
it('returns false when docs base path presence differs', () => {
|
|
73
|
+
expect(pathsDifferByOneSegment('/docs/2025-09/getting-started', '/2025-09/getting-started', 'version')).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
it('returns false for paths that differ by last segment', () => {
|
|
76
|
+
expect(pathsDifferByOneSegment('/api/v1', '/api/v2', 'version')).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
it('returns false for paths with different lengths', () => {
|
|
79
|
+
expect(pathsDifferByOneSegment('/v1/api/docs', '/v2/api', 'version')).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
it('returns false for paths with multiple differences', () => {
|
|
82
|
+
expect(pathsDifferByOneSegment('/v1/foo/bar', '/v2/baz/bar', 'version')).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
it('returns false for paths with fewer than 2 segments', () => {
|
|
85
|
+
expect(pathsDifferByOneSegment('/v1', '/v2', 'version')).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it('returns false for root-vs-prefixed paths with different content', () => {
|
|
88
|
+
expect(pathsDifferByOneSegment('/2025-09/getting-started', '/quickstart', 'version')).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LocaleType } from '@mintlify/models';
|
|
2
|
+
import type { DecoratedNavigationConfig, DocsConfig } from '@mintlify/validation';
|
|
3
|
+
import { DivisionMatch } from './updateFirstHrefInDivision.js';
|
|
4
|
+
export declare function scopeDecoratedNavToPath(nav: DecoratedNavigationConfig, currentPath: string): DecoratedNavigationConfig;
|
|
5
|
+
export declare function scopeDocsConfigNavToPath(docsConfig: DocsConfig, currentPath: string): DocsConfig;
|
|
6
|
+
export type ScopedNavForPath = {
|
|
7
|
+
scopedNav: DecoratedNavigationConfig;
|
|
8
|
+
firstHrefInVersion: Map<string | undefined, DivisionMatch>;
|
|
9
|
+
firstHrefInLanguage: Map<LocaleType | undefined, DivisionMatch>;
|
|
10
|
+
currentVersion: string | undefined;
|
|
11
|
+
currentLanguage: LocaleType | undefined;
|
|
12
|
+
};
|
|
13
|
+
export declare function getScopedNavForPath({ decoratedNav, currentPath, userGroups, isPreview, }: {
|
|
14
|
+
decoratedNav: DecoratedNavigationConfig;
|
|
15
|
+
currentPath: string;
|
|
16
|
+
userGroups?: Set<string>;
|
|
17
|
+
isPreview?: boolean;
|
|
18
|
+
}): ScopedNavForPath;
|