@f5-sales-demo/docs-theme 4.4.1 → 4.4.3

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.
@@ -5,7 +5,7 @@ import Icon from './Icon.astro';
5
5
 
6
6
  <Default {...Astro.props}><slot /></Default>
7
7
 
8
- <div class="social-links">
8
+ {process.env.DOCS_PROFILE !== 'canonical-provider' && <div class="social-links">
9
9
  <a href="https://www.facebook.com/f5incorporated" target="_blank" rel="noopener noreferrer" aria-label="Facebook">
10
10
  <Icon name="hashicorp-flight:facebook-color" size="1.5rem" />
11
11
  </a>
@@ -21,7 +21,7 @@ import Icon from './Icon.astro';
21
21
  <a href="https://www.youtube.com/user/f5networksinc" target="_blank" rel="noopener noreferrer" aria-label="YouTube">
22
22
  <Icon name="hashicorp-flight:youtube-color" size="1.5rem" />
23
23
  </a>
24
- </div>
24
+ </div>}
25
25
 
26
26
  <style>
27
27
  .social-links {
@@ -7,7 +7,5 @@ import ImageZoom from 'starlight-image-zoom/components/ImageZoom.astro';
7
7
  import StarlightVideosMarkdownContent from 'starlight-videos/components/MarkdownContent.astro';
8
8
  ---
9
9
 
10
- <ImageZoom />
11
- <StarlightVideosMarkdownContent>
12
- {canonical ? <ProviderReference><slot /></ProviderReference> : <slot />}
13
- </StarlightVideosMarkdownContent>
10
+ {!canonical && <ImageZoom />}
11
+ {canonical ? <div class="sl-markdown-content"><ProviderReference><slot /></ProviderReference></div> : <StarlightVideosMarkdownContent><slot /></StarlightVideosMarkdownContent>}
@@ -0,0 +1,6 @@
1
+ ---
2
+ import ProviderSidebarTree from './ProviderSidebarTree.astro';
3
+
4
+ const { sidebar } = Astro.locals.starlightRoute;
5
+ ---
6
+ <nav aria-label="Provider reference"><ProviderSidebarTree entries={sidebar} /></nav>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import type { StarlightRouteData } from '@astrojs/starlight/route-data';
3
+
4
+ interface Props {
5
+ entries: StarlightRouteData['sidebar'];
6
+ }
7
+ const { entries } = Astro.props;
8
+ ---
9
+ <ul>{entries.map(entry => <li>{entry.type === 'link'
10
+ ? <a href={entry.href} aria-current={entry.isCurrent ? 'page' : undefined}>{entry.label}</a>
11
+ : <details open={!entry.collapsed}><summary>{entry.label}</summary><Astro.self entries={entry.entries} /></details>}
12
+ </li>)}</ul>
13
+ <style>
14
+ ul { list-style: none; padding: 0; margin: 0; }
15
+ ul ul { padding-inline-start: .75rem; border-inline-start: 1px solid var(--sl-color-gray-5); }
16
+ li { margin: .3rem 0; overflow-wrap: anywhere; }
17
+ a { display: block; padding: .2rem .4rem; color: var(--sl-color-gray-2); text-decoration: none; font-size: var(--sl-text-sm); }
18
+ a:hover { color: var(--sl-color-white); }
19
+ a[aria-current] { background: var(--sl-color-accent-low); color: var(--sl-color-accent-high); border-inline-start: 2px solid var(--sl-color-accent); }
20
+ summary { cursor: pointer; padding: .25rem; font-weight: 600; font-size: var(--sl-text-sm); }
21
+ </style>
package/config.ts CHANGED
@@ -33,6 +33,7 @@ import { sidebarTranslations } from './src/i18n/translations.ts';
33
33
  import remarkMermaid from './src/plugins/remark-mermaid.mjs';
34
34
  import { providerMegaMenu } from './src/utils/canonical-provider.ts';
35
35
  import { resolveMegaMenuIcon } from './src/utils/resolve-icon.ts';
36
+ import providerSharedAssets from './src/utils/share-provider-assets.mjs';
36
37
  import { buildSubcategorySidebar } from './src/utils/subcategory-sidebar.ts';
37
38
 
38
39
  export type { LocaleConfig } from './src/i18n/locales.ts';
@@ -712,7 +713,7 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
712
713
  }
713
714
  }
714
715
  const megaMenuItems = options.megaMenuItems || (canonicalProvider ? providerMegaMenu(base) : defaultMegaMenuItems);
715
- const head = options.head || defaultHead;
716
+ const head = options.head || (canonicalProvider ? [] : defaultHead);
716
717
  const logo = options.logo || { src: '@f5-sales-demo/docs-theme/assets/f5-distributed-cloud.svg' };
717
718
  const additionalRemarkPlugins = options.additionalRemarkPlugins || [];
718
719
  const additionalIntegrations = options.additionalIntegrations || [];
@@ -774,8 +775,7 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
774
775
  progressRingColor: '#e4002b',
775
776
  showOnHomepage: false,
776
777
  }),
777
- starlightHeadingBadges(),
778
- starlightPageActions(),
778
+ ...(!canonicalProvider ? [starlightHeadingBadges(), starlightPageActions()] : []),
779
779
  starlightIconsPlugin(),
780
780
  ...(openAPISpecs.length > 0
781
781
  ? [
@@ -870,7 +870,10 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
870
870
  site,
871
871
  base,
872
872
  ...(canonicalProvider
873
- ? { experimental: { collectionStorage: { type: 'chunked' as const, chunkSize: 1024 * 1024 } } }
873
+ ? {
874
+ experimental: { collectionStorage: { type: 'chunked' as const, chunkSize: 1024 * 1024 } },
875
+ vite: { build: { assetsInlineLimit: 0 } },
876
+ }
874
877
  : {}),
875
878
  ...(resolvedLocales ? { redirects: { '/': `${normalizedBase}/${resolvedDefaultLocale}/` } } : {}),
876
879
  markdown: {
@@ -920,6 +923,7 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
920
923
  ],
921
924
  }),
922
925
  react(),
926
+ ...(canonicalProvider ? [providerSharedAssets()] : []),
923
927
  ...additionalIntegrations,
924
928
  ],
925
929
  });
package/index.ts CHANGED
@@ -24,6 +24,9 @@ export default function f5xcDocsTheme(): StarlightPlugin {
24
24
  ],
25
25
  components: {
26
26
  ...config.components,
27
+ ...(process.env.DOCS_PROFILE === 'canonical-provider'
28
+ ? { Sidebar: '@f5-sales-demo/docs-theme/components/ProviderSidebar.astro' }
29
+ : {}),
27
30
  Banner: '@f5-sales-demo/docs-theme/components/Banner.astro',
28
31
  EditLink: '@f5-sales-demo/docs-theme/components/EditLink.astro',
29
32
  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.4.1",
3
+ "version": "4.4.3",
4
4
  "description": "F5 Distributed Cloud branded Starlight documentation theme",
5
5
  "type": "module",
6
6
  "engines": {
@@ -41,7 +41,8 @@
41
41
  "./src/i18n/translator.ts": "./src/i18n/translator.ts",
42
42
  "./src/i18n/prompt.ts": "./src/i18n/prompt.ts",
43
43
  "./src/utils/localize-ecosystem-href.ts": "./src/utils/localize-ecosystem-href.ts",
44
- "./canonical-route-middleware": "./canonical-route-middleware.ts"
44
+ "./canonical-route-middleware": "./canonical-route-middleware.ts",
45
+ "./components/ProviderSidebar.astro": "./components/ProviderSidebar.astro"
45
46
  },
46
47
  "files": [
47
48
  "index.ts",
@@ -0,0 +1,61 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const hash = (value) => createHash('sha256').update(value).digest('hex').slice(0, 24);
7
+ // Input is Astro-generated HTML. Keep each asset's original execution position
8
+ // and SVG attributes; only identical generated bytes share a file.
9
+ export async function shareProviderAssets(root, base = '/') {
10
+ const files = (await readdir(root, { recursive: true })).filter((file) => file.endsWith('.html'));
11
+ const assetRoot = join(root, '_shared');
12
+ const assets = new Map();
13
+ const prefix = `${base.replace(/\/$/, '')}/_shared/`;
14
+ let saved = 0;
15
+ await mkdir(assetRoot, { recursive: true });
16
+ for (const file of files) {
17
+ const path = join(root, file);
18
+ const before = await readFile(path, 'utf8');
19
+ let html = before.replace(/<script\b([^>]*)>([\s\S]*?)<\/script>/g, (original, attrs, body) => {
20
+ if (/\bsrc\s*=/.test(attrs) || !body.trim() || /type=["'](?:application\/|text\/template)/.test(attrs))
21
+ return original;
22
+ const name = `${hash(body)}.js`;
23
+ assets.set(name, body);
24
+ return `<script${attrs} src="${prefix}${name}"></script>`;
25
+ });
26
+ const islands = [];
27
+ html = html.replace(/<astro-island\b[\s\S]*?<\/astro-island>/g, (island) => {
28
+ islands.push(island);
29
+ return `<!--provider-island-${islands.length - 1}-->`;
30
+ });
31
+ html = html.replace(/<svg\b([^>]*)>([\s\S]*?)<\/svg>/g, (original, attrs, body) => {
32
+ // Sprite definitions and interactive SVG content must remain inline.
33
+ if (/<(?:symbol|script|foreignObject)\b/.test(body) || /\bid=/.test(attrs) || /\bon\w+=/.test(attrs))
34
+ return original;
35
+ const name = `${hash(body)}.svg`;
36
+ assets.set(name, `<svg xmlns="http://www.w3.org/2000/svg"><symbol id="icon">${body}</symbol></svg>`);
37
+ return `<svg${attrs}><use href="${prefix}${name}#icon"></use></svg>`;
38
+ });
39
+ html = html.replace(/<!--provider-island-(\d+)-->/g, (_, index) => islands[Number(index)]);
40
+ saved += Buffer.byteLength(before) - Buffer.byteLength(html);
41
+ await writeFile(path, html);
42
+ }
43
+ let assetBytes = 0;
44
+ for (const [name, body] of assets) {
45
+ await writeFile(join(assetRoot, name), body);
46
+ assetBytes += Buffer.byteLength(body);
47
+ }
48
+ return { pages: files.length, assets: assets.size, savedBytes: saved - assetBytes };
49
+ }
50
+
51
+ export default function providerSharedAssets() {
52
+ return {
53
+ name: 'f5-canonical-shared-assets',
54
+ hooks: {
55
+ 'astro:build:done': async ({ dir, logger }) => {
56
+ const result = await shareProviderAssets(fileURLToPath(dir), process.env.DOCS_BASE || '/');
57
+ logger.info(`Shared canonical assets: ${JSON.stringify(result)}`);
58
+ },
59
+ },
60
+ };
61
+ }
@@ -0,0 +1,25 @@
1
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { expect, it } from 'vitest';
5
+ import { shareProviderAssets } from './share-provider-assets.mjs';
6
+
7
+ it('shares identical generated assets, preserving script order and SVG dimensions', async () => {
8
+ const root = await mkdtemp(join(tmpdir(), 'shared-provider-'));
9
+ try {
10
+ const text =
11
+ '<h1>Reference</h1><script>window.example=1;</script><svg width="16" viewBox="0 0 24 24"><path d="M1 2"/></svg><script src="keep.js"></script>';
12
+ await writeFile(join(root, 'one.html'), text);
13
+ await writeFile(join(root, 'two.html'), text);
14
+ const result = await shareProviderAssets(root, '/provider/version/');
15
+ expect(result.assets).toBe(2);
16
+ const html = await readFile(join(root, 'one.html'), 'utf8');
17
+ expect(html).toContain('<h1>Reference</h1>');
18
+ expect(html).toMatch(/<script src="\/provider\/version\/_shared\/[a-f0-9]+\.js"><\/script>/);
19
+ expect(html).toContain('<svg width="16" viewBox="0 0 24 24"><use');
20
+ expect(html).toContain('<script src="keep.js"></script>');
21
+ expect(await readFile(join(root, 'two.html'), 'utf8')).toBe(html);
22
+ } finally {
23
+ await rm(root, { recursive: true, force: true });
24
+ }
25
+ });