@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.
- package/components/Footer.astro +2 -2
- package/components/MarkdownContent.astro +2 -4
- package/components/ProviderSidebar.astro +6 -0
- package/components/ProviderSidebarTree.astro +21 -0
- package/config.ts +8 -4
- package/index.ts +3 -0
- package/package.json +3 -2
- package/src/utils/share-provider-assets.mjs +61 -0
- package/src/utils/share-provider-assets.test.ts +25 -0
package/components/Footer.astro
CHANGED
|
@@ -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,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
|
-
? {
|
|
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.
|
|
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
|
+
});
|