@f5-sales-demo/docs-theme 4.3.14 → 4.4.0
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/canonical-route-middleware.ts +40 -0
- package/components/MarkdownContent.astro +7 -1
- package/components/ProviderReference.astro +55 -0
- package/config.ts +41 -17
- package/index.ts +5 -2
- package/package.json +7 -5
- package/route-middleware.ts +2 -1
- package/src/content.config.ts +19 -1
- package/src/utils/canonical-provider.test.ts +30 -0
- package/src/utils/canonical-provider.ts +74 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { defineRouteMiddleware } from '@astrojs/starlight/route-data';
|
|
3
|
+
import type { ProviderPage } from './src/utils/canonical-provider.ts';
|
|
4
|
+
import { collectionNavigation, providerSections } from './src/utils/canonical-provider.ts';
|
|
5
|
+
|
|
6
|
+
let providerNavigation: { collections: Record<string, ProviderPage[]> };
|
|
7
|
+
export const onRequest = defineRouteMiddleware(async (context, next) => {
|
|
8
|
+
const route = context.locals.starlightRoute;
|
|
9
|
+
const entry = route.entry;
|
|
10
|
+
const canonical = process.env.DOCS_PROFILE === 'canonical-provider';
|
|
11
|
+
if (canonical) {
|
|
12
|
+
providerNavigation ||= JSON.parse(fs.readFileSync(process.env.PROVIDER_NAVIGATION || '', 'utf8'));
|
|
13
|
+
const metadata = entry.data.xcsh_docs;
|
|
14
|
+
const base = import.meta.env.BASE_URL;
|
|
15
|
+
const sections = providerSections.map((section) => ({
|
|
16
|
+
type: 'link' as const,
|
|
17
|
+
label: section.label,
|
|
18
|
+
href: `${base.replace(/\/$/, '')}/${section.path}/`,
|
|
19
|
+
isCurrent: route.id === section.path,
|
|
20
|
+
badge: undefined,
|
|
21
|
+
attrs: {},
|
|
22
|
+
}));
|
|
23
|
+
route.sidebar = [
|
|
24
|
+
...sections,
|
|
25
|
+
...(metadata
|
|
26
|
+
? collectionNavigation(
|
|
27
|
+
providerNavigation.collections[metadata.collection_id] || [],
|
|
28
|
+
metadata.collection_id,
|
|
29
|
+
base,
|
|
30
|
+
route.id,
|
|
31
|
+
)
|
|
32
|
+
: []),
|
|
33
|
+
];
|
|
34
|
+
route.hasSidebar = true;
|
|
35
|
+
route.pagination = { prev: undefined, next: undefined };
|
|
36
|
+
route.siteTitleHref = base;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await next();
|
|
40
|
+
});
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
+
import ProviderReference from './ProviderReference.astro';
|
|
3
|
+
|
|
4
|
+
const canonical = process.env.DOCS_PROFILE === 'canonical-provider';
|
|
5
|
+
|
|
2
6
|
import ImageZoom from 'starlight-image-zoom/components/ImageZoom.astro';
|
|
3
7
|
import StarlightVideosMarkdownContent from 'starlight-videos/components/MarkdownContent.astro';
|
|
4
8
|
---
|
|
5
9
|
|
|
6
10
|
<ImageZoom />
|
|
7
|
-
<StarlightVideosMarkdownContent
|
|
11
|
+
<StarlightVideosMarkdownContent>
|
|
12
|
+
{canonical ? <ProviderReference><slot /></ProviderReference> : <slot />}
|
|
13
|
+
</StarlightVideosMarkdownContent>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { CardGrid } from '@astrojs/starlight/components';
|
|
3
|
+
import { providerSections } from '../src/utils/canonical-provider';
|
|
4
|
+
import LinkCard from './LinkCard.astro';
|
|
5
|
+
|
|
6
|
+
const { entry } = Astro.locals.starlightRoute;
|
|
7
|
+
const metadata = entry.data.xcsh_docs;
|
|
8
|
+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
9
|
+
---
|
|
10
|
+
{!metadata && (entry.id === 'index' || entry.id === '') && <CardGrid>
|
|
11
|
+
{providerSections.map(section => <LinkCard title={section.label} href={`${base}/${section.path}/`} />)}
|
|
12
|
+
</CardGrid>}
|
|
13
|
+
<label class="version-control">Documentation version
|
|
14
|
+
<select id="provider-version" aria-label="Documentation version"><option>{process.env.DOCUMENTATION_LABEL || 'Latest stable'}</option></select>
|
|
15
|
+
</label>
|
|
16
|
+
<slot />
|
|
17
|
+
{metadata && <footer class="provider-provenance">
|
|
18
|
+
<p>Schema path: <code>{metadata.schema_path.join('.') || 'root'}</code> · <a href={metadata.source_url}>Complete Markdown</a></p>
|
|
19
|
+
<small>{metadata.id} · {metadata.body_sha256}</small>
|
|
20
|
+
</footer>}
|
|
21
|
+
<style>
|
|
22
|
+
.provider-provenance { border-top: 1px solid var(--sl-color-gray-5); margin-top: 2rem; padding-top: 1rem; overflow-wrap: anywhere; }
|
|
23
|
+
.provider-provenance p { margin-bottom: .5rem; }
|
|
24
|
+
.provider-provenance small { color: var(--sl-color-gray-3); }
|
|
25
|
+
</style>
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
const select = document.querySelector<HTMLSelectElement>('#provider-version');
|
|
29
|
+
const publicationRoot = '/terraform-provider-xcsh/';
|
|
30
|
+
if (select) {
|
|
31
|
+
fetch(`${publicationRoot}documentation-versions.json`).then(response => response.json()).then(data => {
|
|
32
|
+
const path = window.location.pathname.slice(publicationRoot.length);
|
|
33
|
+
const prefix = path.startsWith('versions/') ? path.split('/').slice(0, 2).join('/') + '/' : path.startsWith('preview/main/') ? 'preview/main/' : '';
|
|
34
|
+
const equivalent = path.slice(prefix.length);
|
|
35
|
+
const versions = [{ label: `Latest stable (${data.latest})`, prefix: '' }, ...data.versions.map((version: string) => ({ label: version, prefix: `versions/${version}/` })), { label: 'Main preview', prefix: 'preview/main/' }];
|
|
36
|
+
select.replaceChildren(...versions.map(version => {
|
|
37
|
+
const option = document.createElement('option');
|
|
38
|
+
option.textContent = version.label;
|
|
39
|
+
option.value = version.prefix;
|
|
40
|
+
option.selected = prefix === version.prefix;
|
|
41
|
+
return option;
|
|
42
|
+
}));
|
|
43
|
+
select.addEventListener('change', async () => {
|
|
44
|
+
const landing = `${publicationRoot}${select.value}`;
|
|
45
|
+
const destination = `${landing}${equivalent}`;
|
|
46
|
+
const response = await fetch(destination, { method: 'HEAD' });
|
|
47
|
+
window.location.assign(response.ok ? destination : landing);
|
|
48
|
+
});
|
|
49
|
+
}).catch(() => { select.disabled = true; });
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
<style>
|
|
53
|
+
.version-control { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: 1rem; font-size: var(--sl-text-sm); }
|
|
54
|
+
select { max-width: 100%; padding: .25rem; }
|
|
55
|
+
</style>
|
package/config.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from './src/i18n/mega-menu-translations.ts';
|
|
32
32
|
import { sidebarTranslations } from './src/i18n/translations.ts';
|
|
33
33
|
import remarkMermaid from './src/plugins/remark-mermaid.mjs';
|
|
34
|
+
import { providerMegaMenu } from './src/utils/canonical-provider.ts';
|
|
34
35
|
import { resolveMegaMenuIcon } from './src/utils/resolve-icon.ts';
|
|
35
36
|
import { buildSubcategorySidebar } from './src/utils/subcategory-sidebar.ts';
|
|
36
37
|
|
|
@@ -100,6 +101,7 @@ interface HeadEntry {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
export interface F5xcDocsConfigOptions {
|
|
104
|
+
canonicalProvider?: { contentRoot: string; manifest: string; navigation: string; version: string };
|
|
103
105
|
site?: string;
|
|
104
106
|
base?: string;
|
|
105
107
|
title?: string;
|
|
@@ -651,6 +653,19 @@ export const federatedSearchSites = [
|
|
|
651
653
|
];
|
|
652
654
|
|
|
653
655
|
export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
656
|
+
const canonicalProvider =
|
|
657
|
+
options.canonicalProvider ||
|
|
658
|
+
(process.env.DOCS_PROFILE === 'canonical-provider'
|
|
659
|
+
? {
|
|
660
|
+
contentRoot: process.env.CONTENT_DIR || 'src/content/docs',
|
|
661
|
+
manifest: process.env.CANONICAL_MANIFEST || '',
|
|
662
|
+
navigation: process.env.PROVIDER_NAVIGATION || '',
|
|
663
|
+
version: process.env.DOCUMENTATION_LABEL || 'Latest stable',
|
|
664
|
+
}
|
|
665
|
+
: undefined);
|
|
666
|
+
if (canonicalProvider && (!canonicalProvider.manifest || !canonicalProvider.navigation)) {
|
|
667
|
+
throw new Error('[docs-theme] canonical-provider requires manifest and navigation');
|
|
668
|
+
}
|
|
654
669
|
const site = options.site || process.env.DOCS_SITE || 'https://f5-sales-demo.github.io';
|
|
655
670
|
const base = options.base || process.env.DOCS_BASE || '/';
|
|
656
671
|
const title = options.title || process.env.DOCS_TITLE || 'Documentation';
|
|
@@ -696,13 +711,13 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
|
696
711
|
console.warn('[docs-theme] OPENAPI_SPECS_CONFIG contains invalid JSON; skipping OpenAPI plugin.', e);
|
|
697
712
|
}
|
|
698
713
|
}
|
|
699
|
-
const megaMenuItems = options.megaMenuItems || defaultMegaMenuItems;
|
|
714
|
+
const megaMenuItems = options.megaMenuItems || (canonicalProvider ? providerMegaMenu(base) : defaultMegaMenuItems);
|
|
700
715
|
const head = options.head || defaultHead;
|
|
701
716
|
const logo = options.logo || { src: '@f5-sales-demo/docs-theme/assets/f5-distributed-cloud.svg' };
|
|
702
717
|
const additionalRemarkPlugins = options.additionalRemarkPlugins || [];
|
|
703
718
|
const additionalIntegrations = options.additionalIntegrations || [];
|
|
704
719
|
|
|
705
|
-
const federatedSearch = options.federatedSearch !== false;
|
|
720
|
+
const federatedSearch = !canonicalProvider && options.federatedSearch !== false;
|
|
706
721
|
const normalizedBase = base.replace(/\/+$/, '');
|
|
707
722
|
const repositoryCorpusPolicy = progressiveCorpusPolicy(llmsConfig);
|
|
708
723
|
const progressiveCorpus =
|
|
@@ -776,29 +791,35 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
|
776
791
|
),
|
|
777
792
|
]
|
|
778
793
|
: []),
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
794
|
+
...(!canonicalProvider
|
|
795
|
+
? [
|
|
796
|
+
starlightLlmsTxt({
|
|
797
|
+
projectName: title,
|
|
798
|
+
description,
|
|
799
|
+
rawContent: false,
|
|
800
|
+
optionalLinks: llmsOptionalLinks,
|
|
801
|
+
sidebarNav: true,
|
|
802
|
+
tieredHierarchy: true,
|
|
803
|
+
promote: llmsConfig.promote || ['index*', 'overview*'],
|
|
804
|
+
demote: llmsConfig.demote || ['references*'],
|
|
805
|
+
...(llmsFederatedSites.length > 0 ? { federatedSites: llmsFederatedSites } : {}),
|
|
806
|
+
...(llmsFederatedSiteCategories.length > 0 ? { federatedSiteCategories: llmsFederatedSiteCategories } : {}),
|
|
807
|
+
...(progressiveCorpus ? { progressiveCorpus } : {}),
|
|
808
|
+
}),
|
|
809
|
+
]
|
|
810
|
+
: []),
|
|
792
811
|
];
|
|
793
812
|
|
|
794
813
|
const contentDir = process.env.CONTENT_DIR || 'src/content/docs';
|
|
795
|
-
const subcategorySidebar = buildSubcategorySidebar(contentDir);
|
|
814
|
+
const subcategorySidebar = canonicalProvider ? [] : buildSubcategorySidebar(contentDir);
|
|
796
815
|
|
|
797
816
|
// Auto-detect i18n: enable locales only when content has an en/ subdirectory.
|
|
798
817
|
// Repos that haven't migrated to docs/en/ won't get a broken language selector.
|
|
799
818
|
const hasEnSubdir = fs.existsSync(path.resolve(contentDir, 'en'));
|
|
800
819
|
const resolvedLocales =
|
|
801
|
-
options.locales === false
|
|
820
|
+
canonicalProvider || options.locales === false
|
|
821
|
+
? undefined
|
|
822
|
+
: options.locales || (hasEnSubdir ? f5xcDefaultLocales : undefined);
|
|
802
823
|
const resolvedDefaultLocale = options.defaultLocale || f5xcDefaultLocale;
|
|
803
824
|
|
|
804
825
|
const localeHeadScripts: HeadEntry[] = [];
|
|
@@ -848,6 +869,9 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
|
848
869
|
return defineConfig({
|
|
849
870
|
site,
|
|
850
871
|
base,
|
|
872
|
+
...(canonicalProvider
|
|
873
|
+
? { experimental: { collectionStorage: { type: 'chunked' as const, chunkSize: 1024 * 1024 } } }
|
|
874
|
+
: {}),
|
|
851
875
|
...(resolvedLocales ? { redirects: { '/': `${normalizedBase}/${resolvedDefaultLocale}/` } } : {}),
|
|
852
876
|
markdown: {
|
|
853
877
|
processor: unified({
|
package/index.ts
CHANGED
|
@@ -10,8 +10,11 @@ export default function f5xcDocsTheme(): StarlightPlugin {
|
|
|
10
10
|
},
|
|
11
11
|
'config:setup'({ config, updateConfig, addRouteMiddleware, logger }) {
|
|
12
12
|
addRouteMiddleware({
|
|
13
|
-
entrypoint:
|
|
14
|
-
|
|
13
|
+
entrypoint:
|
|
14
|
+
process.env.DOCS_PROFILE === 'canonical-provider'
|
|
15
|
+
? '@f5-sales-demo/docs-theme/canonical-route-middleware'
|
|
16
|
+
: '@f5-sales-demo/docs-theme/route-middleware',
|
|
17
|
+
order: process.env.DOCS_PROFILE === 'canonical-provider' ? 'post' : 'pre',
|
|
15
18
|
});
|
|
16
19
|
updateConfig({
|
|
17
20
|
customCss: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@f5-sales-demo/docs-theme",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "F5 Distributed Cloud branded Starlight documentation theme",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"./src/i18n/locales.ts": "./src/i18n/locales.ts",
|
|
41
41
|
"./src/i18n/translator.ts": "./src/i18n/translator.ts",
|
|
42
42
|
"./src/i18n/prompt.ts": "./src/i18n/prompt.ts",
|
|
43
|
-
"./src/utils/localize-ecosystem-href.ts": "./src/utils/localize-ecosystem-href.ts"
|
|
43
|
+
"./src/utils/localize-ecosystem-href.ts": "./src/utils/localize-ecosystem-href.ts",
|
|
44
|
+
"./canonical-route-middleware": "./canonical-route-middleware.ts"
|
|
44
45
|
},
|
|
45
46
|
"files": [
|
|
46
47
|
"index.ts",
|
|
@@ -52,7 +53,8 @@
|
|
|
52
53
|
"styles/",
|
|
53
54
|
"assets/",
|
|
54
55
|
"components/",
|
|
55
|
-
"src/"
|
|
56
|
+
"src/",
|
|
57
|
+
"canonical-route-middleware.ts"
|
|
56
58
|
],
|
|
57
59
|
"keywords": [
|
|
58
60
|
"starlight",
|
|
@@ -70,7 +72,7 @@
|
|
|
70
72
|
},
|
|
71
73
|
"devDependencies": {
|
|
72
74
|
"@astrojs/markdown-remark": "^7.2.4",
|
|
73
|
-
"@astrojs/starlight": "
|
|
75
|
+
"@astrojs/starlight": "0.41.9",
|
|
74
76
|
"@f5-sales-demo/icons-f5-brand": "^0.2.0",
|
|
75
77
|
"@f5-sales-demo/icons-f5xc": "^0.3.2",
|
|
76
78
|
"@f5-sales-demo/icons-hashicorp-flight": "^0.3.0",
|
|
@@ -82,7 +84,7 @@
|
|
|
82
84
|
"@playwright/test": "^1.62.1",
|
|
83
85
|
"@types/react": "^19.2.18",
|
|
84
86
|
"@types/react-dom": "^19.2.7",
|
|
85
|
-
"astro": "
|
|
87
|
+
"astro": "7.2.8",
|
|
86
88
|
"react": "^19.2.8",
|
|
87
89
|
"react-dom": "^19.2.8",
|
|
88
90
|
"vitest": "^4.1.11"
|
package/route-middleware.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
2
2
|
import { defineRouteMiddleware } from '@astrojs/starlight/route-data';
|
|
3
3
|
import { itemLabels, menuLabels } from './src/i18n/mega-menu-translations.ts';
|
|
4
|
+
import { isCanonicalReference } from './src/utils/canonical-provider.ts';
|
|
4
5
|
|
|
5
6
|
const titleTranslations: Record<string, Record<string, string>> = { ...menuLabels, ...itemLabels };
|
|
6
7
|
|
|
@@ -55,7 +56,7 @@ export const onRequest = defineRouteMiddleware(async (context, next) => {
|
|
|
55
56
|
const route = context.locals.starlightRoute;
|
|
56
57
|
const entry = route.entry;
|
|
57
58
|
|
|
58
|
-
if (isIndexPage(entry.filePath)) {
|
|
59
|
+
if (!isCanonicalReference(entry.data) && isIndexPage(entry.filePath)) {
|
|
59
60
|
// Unconditionally hide TOC on index pages
|
|
60
61
|
route.toc = undefined;
|
|
61
62
|
}
|
package/src/content.config.ts
CHANGED
|
@@ -5,6 +5,24 @@ import { docsSchema } from '@astrojs/starlight/schema';
|
|
|
5
5
|
export const collections = {
|
|
6
6
|
docs: defineCollection({
|
|
7
7
|
loader: docsLoader(),
|
|
8
|
-
schema: docsSchema({
|
|
8
|
+
schema: docsSchema({
|
|
9
|
+
extend: z.object({
|
|
10
|
+
subcategory: z.string().optional(),
|
|
11
|
+
xcsh_docs: z
|
|
12
|
+
.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
collection_id: z.string(),
|
|
15
|
+
role: z.string(),
|
|
16
|
+
parent_id: z.string().nullable(),
|
|
17
|
+
child_ids: z.array(z.string()),
|
|
18
|
+
schema_path: z.array(z.string()),
|
|
19
|
+
provider_type: z.string(),
|
|
20
|
+
body_sha256: z.string(),
|
|
21
|
+
source_url: z.string().optional(),
|
|
22
|
+
})
|
|
23
|
+
.passthrough()
|
|
24
|
+
.optional(),
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
9
27
|
}),
|
|
10
28
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { collectionNavigation, isCanonicalReference, providerSections } from './canonical-provider';
|
|
3
|
+
|
|
4
|
+
describe('canonical provider navigation', () => {
|
|
5
|
+
it('includes all four provider families and setup and guides', () => {
|
|
6
|
+
expect(providerSections.map((s) => s.path)).toEqual([
|
|
7
|
+
'provider/setup',
|
|
8
|
+
'guides',
|
|
9
|
+
'resources',
|
|
10
|
+
'data-sources',
|
|
11
|
+
'actions',
|
|
12
|
+
'ephemeral-resources',
|
|
13
|
+
]);
|
|
14
|
+
});
|
|
15
|
+
it('retains canonical index navigation and TOC', () => {
|
|
16
|
+
expect(isCanonicalReference({ xcsh_docs: { role: 'reference' } })).toBe(true);
|
|
17
|
+
expect(isCanonicalReference({})).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
it('includes only the active collection, with parent relationships', () => {
|
|
20
|
+
const pages = [
|
|
21
|
+
{ id: 'a', collection_id: 'one', parent_id: null, title: 'One', slug: 'resources/one' },
|
|
22
|
+
{ id: 'b', collection_id: 'one', parent_id: 'a', title: 'Property', slug: 'resources/one/properties' },
|
|
23
|
+
{ id: 'c', collection_id: 'two', parent_id: null, title: 'Two', slug: 'resources/two' },
|
|
24
|
+
];
|
|
25
|
+
const nav = collectionNavigation(pages, 'one', '/version/', 'resources/one/properties');
|
|
26
|
+
expect(JSON.stringify(nav)).not.toContain('Two');
|
|
27
|
+
expect(nav[0].entries[1].href).toBe('/version/resources/one/properties/');
|
|
28
|
+
expect(nav[0].entries[1].isCurrent).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
2
|
+
export const providerSections = [
|
|
3
|
+
{ label: 'Setup', path: 'provider/setup', icon: 'seti:terraform' },
|
|
4
|
+
{ label: 'Guides', path: 'guides', icon: 'open-book' },
|
|
5
|
+
{ label: 'Resources', path: 'resources', icon: 'puzzle' },
|
|
6
|
+
{ label: 'Data sources', path: 'data-sources', icon: 'document' },
|
|
7
|
+
{ label: 'Actions', path: 'actions', icon: 'rocket' },
|
|
8
|
+
{ label: 'Ephemeral resources', path: 'ephemeral-resources', icon: 'random' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export interface ProviderPage {
|
|
12
|
+
id: string;
|
|
13
|
+
collection_id: string;
|
|
14
|
+
parent_id: string | null;
|
|
15
|
+
title: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isCanonicalReference(data: { xcsh_docs?: { role?: string } }) {
|
|
20
|
+
return !!data.xcsh_docs?.role;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function collectionNavigation(pages: ProviderPage[], collection: string, base: string, current: string) {
|
|
24
|
+
const active = pages.filter((page) => page.collection_id === collection);
|
|
25
|
+
const ids = new Set(active.map((page) => page.id));
|
|
26
|
+
const children = new Map<string | null, ProviderPage[]>();
|
|
27
|
+
for (const page of active) {
|
|
28
|
+
const parent = page.parent_id && ids.has(page.parent_id) ? page.parent_id : null;
|
|
29
|
+
children.set(parent, [...(children.get(parent) || []), page]);
|
|
30
|
+
}
|
|
31
|
+
const link = (page: ProviderPage) => ({
|
|
32
|
+
type: 'link' as const,
|
|
33
|
+
label: page.title,
|
|
34
|
+
href: `${base.replace(/\/$/, '')}/${page.slug}/`,
|
|
35
|
+
isCurrent: page.slug === current,
|
|
36
|
+
badge: undefined,
|
|
37
|
+
attrs: {},
|
|
38
|
+
});
|
|
39
|
+
const tree = (page: ProviderPage, seen = new Set<string>()): StarlightRouteData['sidebar'][number] => {
|
|
40
|
+
if (seen.has(page.id)) throw new Error(`Cyclic canonical navigation: ${page.id}`);
|
|
41
|
+
const descendants = children.get(page.id) || [];
|
|
42
|
+
if (!descendants.length) return link(page);
|
|
43
|
+
const next = new Set([...seen, page.id]);
|
|
44
|
+
return {
|
|
45
|
+
type: 'group',
|
|
46
|
+
label: page.title,
|
|
47
|
+
collapsed: !current.startsWith(page.slug),
|
|
48
|
+
badge: undefined,
|
|
49
|
+
entries: [link(page), ...descendants.map((child) => tree(child, next))],
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
return (children.get(null) || []).map((page) => tree(page));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function providerMegaMenu(base: string) {
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
label: 'Provider documentation',
|
|
59
|
+
content: {
|
|
60
|
+
layout: 'grid' as const,
|
|
61
|
+
columns: 2 as const,
|
|
62
|
+
categories: [
|
|
63
|
+
{
|
|
64
|
+
title: 'Terraform xcsh',
|
|
65
|
+
items: providerSections.map((section) => ({
|
|
66
|
+
label: section.label,
|
|
67
|
+
href: `${base.replace(/\/$/, '')}/${section.path}/`,
|
|
68
|
+
})),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
}
|