@f5-sales-demo/docs-theme 4.8.0 → 4.9.1
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/components/CanadaSidebar.astro +77 -0
- package/config.ts +5 -7
- package/index.ts +6 -2
- package/package.json +2 -1
- package/src/utils/canada-sidebar-plugin.test.ts +27 -0
- package/src/utils/localize-ecosystem-href.test.ts +13 -13
- package/src/utils/publication-profiles.test.ts +28 -13
- package/src/utils/publication-profiles.ts +17 -13
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
import MobileMenuFooter from 'virtual:starlight/components/MobileMenuFooter';
|
|
3
|
+
import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro';
|
|
4
|
+
import SidebarSublist from '@astrojs/starlight/components/SidebarSublist.astro';
|
|
5
|
+
import SidebarRestorePoint from '@astrojs/starlight/components/SidebarRestorePoint.astro';
|
|
6
|
+
|
|
7
|
+
const { sidebar } = Astro.locals.starlightRoute;
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<SidebarPersister>
|
|
11
|
+
<ul class="canada-sidebar">
|
|
12
|
+
{sidebar.map((entry) => {
|
|
13
|
+
if (entry.type === 'link') {
|
|
14
|
+
return <li><a class="home-link" href={entry.href} aria-current={entry.isCurrent ? 'page' : undefined}>{entry.label}</a></li>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [overview, ...children] = entry.entries;
|
|
18
|
+
if (!overview || overview.type !== 'link') {
|
|
19
|
+
return <li><SidebarSublist sublist={[entry]} /></li>;
|
|
20
|
+
}
|
|
21
|
+
const active = overview.isCurrent || children.some((child) => child.type === 'link' && child.isCurrent);
|
|
22
|
+
|
|
23
|
+
return <li class="stage">
|
|
24
|
+
<a class="stage-link" href={overview.href} aria-current={overview.isCurrent ? 'page' : undefined}>{entry.label}</a>
|
|
25
|
+
<details open={active || !entry.collapsed}>
|
|
26
|
+
<summary aria-label={`Toggle ${entry.label} pages`} title={`Toggle ${entry.label} pages`}><span class="caret" aria-hidden="true"></span></summary>
|
|
27
|
+
<SidebarRestorePoint />
|
|
28
|
+
<SidebarSublist sublist={children} nested />
|
|
29
|
+
</details>
|
|
30
|
+
</li>;
|
|
31
|
+
})}
|
|
32
|
+
</ul>
|
|
33
|
+
</SidebarPersister>
|
|
34
|
+
|
|
35
|
+
<div class="md:sl-hidden"><MobileMenuFooter /></div>
|
|
36
|
+
|
|
37
|
+
<style>
|
|
38
|
+
@layer starlight.core {
|
|
39
|
+
.canada-sidebar { list-style: none; margin: 0; padding: 0; }
|
|
40
|
+
.canada-sidebar > li + li { margin-top: 0.5rem; }
|
|
41
|
+
.stage { position: relative; }
|
|
42
|
+
.home-link, .stage-link {
|
|
43
|
+
display: block;
|
|
44
|
+
border-radius: 0.25rem;
|
|
45
|
+
color: var(--sl-color-gray-2);
|
|
46
|
+
line-height: 1.4;
|
|
47
|
+
padding: 0.3em 2.5rem 0.3em 0.5rem;
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
font-size: var(--sl-text-sm);
|
|
50
|
+
}
|
|
51
|
+
.stage-link { color: var(--sl-color-white); font-weight: 600; }
|
|
52
|
+
.home-link:hover, .home-link:focus, .stage-link:hover, .stage-link:focus { color: var(--sl-color-white); }
|
|
53
|
+
a[aria-current='page'], a[aria-current='page']:hover, a[aria-current='page']:focus {
|
|
54
|
+
color: var(--sl-color-text-invert);
|
|
55
|
+
background-color: var(--sl-color-text-accent);
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
}
|
|
58
|
+
summary {
|
|
59
|
+
position: absolute;
|
|
60
|
+
inset-block-start: 0;
|
|
61
|
+
inset-inline-end: 0;
|
|
62
|
+
display: grid;
|
|
63
|
+
place-items: center;
|
|
64
|
+
width: 2rem;
|
|
65
|
+
height: 2rem;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
border-radius: 0.25rem;
|
|
68
|
+
list-style: none;
|
|
69
|
+
}
|
|
70
|
+
summary::-webkit-details-marker { display: none; }
|
|
71
|
+
summary:hover, summary:focus-visible { color: var(--sl-color-white); background: var(--sl-color-gray-5); }
|
|
72
|
+
.caret { width: 0.55rem; height: 0.55rem; border-block-start: 2px solid currentColor; border-inline-end: 2px solid currentColor; transform: rotate(45deg); transition: transform 0.2s ease; }
|
|
73
|
+
details[open] .caret { transform: rotate(135deg); }
|
|
74
|
+
:global([dir='rtl']) .caret { transform: rotate(225deg); }
|
|
75
|
+
:global([dir='rtl']) details[open] .caret { transform: rotate(135deg); }
|
|
76
|
+
}
|
|
77
|
+
</style>
|
package/config.ts
CHANGED
|
@@ -156,11 +156,9 @@ export const defaultMegaMenuItems: MegaMenuItem[] = [
|
|
|
156
156
|
translations: categoryTitles['App & API Security'],
|
|
157
157
|
items: [
|
|
158
158
|
{
|
|
159
|
-
label: 'Web App
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
descriptionTranslations: itemDescriptions['Firewall policies and configuration'],
|
|
163
|
-
href: 'https://f5-sales-demo.github.io/waf/',
|
|
159
|
+
label: 'Web App & API Protection',
|
|
160
|
+
description: 'Web application and API protection demonstrations',
|
|
161
|
+
href: 'https://f5-sales-demo.github.io/webapp-api-protection/',
|
|
164
162
|
icon: resolveMegaMenuIcon('f5xc:web-app-and-api-protection'),
|
|
165
163
|
},
|
|
166
164
|
{
|
|
@@ -660,7 +658,7 @@ export const federatedSearchSites = [
|
|
|
660
658
|
{ repo: 'bot-standard', label: 'Bot Standard' },
|
|
661
659
|
{ repo: 'bot-advanced', label: 'Bot Advanced' },
|
|
662
660
|
{ repo: 'ddos', label: 'DDoS' },
|
|
663
|
-
{ repo: '
|
|
661
|
+
{ repo: 'webapp-api-protection', label: 'Web App & API Protection' },
|
|
664
662
|
{ repo: 'api-protection', label: 'API Security' },
|
|
665
663
|
{ repo: 'xcsh', label: 'xcsh' },
|
|
666
664
|
{ repo: 'xcsh-action', label: 'xcsh Manifest Automation' },
|
|
@@ -782,7 +780,7 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
|
782
780
|
starlightMegaMenu({ items: megaMenuItems, mobileLabels }),
|
|
783
781
|
starlightVideosPlugin(),
|
|
784
782
|
starlightImageZoom(),
|
|
785
|
-
f5xcDocsTheme(),
|
|
783
|
+
f5xcDocsTheme(githubRepository),
|
|
786
784
|
starlightScrollToTop({
|
|
787
785
|
showTooltip: true,
|
|
788
786
|
tooltipText: {
|
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StarlightPlugin } from '@astrojs/starlight/types';
|
|
2
2
|
import { translations } from './src/i18n/translations.ts';
|
|
3
3
|
|
|
4
|
-
export default function f5xcDocsTheme(): StarlightPlugin {
|
|
4
|
+
export default function f5xcDocsTheme(repository = process.env.GITHUB_REPOSITORY || ''): StarlightPlugin {
|
|
5
5
|
return {
|
|
6
6
|
name: '@f5-sales-demo/docs-theme',
|
|
7
7
|
hooks: {
|
|
@@ -29,7 +29,11 @@ export default function f5xcDocsTheme(): StarlightPlugin {
|
|
|
29
29
|
? {
|
|
30
30
|
Sidebar: '@f5-sales-demo/docs-theme/components/ProviderSidebar.astro',
|
|
31
31
|
}
|
|
32
|
-
:
|
|
32
|
+
: repository === 'f5-sales-demo/canada'
|
|
33
|
+
? {
|
|
34
|
+
Sidebar: '@f5-sales-demo/docs-theme/components/CanadaSidebar.astro',
|
|
35
|
+
}
|
|
36
|
+
: {}),
|
|
33
37
|
Banner: '@f5-sales-demo/docs-theme/components/Banner.astro',
|
|
34
38
|
EditLink: '@f5-sales-demo/docs-theme/components/EditLink.astro',
|
|
35
39
|
Footer: '@f5-sales-demo/docs-theme/components/Footer.astro',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@f5-sales-demo/docs-theme",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.1",
|
|
4
4
|
"description": "F5 Distributed Cloud branded Starlight documentation theme",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"./src/utils/localize-ecosystem-href.ts": "./src/utils/localize-ecosystem-href.ts",
|
|
44
44
|
"./canonical-route-middleware": "./canonical-route-middleware.ts",
|
|
45
45
|
"./components/ProviderSidebar.astro": "./components/ProviderSidebar.astro",
|
|
46
|
+
"./components/CanadaSidebar.astro": "./components/CanadaSidebar.astro",
|
|
46
47
|
"./components/ProviderHeader.astro": "./components/ProviderHeader.astro",
|
|
47
48
|
"./components/SharedHeader.astro": "./components/SharedHeader.astro",
|
|
48
49
|
"./assets/canada-flag.svg": "./assets/canada-flag.svg",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import f5xcDocsTheme from '../../index';
|
|
3
|
+
|
|
4
|
+
function configuredComponents(repository: string) {
|
|
5
|
+
const updateConfig = vi.fn();
|
|
6
|
+
const setup = f5xcDocsTheme(repository).hooks?.['config:setup'];
|
|
7
|
+
if (!setup) throw new Error('theme setup hook is missing');
|
|
8
|
+
setup({
|
|
9
|
+
config: { components: {} },
|
|
10
|
+
updateConfig,
|
|
11
|
+
addRouteMiddleware: vi.fn(),
|
|
12
|
+
logger: { info: vi.fn() },
|
|
13
|
+
} as never);
|
|
14
|
+
return updateConfig.mock.calls[0][0].components;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('Canada-only sidebar override', () => {
|
|
18
|
+
it('selects the linked stage sidebar for Canada', () => {
|
|
19
|
+
expect(configuredComponents('f5-sales-demo/canada').Sidebar).toBe(
|
|
20
|
+
'@f5-sales-demo/docs-theme/components/CanadaSidebar.astro',
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('leaves other repositories on their existing sidebar', () => {
|
|
25
|
+
expect(configuredComponents('f5-sales-demo/multi-cloud-networking')).not.toHaveProperty('Sidebar');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -21,28 +21,28 @@ describe('langToSlug', () => {
|
|
|
21
21
|
|
|
22
22
|
describe('localizeEcosystemHref', () => {
|
|
23
23
|
it('injects locale slug into ecosystem URLs', () => {
|
|
24
|
-
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/
|
|
25
|
-
expect(result).toBe('https://f5-sales-demo.github.io/
|
|
24
|
+
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/webapp-api-protection/', 'fr');
|
|
25
|
+
expect(result).toBe('https://f5-sales-demo.github.io/webapp-api-protection/fr/');
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
it('does not double-inject if locale already present', () => {
|
|
29
|
-
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/
|
|
30
|
-
expect(result).toBe('https://f5-sales-demo.github.io/
|
|
29
|
+
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/webapp-api-protection/fr/', 'fr');
|
|
30
|
+
expect(result).toBe('https://f5-sales-demo.github.io/webapp-api-protection/fr/');
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
it('returns href unchanged for non-ecosystem hosts', () => {
|
|
34
|
-
const result = localizeEcosystemHref('https://example.com/
|
|
35
|
-
expect(result).toBe('https://example.com/
|
|
34
|
+
const result = localizeEcosystemHref('https://example.com/webapp-api-protection/', 'fr');
|
|
35
|
+
expect(result).toBe('https://example.com/webapp-api-protection/');
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
it('returns href unchanged for invalid locale slug', () => {
|
|
39
|
-
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/
|
|
40
|
-
expect(result).toBe('https://f5-sales-demo.github.io/
|
|
39
|
+
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/webapp-api-protection/', 'xx-invalid');
|
|
40
|
+
expect(result).toBe('https://f5-sales-demo.github.io/webapp-api-protection/');
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('returns href unchanged for empty locale', () => {
|
|
44
|
-
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/
|
|
45
|
-
expect(result).toBe('https://f5-sales-demo.github.io/
|
|
44
|
+
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/webapp-api-protection/', '');
|
|
45
|
+
expect(result).toBe('https://f5-sales-demo.github.io/webapp-api-protection/');
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('returns invalid URLs unchanged', () => {
|
|
@@ -53,14 +53,14 @@ describe('localizeEcosystemHref', () => {
|
|
|
53
53
|
it('works with all 13 supported locale slugs', () => {
|
|
54
54
|
const slugs = ['en', 'fr', 'es', 'de', 'pt-br', 'ja', 'ko', 'zh-cn', 'zh-tw', 'ar', 'it', 'hi', 'th'];
|
|
55
55
|
for (const slug of slugs) {
|
|
56
|
-
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/
|
|
56
|
+
const result = localizeEcosystemHref('https://f5-sales-demo.github.io/webapp-api-protection/', slug);
|
|
57
57
|
expect(result).toContain(`/${slug}/`);
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('supports custom ecosystem host', () => {
|
|
62
|
-
const result = localizeEcosystemHref('https://docs.example.com/
|
|
63
|
-
expect(result).toBe('https://docs.example.com/
|
|
62
|
+
const result = localizeEcosystemHref('https://docs.example.com/webapp-api-protection/', 'fr', 'docs.example.com');
|
|
63
|
+
expect(result).toBe('https://docs.example.com/webapp-api-protection/fr/');
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it('does not localize single-locale ecosystem sites (would 404)', () => {
|
|
@@ -19,21 +19,36 @@ describe('Canadian publication', () => {
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
describe('publication profile sidebar', () => {
|
|
22
|
-
it('
|
|
22
|
+
it('orders four linked stages and covers all 14 pages once', () => {
|
|
23
23
|
const sidebar = publicationSidebar('f5-sales-demo/canada', []);
|
|
24
|
-
expect(sidebar?.
|
|
25
|
-
const
|
|
26
|
-
expect(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
expect(sidebar?.[0]).toEqual({ slug: 'index' });
|
|
25
|
+
const stages = sidebar?.slice(1) ?? [];
|
|
26
|
+
expect(stages.map((stage) => ('label' in stage ? stage.label : ''))).toEqual([
|
|
27
|
+
'Design',
|
|
28
|
+
'Deploy',
|
|
29
|
+
'Verify',
|
|
30
|
+
'Operate',
|
|
31
|
+
]);
|
|
32
|
+
const expected = [
|
|
33
|
+
['design', 'use-case', 'architecture'],
|
|
34
|
+
['deploy', 'deployment', 'terraform'],
|
|
35
|
+
['verify', 'presentation', 'verification'],
|
|
36
|
+
['operate', 'troubleshooting', 'failover', 'teardown'],
|
|
37
|
+
];
|
|
38
|
+
const routes = ['index'];
|
|
39
|
+
stages.forEach((stage, index) => {
|
|
40
|
+
expect(stage).toMatchObject({ collapsed: true });
|
|
41
|
+
if (!('items' in stage)) throw new Error('Canada stage must have items');
|
|
42
|
+
const stageRoutes = stage.items.map((item) => {
|
|
43
|
+
if (!('slug' in item)) throw new Error('Canada stage item must be a page');
|
|
44
|
+
return item.slug;
|
|
45
|
+
});
|
|
46
|
+
expect(stageRoutes).toEqual(expected[index]);
|
|
47
|
+
routes.push(...stageRoutes);
|
|
33
48
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
expect(new Set(routes).size).toBe(14);
|
|
50
|
+
expect(routes).toHaveLength(14);
|
|
51
|
+
expect(stages.every((stage) => 'label' in stage && stage.label.split(/\s+/).length <= 3)).toBe(true);
|
|
37
52
|
});
|
|
38
53
|
it('preserves fallback navigation for every repository without a sidebar profile', () => {
|
|
39
54
|
const fallback = [{ label: 'Existing', slug: 'en/existing' }];
|
|
@@ -17,21 +17,25 @@ export const publicationProfiles: Record<string, PublicationProfile> = {
|
|
|
17
17
|
favicon: '/assets/canada-favicon.svg',
|
|
18
18
|
sidebar: [
|
|
19
19
|
{ slug: 'index' },
|
|
20
|
-
{ slug: 'use-case' },
|
|
21
20
|
{
|
|
22
|
-
label: '
|
|
21
|
+
label: 'Design',
|
|
23
22
|
collapsed: true,
|
|
24
|
-
items: [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
],
|
|
23
|
+
items: [{ slug: 'design' }, { slug: 'use-case' }, { slug: 'architecture' }],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: 'Deploy',
|
|
27
|
+
collapsed: true,
|
|
28
|
+
items: [{ slug: 'deploy' }, { slug: 'deployment' }, { slug: 'terraform' }],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: 'Verify',
|
|
32
|
+
collapsed: true,
|
|
33
|
+
items: [{ slug: 'verify' }, { slug: 'presentation' }, { slug: 'verification' }],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: 'Operate',
|
|
37
|
+
collapsed: true,
|
|
38
|
+
items: [{ slug: 'operate' }, { slug: 'troubleshooting' }, { slug: 'failover' }, { slug: 'teardown' }],
|
|
35
39
|
},
|
|
36
40
|
],
|
|
37
41
|
},
|