@hagicode/hagilight-starlight 0.1.2-dev.5.1.c41051d → 0.1.2-dev.6.1.f3f7b3d
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/Footer.astro +70 -0
- package/index.mjs +123 -3
- package/package.json +2 -2
package/Footer.astro
CHANGED
|
@@ -1,12 +1,82 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Copyright from '@hagicode/hagilight/Copyright';
|
|
3
3
|
import DefaultFooter from '@astrojs/starlight/components/Footer.astro';
|
|
4
|
+
import { resolveSiteLinks, type SiteLinksOptions } from '@hagicode/hagilight/site-links';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: string;
|
|
8
|
+
links?: SiteLinksOptions;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { locale = Astro.locals?.starlightRoute?.locale, links } = Astro.props;
|
|
12
|
+
const resolvedLinks = resolveSiteLinks(locale, links);
|
|
4
13
|
---
|
|
5
14
|
|
|
6
15
|
<DefaultFooter />
|
|
16
|
+
<nav class="hagilight-site-links" aria-label={resolvedLinks.labels.quickLinks}>
|
|
17
|
+
{resolvedLinks.relatedSites.length > 0 && (
|
|
18
|
+
<section>
|
|
19
|
+
<h2>{resolvedLinks.labels.relatedSites}</h2>
|
|
20
|
+
<ul>
|
|
21
|
+
{resolvedLinks.relatedSites.map((site) => (
|
|
22
|
+
<li><a href={site.href}>{site.name}</a></li>
|
|
23
|
+
))}
|
|
24
|
+
</ul>
|
|
25
|
+
</section>
|
|
26
|
+
)}
|
|
27
|
+
<section>
|
|
28
|
+
<h2>{resolvedLinks.labels.quickLinks}</h2>
|
|
29
|
+
<ul>
|
|
30
|
+
{resolvedLinks.quick.map((link) => (
|
|
31
|
+
<li><a href={link.href} target={link.target} rel={link.rel}>{link.label}</a></li>
|
|
32
|
+
))}
|
|
33
|
+
</ul>
|
|
34
|
+
</section>
|
|
35
|
+
<section>
|
|
36
|
+
<h2>{resolvedLinks.labels.community}</h2>
|
|
37
|
+
<ul>
|
|
38
|
+
{resolvedLinks.community.map((link) => (
|
|
39
|
+
<li><a href={link.href} target={link.target} rel={link.rel}>{link.label}</a></li>
|
|
40
|
+
))}
|
|
41
|
+
</ul>
|
|
42
|
+
</section>
|
|
43
|
+
</nav>
|
|
7
44
|
<p class="hagilight-copyright"><Copyright name="HagiCode" /></p>
|
|
8
45
|
|
|
9
46
|
<style>
|
|
47
|
+
.hagilight-site-links {
|
|
48
|
+
display: grid;
|
|
49
|
+
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
|
50
|
+
gap: 1.5rem;
|
|
51
|
+
margin: 2rem auto 1rem;
|
|
52
|
+
max-width: 72rem;
|
|
53
|
+
padding-inline: 1rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.hagilight-site-links h2 {
|
|
57
|
+
margin: 0 0 0.75rem;
|
|
58
|
+
color: var(--sl-color-white);
|
|
59
|
+
font-size: var(--sl-text-sm);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.hagilight-site-links ul {
|
|
63
|
+
display: grid;
|
|
64
|
+
gap: 0.5rem;
|
|
65
|
+
margin: 0;
|
|
66
|
+
padding: 0;
|
|
67
|
+
list-style: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.hagilight-site-links a {
|
|
71
|
+
color: var(--sl-color-gray-3);
|
|
72
|
+
font-size: var(--sl-text-sm);
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.hagilight-site-links a:hover {
|
|
77
|
+
color: var(--sl-color-white);
|
|
78
|
+
}
|
|
79
|
+
|
|
10
80
|
.hagilight-copyright {
|
|
11
81
|
margin-block: 1rem;
|
|
12
82
|
color: var(--sl-color-gray-3);
|
package/index.mjs
CHANGED
|
@@ -1,19 +1,139 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { fileURLToPath } from 'node:url';
|
|
2
3
|
|
|
4
|
+
const GOOGLE_ID_PATTERN = /^G-[A-Z0-9]+$/u;
|
|
5
|
+
const FIFTY_ONE_LA_ID_PATTERN = /^[A-Za-z0-9_-]+$/u;
|
|
6
|
+
|
|
7
|
+
function resolveProvider(config, { name, idKey, pattern }) {
|
|
8
|
+
if (config === undefined) return undefined;
|
|
9
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
10
|
+
throw new TypeError(`Hagilight ${name} analytics options must be an object.`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const enabled = config.enabled ?? Boolean(config[idKey]);
|
|
14
|
+
if (!enabled) return undefined;
|
|
15
|
+
|
|
16
|
+
const id = config[idKey];
|
|
17
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
18
|
+
throw new Error(`Hagilight ${name} analytics is enabled but its ${idKey} is missing.`);
|
|
19
|
+
}
|
|
20
|
+
if (!pattern.test(id)) {
|
|
21
|
+
throw new Error(`Hagilight ${name} analytics ${idKey} has an invalid format.`);
|
|
22
|
+
}
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createConfiguredIntegration(instanceId, serializedOptions, componentIds) {
|
|
27
|
+
const optionsId = `virtual:hagilight-starlight/${instanceId}/options`;
|
|
28
|
+
const footerPath = fileURLToPath(new URL('./Footer.astro', import.meta.url));
|
|
29
|
+
const promotionImport = serializedOptions.promotoEnabled
|
|
30
|
+
? "import PromotoBanner from '@hagicode/hagilight/PromotoBanner';"
|
|
31
|
+
: '';
|
|
32
|
+
const promotionRender = serializedOptions.promotoEnabled ? '<PromotoBanner />' : '';
|
|
33
|
+
const analyticsImport = serializedOptions.fiftyOneLaId
|
|
34
|
+
? "import Analytics51LA from '@hagicode/hagilight/Analytics51LA';"
|
|
35
|
+
: '';
|
|
36
|
+
const analyticsRender = serializedOptions.fiftyOneLaId
|
|
37
|
+
? '<Analytics51LA siteId={options.fiftyOneLaId} />'
|
|
38
|
+
: '';
|
|
39
|
+
const footerSource = `---
|
|
40
|
+
import Footer from ${JSON.stringify(footerPath)};
|
|
41
|
+
${promotionImport}
|
|
42
|
+
${analyticsImport}
|
|
43
|
+
import options from '${optionsId}';
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
<Footer locale={Astro.locals?.starlightRoute?.locale} links={options.links} />
|
|
47
|
+
${promotionRender}
|
|
48
|
+
${analyticsRender}
|
|
49
|
+
`;
|
|
50
|
+
const headSource = `---
|
|
51
|
+
import DefaultHead from '@astrojs/starlight/components/Head.astro';
|
|
52
|
+
import GoogleAnalytics from '@hagicode/hagilight/GoogleAnalytics';
|
|
53
|
+
import options from '${optionsId}';
|
|
54
|
+
const isNotFound = /(?:^|\\/)404(?:\\.html)?\\/?$/u.test(Astro.url.pathname);
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
<DefaultHead />
|
|
58
|
+
{!isNotFound && options.googleAnalyticsMeasurementId && (
|
|
59
|
+
<GoogleAnalytics measurementId={options.googleAnalyticsMeasurementId} />
|
|
60
|
+
)}
|
|
61
|
+
`;
|
|
62
|
+
const modules = new Map([
|
|
63
|
+
[componentIds.footer, footerSource],
|
|
64
|
+
...(componentIds.head ? [[componentIds.head, headSource]] : []),
|
|
65
|
+
[optionsId, `export default ${JSON.stringify(serializedOptions)};`],
|
|
66
|
+
]);
|
|
67
|
+
const vitePlugin = {
|
|
68
|
+
name: `@hagicode/hagilight-starlight:${instanceId}`,
|
|
69
|
+
resolveId(id) {
|
|
70
|
+
return modules.has(id) ? `\0${id}` : null;
|
|
71
|
+
},
|
|
72
|
+
load(id) {
|
|
73
|
+
return id.startsWith('\0') ? modules.get(id.slice(1)) ?? null : null;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
name: `@hagicode/hagilight-starlight:${instanceId}`,
|
|
79
|
+
hooks: {
|
|
80
|
+
'astro:config:setup'({ updateConfig }) {
|
|
81
|
+
updateConfig({ vite: { plugins: [vitePlugin] } });
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
3
87
|
export default function hagilight(options = {}) {
|
|
4
|
-
|
|
88
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
89
|
+
throw new TypeError('Hagilight Starlight options must be an object.');
|
|
90
|
+
}
|
|
91
|
+
if (options.analytics !== undefined
|
|
92
|
+
&& (!options.analytics || typeof options.analytics !== 'object' || Array.isArray(options.analytics))) {
|
|
93
|
+
throw new TypeError('Hagilight analytics options must be an object.');
|
|
94
|
+
}
|
|
95
|
+
const analytics = options.analytics ?? {};
|
|
96
|
+
const googleAnalyticsMeasurementId = resolveProvider(analytics.googleAnalytics, {
|
|
97
|
+
name: 'Google Analytics',
|
|
98
|
+
idKey: 'measurementId',
|
|
99
|
+
pattern: GOOGLE_ID_PATTERN,
|
|
100
|
+
});
|
|
101
|
+
const fiftyOneLaId = resolveProvider(analytics.fiftyOneLa, {
|
|
102
|
+
name: '51LA',
|
|
103
|
+
idKey: 'siteId',
|
|
104
|
+
pattern: FIFTY_ONE_LA_ID_PATTERN,
|
|
105
|
+
});
|
|
106
|
+
const instanceId = randomUUID();
|
|
107
|
+
const componentIds = {
|
|
108
|
+
footer: `virtual:hagilight-starlight/${instanceId}/Footer.astro`,
|
|
109
|
+
head: googleAnalyticsMeasurementId
|
|
110
|
+
? `virtual:hagilight-starlight/${instanceId}/Head.astro`
|
|
111
|
+
: undefined,
|
|
112
|
+
};
|
|
113
|
+
const serializedOptions = {
|
|
114
|
+
promotoEnabled: options.promoto?.enabled !== false,
|
|
115
|
+
links: options.links ?? {},
|
|
116
|
+
googleAnalyticsMeasurementId,
|
|
117
|
+
fiftyOneLaId,
|
|
118
|
+
};
|
|
119
|
+
|
|
5
120
|
return {
|
|
6
121
|
name: '@hagicode/hagilight-starlight',
|
|
7
122
|
hooks: {
|
|
8
|
-
'config:setup'({ config, updateConfig }) {
|
|
123
|
+
'config:setup'({ config, updateConfig, addIntegration }) {
|
|
9
124
|
if (config.components?.Footer) {
|
|
10
125
|
throw new Error('Hagilight cannot replace an existing Starlight Footer override.');
|
|
11
126
|
}
|
|
127
|
+
if (googleAnalyticsMeasurementId && config.components?.Head) {
|
|
128
|
+
throw new Error('Hagilight Google Analytics cannot replace an existing Starlight Head override. Compose @hagicode/hagilight/GoogleAnalytics in your Head instead.');
|
|
129
|
+
}
|
|
12
130
|
|
|
131
|
+
addIntegration(createConfiguredIntegration(instanceId, serializedOptions, componentIds));
|
|
13
132
|
updateConfig({
|
|
14
133
|
components: {
|
|
15
134
|
...config.components,
|
|
16
|
-
Footer:
|
|
135
|
+
Footer: componentIds.footer,
|
|
136
|
+
...(componentIds.head ? { Head: componentIds.head } : {}),
|
|
17
137
|
},
|
|
18
138
|
});
|
|
19
139
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/hagilight-starlight",
|
|
3
|
-
"version": "0.1.2-dev.
|
|
3
|
+
"version": "0.1.2-dev.6.1.f3f7b3d",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Starlight customizations for HagiCode documentation",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"*.astro"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@hagicode/hagilight": "0.1.2-dev.
|
|
25
|
+
"@hagicode/hagilight": "0.1.2-dev.6.1.f3f7b3d"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@astrojs/starlight": "^0.42.4",
|