@hagicode/hagilight-starlight 0.11.0 → 0.11.1-dev.133.1.338317a
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/ArticlePromotion.astro +180 -41
- package/README.md +46 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +48 -6
- package/dist/site-metadata.d.ts +38 -0
- package/dist/site-metadata.js +109 -0
- package/package.json +2 -2
package/ArticlePromotion.astro
CHANGED
|
@@ -57,7 +57,8 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
57
57
|
href={homeHref}
|
|
58
58
|
{...showcaseTag('navigation', 'home')}
|
|
59
59
|
>
|
|
60
|
-
{copy.visitLabel}
|
|
60
|
+
<span>{copy.visitLabel}</span>
|
|
61
|
+
<svg class="hagilight-article-promotion__arrow" aria-hidden="true" focusable="false" viewBox="0 0 20 20" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10h11M11 5l5 5-5 5" /></svg>
|
|
61
62
|
</a>
|
|
62
63
|
<a
|
|
63
64
|
class="hagilight-article-promotion__cta hagilight-article-promotion__cta--secondary"
|
|
@@ -137,7 +138,7 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
137
138
|
class="hagilight-article-promotion__downloads"
|
|
138
139
|
href={downloadsHref}
|
|
139
140
|
{...showcaseTag('download', 'downloadClient')}
|
|
140
|
-
>{copy.allDownloadsLabel}</a>
|
|
141
|
+
><span>{copy.allDownloadsLabel}</span><svg class="hagilight-article-promotion__arrow" aria-hidden="true" focusable="false" viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10h11M11 5l5 5-5 5" /></svg></a>
|
|
141
142
|
</div>
|
|
142
143
|
</div>
|
|
143
144
|
|
|
@@ -151,43 +152,74 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
151
152
|
|
|
152
153
|
<style>
|
|
153
154
|
/*
|
|
154
|
-
*
|
|
155
|
-
* closing (get it, share it).
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
155
|
+
* One stage, four beats, top to bottom: pitch (what it is, the way in, the three pillars),
|
|
156
|
+
* gallery (proof), closing band (get it, share it). The stage is tinted from the active theme's
|
|
157
|
+
* accent so it reads as a deliberate HagiCode moment against the article's neutral page, and the
|
|
158
|
+
* closing band bleeds to the stage edges so "get it" is the last, heaviest surface. Pillars are
|
|
159
|
+
* separated by accent rules rather than boxes. The topology follows the section's own width, so
|
|
160
|
+
* the wide-content toggle and the sidebar layouts reflow it, not just the viewport. Mobile-first:
|
|
161
|
+
* the base rules are the single-column layout, which is also what a browser without container
|
|
162
|
+
* queries renders. Every color derives from Starlight's tokens, so all themes and both modes work.
|
|
159
163
|
*/
|
|
160
164
|
.hagilight-article-promotion {
|
|
165
|
+
--hagilight-promo-pad: 1.5rem;
|
|
166
|
+
--hagilight-promo-edge: color-mix(in srgb, var(--sl-color-text-accent) 38%, var(--sl-color-gray-5));
|
|
167
|
+
--hagilight-promo-tint: color-mix(in srgb, var(--sl-color-accent-low) 55%, var(--sl-color-bg-nav));
|
|
168
|
+
position: relative;
|
|
161
169
|
container-type: inline-size;
|
|
162
170
|
display: grid;
|
|
163
171
|
grid-template-columns: minmax(0, 1fr);
|
|
164
|
-
gap:
|
|
172
|
+
gap: 2.25rem;
|
|
165
173
|
margin: 3rem auto 2rem;
|
|
166
|
-
padding:
|
|
167
|
-
|
|
174
|
+
padding: calc(var(--hagilight-promo-pad) + 0.25rem) var(--hagilight-promo-pad) var(--hagilight-promo-pad);
|
|
175
|
+
overflow: clip;
|
|
176
|
+
border: 1px solid var(--hagilight-promo-edge);
|
|
168
177
|
border-radius: var(--sl-radius-lg, 0.75rem);
|
|
169
|
-
background:
|
|
178
|
+
background:
|
|
179
|
+
radial-gradient(70% 45% at 0% 0%, color-mix(in srgb, var(--sl-color-text-accent) 16%, transparent), transparent 70%),
|
|
180
|
+
linear-gradient(180deg, var(--hagilight-promo-tint), var(--sl-color-bg-nav) 55%);
|
|
170
181
|
color: var(--sl-color-text);
|
|
171
182
|
}
|
|
172
183
|
|
|
184
|
+
/* The accent rule across the top edge: the stage's signature, drawn in on scroll where supported. */
|
|
185
|
+
.hagilight-article-promotion::before {
|
|
186
|
+
content: '';
|
|
187
|
+
position: absolute;
|
|
188
|
+
inset: 0 0 auto;
|
|
189
|
+
height: 4px;
|
|
190
|
+
background: linear-gradient(90deg, var(--sl-color-text-accent), color-mix(in srgb, var(--sl-color-text-accent) 20%, transparent));
|
|
191
|
+
transform-origin: left center;
|
|
192
|
+
pointer-events: none;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.hagilight-article-promotion ::selection {
|
|
196
|
+
background: color-mix(in srgb, var(--sl-color-text-accent) 38%, transparent);
|
|
197
|
+
color: var(--sl-color-white);
|
|
198
|
+
}
|
|
199
|
+
|
|
173
200
|
.hagilight-article-promotion h2,
|
|
174
201
|
.hagilight-article-promotion h3 {
|
|
175
202
|
margin: 0;
|
|
176
203
|
color: var(--sl-color-white);
|
|
177
|
-
line-height: 1.
|
|
204
|
+
line-height: 1.2;
|
|
205
|
+
text-wrap: balance;
|
|
178
206
|
}
|
|
179
207
|
|
|
180
208
|
.hagilight-article-promotion h2 {
|
|
181
|
-
font-size:
|
|
209
|
+
font-size: clamp(2rem, 1.3rem + 3.2cqi, 3rem);
|
|
210
|
+
font-weight: 800;
|
|
211
|
+
letter-spacing: -0.02em;
|
|
182
212
|
}
|
|
183
213
|
|
|
184
214
|
.hagilight-article-promotion h3 {
|
|
185
|
-
font-size: var(--sl-text-
|
|
215
|
+
font-size: var(--sl-text-xl, 1.25rem);
|
|
216
|
+
font-weight: 700;
|
|
186
217
|
}
|
|
187
218
|
|
|
188
219
|
.hagilight-article-promotion p {
|
|
189
220
|
margin: 0;
|
|
190
221
|
line-height: 1.6;
|
|
222
|
+
text-wrap: pretty;
|
|
191
223
|
}
|
|
192
224
|
|
|
193
225
|
.hagilight-article-promotion ul {
|
|
@@ -201,17 +233,17 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
201
233
|
outline-offset: 3px;
|
|
202
234
|
}
|
|
203
235
|
|
|
204
|
-
/* Pitch: headline,
|
|
236
|
+
/* Pitch: headline, one emphasized promise, the way in, then the pillars. */
|
|
205
237
|
.hagilight-article-promotion__pitch {
|
|
206
238
|
display: grid;
|
|
207
|
-
gap:
|
|
239
|
+
gap: 2rem;
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
.hagilight-article-promotion__intro {
|
|
211
243
|
display: grid;
|
|
212
244
|
align-content: start;
|
|
213
245
|
justify-items: start;
|
|
214
|
-
gap: 0.
|
|
246
|
+
gap: 0.875rem;
|
|
215
247
|
}
|
|
216
248
|
|
|
217
249
|
.hagilight-article-promotion__lead,
|
|
@@ -219,9 +251,15 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
219
251
|
max-inline-size: 68ch;
|
|
220
252
|
}
|
|
221
253
|
|
|
254
|
+
.hagilight-article-promotion__lead {
|
|
255
|
+
color: var(--sl-color-gray-2);
|
|
256
|
+
}
|
|
257
|
+
|
|
222
258
|
.hagilight-article-promotion__tagline {
|
|
223
259
|
color: var(--sl-color-white);
|
|
224
|
-
font-
|
|
260
|
+
font-size: var(--sl-text-lg, 1.125rem);
|
|
261
|
+
font-weight: 650;
|
|
262
|
+
line-height: 1.45;
|
|
225
263
|
}
|
|
226
264
|
|
|
227
265
|
.hagilight-article-promotion__actions {
|
|
@@ -236,32 +274,60 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
236
274
|
display: inline-flex;
|
|
237
275
|
align-items: center;
|
|
238
276
|
justify-content: center;
|
|
277
|
+
gap: 0.5rem;
|
|
239
278
|
min-height: 2.75rem;
|
|
240
|
-
padding: 0.5rem
|
|
279
|
+
padding: 0.5rem 1.25rem;
|
|
241
280
|
border: 1px solid var(--sl-color-text-accent);
|
|
242
281
|
border-radius: var(--sl-radius-sm, 0.375rem);
|
|
243
282
|
font-weight: 700;
|
|
244
283
|
text-decoration: none;
|
|
284
|
+
transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.2s cubic-bezier(0.22, 1, 0.36, 1), background-color 0.2s;
|
|
245
285
|
}
|
|
246
286
|
|
|
287
|
+
/* The one loud control: bigger than its sibling, lifted, with an arrow that leans forward on hover. */
|
|
247
288
|
.hagilight-article-promotion__cta--primary {
|
|
289
|
+
min-height: 3.25rem;
|
|
290
|
+
padding-inline: 1.625rem;
|
|
248
291
|
background: var(--sl-color-text-accent);
|
|
249
292
|
color: var(--sl-color-bg);
|
|
293
|
+
font-size: var(--sl-text-lg, 1.125rem);
|
|
294
|
+
box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--sl-color-text-accent) 70%, transparent);
|
|
250
295
|
}
|
|
251
296
|
|
|
252
297
|
.hagilight-article-promotion__cta--secondary {
|
|
253
298
|
background: transparent;
|
|
299
|
+
color: var(--sl-color-white);
|
|
300
|
+
border-color: var(--sl-color-gray-3);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.hagilight-article-promotion__arrow {
|
|
304
|
+
flex: none;
|
|
305
|
+
transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.hagilight-article-promotion__cta--primary:hover {
|
|
309
|
+
transform: translateY(-2px);
|
|
310
|
+
box-shadow: 0 12px 24px -8px color-mix(in srgb, var(--sl-color-text-accent) 80%, transparent);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.hagilight-article-promotion__cta--primary:active {
|
|
314
|
+
transform: translateY(0);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.hagilight-article-promotion__cta--secondary:hover {
|
|
318
|
+
border-color: var(--sl-color-text-accent);
|
|
254
319
|
color: var(--sl-color-text-accent);
|
|
255
320
|
}
|
|
256
321
|
|
|
257
|
-
.hagilight-article-promotion__cta:hover
|
|
258
|
-
|
|
322
|
+
.hagilight-article-promotion__cta:hover .hagilight-article-promotion__arrow,
|
|
323
|
+
.hagilight-article-promotion__downloads:hover .hagilight-article-promotion__arrow {
|
|
324
|
+
transform: translateX(3px);
|
|
259
325
|
}
|
|
260
326
|
|
|
261
|
-
/* Pillars:
|
|
327
|
+
/* Pillars: each opens with an accent rule; the pillar word is the loudest text after the title. */
|
|
262
328
|
.hagilight-article-promotion__features {
|
|
263
329
|
display: grid;
|
|
264
|
-
gap:
|
|
330
|
+
gap: 1.25rem 1.5rem;
|
|
265
331
|
}
|
|
266
332
|
|
|
267
333
|
.hagilight-article-promotion__features li {
|
|
@@ -269,16 +335,17 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
269
335
|
grid-template-columns: auto minmax(0, 1fr);
|
|
270
336
|
align-content: start;
|
|
271
337
|
align-items: center;
|
|
272
|
-
gap: 0.
|
|
338
|
+
gap: 0.625rem 0.75rem;
|
|
273
339
|
min-width: 0;
|
|
274
|
-
padding-block-start:
|
|
275
|
-
border-block-start:
|
|
340
|
+
padding-block-start: 1rem;
|
|
341
|
+
border-block-start: 2px solid color-mix(in srgb, var(--sl-color-text-accent) 55%, transparent);
|
|
276
342
|
}
|
|
277
343
|
|
|
278
344
|
.hagilight-article-promotion__features strong {
|
|
279
345
|
color: var(--sl-color-text-accent);
|
|
280
|
-
font-size: var(--sl-text-
|
|
281
|
-
|
|
346
|
+
font-size: var(--sl-text-xl, 1.25rem);
|
|
347
|
+
font-weight: 750;
|
|
348
|
+
line-height: 1.2;
|
|
282
349
|
}
|
|
283
350
|
|
|
284
351
|
.hagilight-article-promotion__feature-text {
|
|
@@ -288,6 +355,17 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
288
355
|
}
|
|
289
356
|
|
|
290
357
|
.hagilight-article-promotion__icon {
|
|
358
|
+
position: relative;
|
|
359
|
+
display: grid;
|
|
360
|
+
place-items: center;
|
|
361
|
+
width: 2.5rem;
|
|
362
|
+
height: 2.5rem;
|
|
363
|
+
border-radius: var(--sl-radius-md, 0.5rem);
|
|
364
|
+
background: color-mix(in srgb, var(--sl-color-text-accent) 18%, transparent);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.hagilight-article-promotion__icon::after {
|
|
368
|
+
content: '';
|
|
291
369
|
width: 1.5rem;
|
|
292
370
|
height: 1.5rem;
|
|
293
371
|
background: var(--sl-color-text-accent);
|
|
@@ -298,12 +376,12 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
298
376
|
/* Gallery: three audited screenshots, kept in the catalog's narrative order. */
|
|
299
377
|
.hagilight-article-promotion__gallery-block {
|
|
300
378
|
display: grid;
|
|
301
|
-
gap:
|
|
379
|
+
gap: 1.125rem;
|
|
302
380
|
}
|
|
303
381
|
|
|
304
382
|
.hagilight-article-promotion__gallery {
|
|
305
383
|
display: grid;
|
|
306
|
-
gap: 1.25rem
|
|
384
|
+
gap: 1.5rem 1.25rem;
|
|
307
385
|
}
|
|
308
386
|
|
|
309
387
|
.hagilight-article-promotion__gallery li {
|
|
@@ -312,7 +390,7 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
312
390
|
|
|
313
391
|
.hagilight-article-promotion figure {
|
|
314
392
|
display: grid;
|
|
315
|
-
gap: 0.
|
|
393
|
+
gap: 0.625rem;
|
|
316
394
|
margin: 0;
|
|
317
395
|
}
|
|
318
396
|
|
|
@@ -324,22 +402,31 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
324
402
|
display: block;
|
|
325
403
|
width: 100%;
|
|
326
404
|
height: auto;
|
|
327
|
-
border: 1px solid var(--
|
|
405
|
+
border: 1px solid var(--hagilight-promo-edge);
|
|
328
406
|
border-radius: var(--sl-radius-md, 0.5rem);
|
|
407
|
+
box-shadow: 0 18px 32px -18px color-mix(in srgb, var(--sl-color-black) 80%, transparent);
|
|
408
|
+
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.35s;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.hagilight-article-promotion figure:hover img {
|
|
412
|
+
transform: translateY(-3px);
|
|
413
|
+
border-color: var(--sl-color-text-accent);
|
|
329
414
|
}
|
|
330
415
|
|
|
331
416
|
.hagilight-article-promotion figcaption {
|
|
332
|
-
color: var(--sl-color-gray-
|
|
417
|
+
color: var(--sl-color-gray-1);
|
|
333
418
|
font-size: var(--sl-text-sm, 0.875rem);
|
|
334
419
|
line-height: 1.5;
|
|
335
420
|
}
|
|
336
421
|
|
|
337
|
-
/* Closing: the
|
|
422
|
+
/* Closing: a full-bleed band, the heaviest surface, so "get it" is where the eye lands last. */
|
|
338
423
|
.hagilight-article-promotion__closing {
|
|
339
424
|
display: grid;
|
|
340
|
-
gap: 1.
|
|
341
|
-
|
|
342
|
-
|
|
425
|
+
gap: 1.75rem;
|
|
426
|
+
margin: 0 calc(var(--hagilight-promo-pad) * -1) calc(var(--hagilight-promo-pad) * -1);
|
|
427
|
+
padding: var(--hagilight-promo-pad);
|
|
428
|
+
border-block-start: 1px solid var(--hagilight-promo-edge);
|
|
429
|
+
background: color-mix(in srgb, var(--sl-color-accent-low) 80%, var(--sl-color-bg));
|
|
343
430
|
}
|
|
344
431
|
|
|
345
432
|
.hagilight-article-promotion__windows,
|
|
@@ -347,7 +434,7 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
347
434
|
display: grid;
|
|
348
435
|
align-content: start;
|
|
349
436
|
justify-items: start;
|
|
350
|
-
gap: 0.
|
|
437
|
+
gap: 0.625rem;
|
|
351
438
|
min-width: 0;
|
|
352
439
|
}
|
|
353
440
|
|
|
@@ -356,7 +443,7 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
356
443
|
display: flex;
|
|
357
444
|
flex-wrap: wrap;
|
|
358
445
|
align-items: center;
|
|
359
|
-
gap: 0 1.
|
|
446
|
+
gap: 0 1.5rem;
|
|
360
447
|
max-width: 100%;
|
|
361
448
|
}
|
|
362
449
|
|
|
@@ -378,15 +465,18 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
378
465
|
.hagilight-article-promotion__downloads {
|
|
379
466
|
display: inline-flex;
|
|
380
467
|
align-items: center;
|
|
468
|
+
gap: 0.375rem;
|
|
381
469
|
min-height: 2.75rem;
|
|
382
470
|
color: var(--sl-color-text-accent);
|
|
383
|
-
font-weight:
|
|
471
|
+
font-weight: 650;
|
|
472
|
+
text-decoration-thickness: 1px;
|
|
473
|
+
text-underline-offset: 0.25em;
|
|
384
474
|
}
|
|
385
475
|
|
|
386
476
|
.hagilight-article-promotion__share-text {
|
|
387
477
|
justify-self: stretch;
|
|
388
478
|
padding: 0.75rem 1rem;
|
|
389
|
-
border: 1px dashed var(--sl-color-
|
|
479
|
+
border: 1px dashed var(--sl-color-text-accent);
|
|
390
480
|
border-radius: var(--sl-radius-md, 0.5rem);
|
|
391
481
|
background: var(--sl-color-bg);
|
|
392
482
|
overflow-wrap: anywhere;
|
|
@@ -452,4 +542,53 @@ const gallery = copy.gallery.flatMap((item) => {
|
|
|
452
542
|
justify-self: stretch;
|
|
453
543
|
}
|
|
454
544
|
}
|
|
545
|
+
|
|
546
|
+
/*
|
|
547
|
+
* The one authored moment: as the stage scrolls into view its accent rule draws across and the
|
|
548
|
+
* screenshots rise into place one after another. Scroll-driven, so there is no JS and nothing
|
|
549
|
+
* replays; engines without view timelines, and readers who ask for less motion, get the final
|
|
550
|
+
* state. The animations start at 0.35 opacity, so a tall item that never reaches its full entry
|
|
551
|
+
* range is still readable.
|
|
552
|
+
*/
|
|
553
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
554
|
+
@supports (animation-timeline: view()) {
|
|
555
|
+
.hagilight-article-promotion::before {
|
|
556
|
+
animation-name: hagilight-promotion-rule;
|
|
557
|
+
animation-timing-function: linear;
|
|
558
|
+
animation-fill-mode: both;
|
|
559
|
+
animation-timeline: view();
|
|
560
|
+
animation-range: entry 0% entry 80%;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.hagilight-article-promotion__gallery li {
|
|
564
|
+
animation-name: hagilight-promotion-rise;
|
|
565
|
+
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
|
|
566
|
+
animation-fill-mode: both;
|
|
567
|
+
animation-timeline: view();
|
|
568
|
+
animation-range: entry 5% entry 45%;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.hagilight-article-promotion__gallery li:nth-child(2) {
|
|
572
|
+
animation-range: entry 12% entry 52%;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.hagilight-article-promotion__gallery li:nth-child(3) {
|
|
576
|
+
animation-range: entry 19% entry 59%;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@keyframes hagilight-promotion-rule {
|
|
582
|
+
from {
|
|
583
|
+
transform: scaleX(0.04);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
@keyframes hagilight-promotion-rise {
|
|
588
|
+
from {
|
|
589
|
+
opacity: 0.35;
|
|
590
|
+
translate: 0 1.75rem;
|
|
591
|
+
scale: 0.97;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
455
594
|
</style>
|
package/README.md
CHANGED
|
@@ -29,6 +29,43 @@ export default defineConfig({
|
|
|
29
29
|
|
|
30
30
|
The plugin also exposes `@hagicode/hagilight-starlight/schema` for optional SEO frontmatter and individual Astro components such as `/Header` and `/MarkdownContent` for custom Starlight overrides. Configure features through the typed `HagilightStarlightOptions` passed to `hagilight()`.
|
|
31
31
|
|
|
32
|
+
### Analytics
|
|
33
|
+
|
|
34
|
+
`hagilight()` enables PostHog without analytics configuration, an enabled flag, or a consumer project key. It uses `phc_qiVVihsL925ovhPYNdLJWz4k7aoKVMwYCevgZPE6PER5`, API host `https://us.i.posthog.com`, and SDK defaults `2026-05-30`. Google Analytics and PostHog render together in the generated global Head; 51LA stays in the Footer. PostHog covers all production pages, including localized and 404 pages, independently of Google's existing 404 exclusion. Development pages emit no PostHog loader.
|
|
35
|
+
|
|
36
|
+
PostHog options follow the Google Analytics provider convention:
|
|
37
|
+
|
|
38
|
+
| PostHog input | Behavior |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| Plugin options omitted, `analytics: {}`, or `posthog` omitted | Enabled with the supplied default key |
|
|
41
|
+
| `posthog: {}` | Disabled, like an empty Google Analytics provider |
|
|
42
|
+
| `posthog: { apiKey: 'phc_Custom123' }` | Enabled with the custom key |
|
|
43
|
+
| `posthog: { enabled: true, apiKey: 'phc_Custom123' }` | Enabled with the custom key |
|
|
44
|
+
| `posthog: { enabled: true }` | Configuration error: `apiKey` required |
|
|
45
|
+
| `posthog: { enabled: false }` | Disabled independently of Google Analytics and 51LA |
|
|
46
|
+
|
|
47
|
+
Keys must match `^phc_[A-Za-z0-9]+$`; enabled providers reject missing or malformed keys. Provider options must be objects and `enabled` must be boolean.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
hagilight(); // shared analytics defaults
|
|
51
|
+
hagilight({ analytics: { posthog: { apiKey: 'phc_Custom123' } } });
|
|
52
|
+
hagilight({ analytics: { posthog: { enabled: false } } });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A site-owned `components.Head` conflicts with enabled SEO, Google Analytics, or PostHog. To retain it, disable all three head features and explicitly compose the desired metadata and `@hagicode/hagilight-core/GoogleAnalytics` / `@hagicode/hagilight-core/PostHog` components in that Head:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
hagilight({
|
|
59
|
+
seo: { enabled: false },
|
|
60
|
+
analytics: {
|
|
61
|
+
googleAnalytics: { enabled: false },
|
|
62
|
+
posthog: { enabled: false },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The shared component initializes once per document; SDK defaults own PostHog navigation behavior. Choose one initialization owner and disable this provider when the site already initializes PostHog. Google Analytics and 51LA keep their configured behavior when PostHog is disabled.
|
|
68
|
+
|
|
32
69
|
### Theme picker
|
|
33
70
|
|
|
34
71
|
By default the plugin replaces Starlight's light/dark toggle with a theme picker offering nine choices: the default theme plus the Ocean, Sakura, and Forest themes in light and dark, and a "Default" choice that follows the visitor's system color scheme and renders the Forest theme. First-time visitors, and visitors with an invalid saved choice, therefore see Forest; the other themes appear only after an explicit selection, which is remembered in `localStorage`.
|
|
@@ -45,6 +82,15 @@ hagilight({ themes: { enabled: false } })
|
|
|
45
82
|
|
|
46
83
|
The plugin footer shows the same build line as the Astro package: `Branch:`, `Commit:`, and `Built:` after the attribution. It is always on and has no option of its own. The values come from `HAGILIGHT_BUILD_BRANCH` and `HAGILIGHT_BUILD_COMMIT`, then Git, then `unknown`; the core README gives the order. Detached-HEAD CI checkouts report `Branch: unknown` unless the build step sets `HAGILIGHT_BUILD_BRANCH`, as shown in the Astro README. Footer components mounted directly in a page (`@hagicode/hagilight-starlight/Footer` and `PromotoFooter`) render no build text.
|
|
47
84
|
|
|
85
|
+
### Site metadata
|
|
86
|
+
|
|
87
|
+
The plugin writes `/.well-known/hagilight.metadata.json` once per build, after the sitemap, so `discovery` lists the sitemap and any RSS feed that exist. The file's schema and privacy rules are in `@hagicode/hagilight-core`'s README.
|
|
88
|
+
|
|
89
|
+
- `metadata: false` opts out. `metadata: { name, description }` sets the values; the name defaults to the Starlight `title` for the default locale, then the site hostname.
|
|
90
|
+
- Analytics flags report the resolved options. A provider you disable with `analytics` reads `false`. The IDs and keys are never written.
|
|
91
|
+
- When the plain-Astro `hagilight()` integration is also registered, this plugin writes the file, including `llms.txt` in `discovery`, whatever the order of registration. Set `metadata: false` here to let the plain-Astro integration write it instead.
|
|
92
|
+
- GitHub Pages needs a `.nojekyll` file in the published root, or the `.well-known` directory is dropped. The `/docs/` base path puts the file at `<site URL>/docs/.well-known/hagilight.metadata.json`.
|
|
93
|
+
|
|
48
94
|
### Body images
|
|
49
95
|
|
|
50
96
|
`hagilight()` registers the content-image plugin from `@hagicode/hagilight-core/content-images` for the whole site. Every Markdown or MDX body image outside a `<picture>` element gets `loading="lazy"`, `decoding="async"`, and the class `hagilight-content-image`. Values the author set are kept, and logos and showcase images are not changed.
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export interface HagilightFiftyOneLaOptions {
|
|
|
20
20
|
export interface HagilightAnalyticsOptions {
|
|
21
21
|
googleAnalytics?: HagilightGoogleAnalyticsOptions;
|
|
22
22
|
fiftyOneLa?: HagilightFiftyOneLaOptions;
|
|
23
|
+
posthog?: HagilightPostHogOptions;
|
|
24
|
+
}
|
|
25
|
+
export interface HagilightPostHogOptions {
|
|
26
|
+
/** Defaults to true when the provider options are omitted, otherwise to whether apiKey is set. */
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
apiKey?: string;
|
|
23
29
|
}
|
|
24
30
|
export interface HagilightSeoOptions {
|
|
25
31
|
/** Replace Starlight's Head with Hagilight SEO metadata. Defaults to `true`; requires Astro `site`. */
|
|
@@ -49,7 +55,22 @@ export interface HagilightContentComponentsOptions {
|
|
|
49
55
|
markdownContent?: boolean;
|
|
50
56
|
}
|
|
51
57
|
export type HagilightAIDisclosureOptions = Partial<AIDisclosureDefaults>;
|
|
58
|
+
export interface HagilightMetadataOptions {
|
|
59
|
+
/** Defaults to `true`. */
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
/** Site name. Defaults to the Starlight `title` for the default locale, then the site hostname. */
|
|
62
|
+
name?: string;
|
|
63
|
+
/** Site description. Defaults to the Starlight `description` when the site sets one. */
|
|
64
|
+
description?: string;
|
|
65
|
+
}
|
|
52
66
|
export interface HagilightStarlightOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Generate `/.well-known/hagilight.metadata.json` after the build. Defaults to `true`; `false`
|
|
69
|
+
* opts out, and an options object may set `name` and `description`. A site-provided file is
|
|
70
|
+
* never overwritten. When a plain-Astro `hagilight()` is also registered, this plugin writes
|
|
71
|
+
* the file and the plain-Astro integration defers to it.
|
|
72
|
+
*/
|
|
73
|
+
metadata?: boolean | HagilightMetadataOptions;
|
|
53
74
|
header?: HagilightToggleOptions;
|
|
54
75
|
notFoundPage?: HagilightToggleOptions;
|
|
55
76
|
rss?: HagilightRssOptions;
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,15 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { resolveFaviconHeadEntry } from '@hagicode/hagilight-core/favicon';
|
|
4
4
|
import { resolveBuildInfo } from '@hagicode/hagilight-core/build-info-resolver';
|
|
5
5
|
import { checkContentImageRegistration, registerContentImages } from '@hagicode/hagilight-core/content-images';
|
|
6
|
+
import { DEFAULT_POSTHOG_API_KEY, POSTHOG_API_KEY_PATTERN } from '@hagicode/hagilight-core/posthog';
|
|
6
7
|
import { resolveRssLocales } from '@hagicode/hagilight-core/rss';
|
|
7
8
|
import { RSS_OWNER, registerStarlightRssOwner, } from '@hagicode/hagilight-core/rss-ownership';
|
|
8
9
|
import { isValidSeoImageReference, resolveSeoImageUrl } from '@hagicode/hagilight-core/seo';
|
|
10
|
+
import { resolveSiteMetadataOption } from '@hagicode/hagilight-core/site-metadata';
|
|
11
|
+
import { registerStarlightSiteMetadataOwner } from '@hagicode/hagilight-core/site-metadata-ownership';
|
|
9
12
|
import { resolveRssOptions } from './rss-utils.js';
|
|
10
13
|
import { resolveSeoLocales } from './seo-utils.js';
|
|
14
|
+
import { createStarlightMetadataFinalizer } from './site-metadata.js';
|
|
11
15
|
import { THEME_BOOTSTRAP } from './themes.js';
|
|
12
16
|
const packageRoot = new URL('../', import.meta.url);
|
|
13
17
|
const packageFile = (name) => fileURLToPath(new URL(name, packageRoot));
|
|
@@ -201,7 +205,7 @@ function resolveDefaultSeoLocale(defaultLocale, locales) {
|
|
|
201
205
|
}
|
|
202
206
|
return found.route;
|
|
203
207
|
}
|
|
204
|
-
function createConfiguredIntegration(instanceId, serializedOptions, rssConfig, rssLocaleFeedUrls, componentIds, getConfiguredRssFeed, generateRss, unregisterRssOwner, contentImagesEnabled) {
|
|
208
|
+
function createConfiguredIntegration(instanceId, serializedOptions, rssConfig, rssLocaleFeedUrls, componentIds, getConfiguredRssFeed, generateRss, unregisterRssOwner, contentImagesEnabled, siteMetadata, unregisterSiteMetadataOwner) {
|
|
205
209
|
const optionsId = `virtual:hagilight-starlight/${instanceId}/options`;
|
|
206
210
|
const rssConfigId = 'virtual:hagilight-starlight/rss-config';
|
|
207
211
|
const headerPath = componentIds.header ? packageFile('Header.astro') : undefined;
|
|
@@ -267,11 +271,13 @@ import options from '${optionsId}';
|
|
|
267
271
|
import DefaultHead from '@astrojs/starlight/components/Head.astro';
|
|
268
272
|
import SEOHead from ${JSON.stringify(seoHeadPath)};
|
|
269
273
|
import GoogleAnalytics from ${JSON.stringify(coreExport('GoogleAnalytics'))};
|
|
274
|
+
${serializedOptions.posthogApiKey ? `import PostHog from ${JSON.stringify(coreExport('PostHog'))};` : ''}
|
|
270
275
|
import options from '${optionsId}';
|
|
271
276
|
const isNotFound = /(?:^|\\/)404(?:\\.html)?\\/?$/u.test(Astro.url.pathname);
|
|
272
277
|
---
|
|
273
278
|
|
|
274
279
|
{options.seoEnabled ? <SEOHead options={options} /> : <DefaultHead />}
|
|
280
|
+
${serializedOptions.posthogApiKey ? '<PostHog apiKey={options.posthogApiKey} />' : ''}
|
|
275
281
|
{!isNotFound && options.googleAnalyticsMeasurementId && (
|
|
276
282
|
<GoogleAnalytics measurementId={options.googleAnalyticsMeasurementId} />
|
|
277
283
|
)}
|
|
@@ -325,10 +331,18 @@ const isNotFound = /(?:^|\\/)404(?:\\.html)?\\/?$/u.test(Astro.url.pathname);
|
|
|
325
331
|
prerender: true,
|
|
326
332
|
});
|
|
327
333
|
}
|
|
328
|
-
|
|
334
|
+
// Starlight splices its own sitemap into the integration list before this setup runs, so
|
|
335
|
+
// the finalizer appended in the same update runs after the sitemap has written its index.
|
|
336
|
+
updateConfig({
|
|
337
|
+
vite: { plugins: [vitePlugin] },
|
|
338
|
+
...(siteMetadata.enabled
|
|
339
|
+
? { integrations: [createStarlightMetadataFinalizer(siteMetadata, () => buildInfo)] }
|
|
340
|
+
: {}),
|
|
341
|
+
});
|
|
329
342
|
},
|
|
330
343
|
'astro:config:done'({ config, logger }) {
|
|
331
344
|
unregisterRssOwner();
|
|
345
|
+
unregisterSiteMetadataOwner();
|
|
332
346
|
checkContentImageRegistration({ config, logger, enabled: contentImagesEnabled });
|
|
333
347
|
if (config.image?.service?.entrypoint === NOOP_IMAGE_SERVICE) {
|
|
334
348
|
logger.warn(NOOP_IMAGE_SERVICE_WARNING);
|
|
@@ -371,6 +385,14 @@ export default function hagilight(options = {}) {
|
|
|
371
385
|
pattern: FIFTY_ONE_LA_ID_PATTERN,
|
|
372
386
|
defaultId: DEFAULT_51LA_ID,
|
|
373
387
|
});
|
|
388
|
+
const posthog = optionalRecord(analytics.posthog, 'PostHog analytics');
|
|
389
|
+
optionalBoolean(posthog, 'enabled', 'PostHog analytics enabled');
|
|
390
|
+
const posthogApiKey = resolveProvider(analytics.posthog, {
|
|
391
|
+
name: 'PostHog',
|
|
392
|
+
idKey: 'apiKey',
|
|
393
|
+
pattern: POSTHOG_API_KEY_PATTERN,
|
|
394
|
+
defaultId: DEFAULT_POSTHOG_API_KEY,
|
|
395
|
+
});
|
|
374
396
|
const promoto = optionalRecord(input.promoto, 'promoto');
|
|
375
397
|
const promotoEnabled = optionalBoolean(promoto, 'enabled', 'promoto enabled') !== false;
|
|
376
398
|
const contentImages = optionalRecord(input.contentImages, 'contentImages');
|
|
@@ -388,7 +410,7 @@ export default function hagilight(options = {}) {
|
|
|
388
410
|
markdownContent: markdownContentEnabled
|
|
389
411
|
? `virtual:hagilight-starlight/${instanceId}/MarkdownContent.astro`
|
|
390
412
|
: undefined,
|
|
391
|
-
head: seo.enabled || googleAnalyticsMeasurementId
|
|
413
|
+
head: seo.enabled || googleAnalyticsMeasurementId || posthogApiKey
|
|
392
414
|
? `virtual:hagilight-starlight/${instanceId}/Head.astro`
|
|
393
415
|
: undefined,
|
|
394
416
|
themeSelect: themesEnabled ? packageFile('ThemeSelect.astro') : undefined,
|
|
@@ -399,11 +421,14 @@ export default function hagilight(options = {}) {
|
|
|
399
421
|
links: options.links ?? {},
|
|
400
422
|
googleAnalyticsMeasurementId,
|
|
401
423
|
fiftyOneLaId,
|
|
424
|
+
posthogApiKey,
|
|
402
425
|
aiDisclosures,
|
|
403
426
|
seoEnabled: seo.enabled,
|
|
404
427
|
seo,
|
|
405
428
|
};
|
|
406
429
|
const unregisterRssOwner = registerStarlightRssOwner(rssEnabled);
|
|
430
|
+
const metadata = resolveSiteMetadataOption(input.metadata);
|
|
431
|
+
const unregisterSiteMetadataOwner = registerStarlightSiteMetadataOwner(metadata.enabled);
|
|
407
432
|
return {
|
|
408
433
|
name: '@hagicode/hagilight-starlight',
|
|
409
434
|
hooks: {
|
|
@@ -428,9 +453,9 @@ export default function hagilight(options = {}) {
|
|
|
428
453
|
}
|
|
429
454
|
if (componentIds.head && config.components?.Head) {
|
|
430
455
|
if (seo.enabled) {
|
|
431
|
-
throw new Error('Hagilight SEO cannot replace an existing Starlight Head override. Set seo: { enabled: false } and
|
|
456
|
+
throw new Error('Hagilight SEO cannot replace an existing Starlight Head override. Set seo: { enabled: false }, analytics.googleAnalytics: { enabled: false }, and analytics.posthog: { enabled: false } to keep your Head, or compose SEO, @hagicode/hagilight-core/GoogleAnalytics and @hagicode/hagilight-core/PostHog explicitly.');
|
|
432
457
|
}
|
|
433
|
-
throw new Error('Hagilight Google Analytics cannot replace an existing Starlight Head override.
|
|
458
|
+
throw new Error('Hagilight Google Analytics or PostHog cannot replace an existing Starlight Head override. Set analytics.googleAnalytics: { enabled: false } and analytics.posthog: { enabled: false } to keep it, or compose @hagicode/hagilight-core/GoogleAnalytics and @hagicode/hagilight-core/PostHog in your Head.');
|
|
434
459
|
}
|
|
435
460
|
let seoSite;
|
|
436
461
|
let seoLocales = [];
|
|
@@ -483,7 +508,24 @@ export default function hagilight(options = {}) {
|
|
|
483
508
|
new URL(`rss.${filename}.xml`, baseUrl).toString(),
|
|
484
509
|
]))
|
|
485
510
|
: {};
|
|
486
|
-
addIntegration(createConfiguredIntegration(instanceId, serializedOptions, { options: rssOptions, locales: rssLocales }, rssLocaleFeedUrls, componentIds, () => getRssFeedUrl(config) ?? rssFeedUrl, generateRss, unregisterRssOwner, contentImagesEnabled
|
|
511
|
+
addIntegration(createConfiguredIntegration(instanceId, serializedOptions, { options: rssOptions, locales: rssLocales }, rssLocaleFeedUrls, componentIds, () => getRssFeedUrl(config) ?? rssFeedUrl, generateRss, unregisterRssOwner, contentImagesEnabled, {
|
|
512
|
+
enabled: metadata.enabled,
|
|
513
|
+
name: metadata.name,
|
|
514
|
+
description: metadata.description,
|
|
515
|
+
title: config.title,
|
|
516
|
+
siteDescription: config.description,
|
|
517
|
+
locales: config.locales,
|
|
518
|
+
defaultLocale: config.defaultLocale,
|
|
519
|
+
site: astroConfig.site,
|
|
520
|
+
base: astroConfig.base,
|
|
521
|
+
publicDir: astroConfig.publicDir,
|
|
522
|
+
analytics: {
|
|
523
|
+
googleAnalytics: serializedOptions.googleAnalyticsMeasurementId !== undefined,
|
|
524
|
+
fiftyOneLa: serializedOptions.fiftyOneLaId !== undefined,
|
|
525
|
+
posthog: serializedOptions.posthogApiKey !== undefined,
|
|
526
|
+
},
|
|
527
|
+
features: { seo: seo.enabled, promoto: promotoEnabled },
|
|
528
|
+
}, unregisterSiteMetadataOwner));
|
|
487
529
|
updateConfig({
|
|
488
530
|
...(config.logo === undefined ? { logo: { src: defaultLogo, alt: 'HagiCode' } } : {}),
|
|
489
531
|
customCss: [
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
import type { BuildInfo } from '@hagicode/hagilight-core/build-info';
|
|
3
|
+
import { type SiteMetadataAnalytics, type SiteMetadataFeatures } from '@hagicode/hagilight-core/site-metadata';
|
|
4
|
+
import { type SiteMetadataLogger } from '@hagicode/hagilight-core/site-metadata-writer';
|
|
5
|
+
export interface StarlightSiteMetadataSetup {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
/** Explicit `metadata.name`, when set. */
|
|
8
|
+
name?: string | undefined;
|
|
9
|
+
/** Explicit `metadata.description`, when set. */
|
|
10
|
+
description?: string | undefined;
|
|
11
|
+
/** Starlight `title`: a string or a localized record. */
|
|
12
|
+
title: unknown;
|
|
13
|
+
/** Starlight `description`, when the site configures one. */
|
|
14
|
+
siteDescription?: unknown;
|
|
15
|
+
/** Starlight `locales` map, for example `{ root: { lang: 'en' } }`. */
|
|
16
|
+
locales: unknown;
|
|
17
|
+
/** Starlight `defaultLocale`. */
|
|
18
|
+
defaultLocale: unknown;
|
|
19
|
+
site: unknown;
|
|
20
|
+
base: string | undefined;
|
|
21
|
+
publicDir: URL;
|
|
22
|
+
analytics: SiteMetadataAnalytics;
|
|
23
|
+
features: SiteMetadataFeatures;
|
|
24
|
+
}
|
|
25
|
+
export interface WriteStarlightSiteMetadataInput {
|
|
26
|
+
outDir: string;
|
|
27
|
+
setup: StarlightSiteMetadataSetup;
|
|
28
|
+
build: BuildInfo;
|
|
29
|
+
logger: SiteMetadataLogger;
|
|
30
|
+
}
|
|
31
|
+
/** Build and write the metadata file for a Starlight build. Skips with a warning when no absolute site URL exists. */
|
|
32
|
+
export declare function writeStarlightSiteMetadata({ outDir, setup, build, logger }: WriteStarlightSiteMetadataInput): void;
|
|
33
|
+
/**
|
|
34
|
+
* Integration appended after this plugin's sitemap, so the write runs after
|
|
35
|
+
* `@astrojs/sitemap` has written its index. The owner's write also waits for any
|
|
36
|
+
* deferring plain-Astro peer that reported before it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createStarlightMetadataFinalizer(setup: StarlightSiteMetadataSetup, buildInfo: () => BuildInfo | undefined): AstroIntegration;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Facts for `.well-known/hagilight.metadata.json` from a Starlight build. The
|
|
3
|
+
* Starlight plugin owns the file when it is enabled, so this module collects the
|
|
4
|
+
* title, locales and resolved feature flags from its own configuration.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { buildSiteMetadata, SITE_METADATA_PATH, } from '@hagicode/hagilight-core/site-metadata';
|
|
10
|
+
import { findDiscoveryOutputs, writeSiteMetadata } from '@hagicode/hagilight-core/site-metadata-writer';
|
|
11
|
+
import { completeSiteMetadataOwnerBuild } from '@hagicode/hagilight-core/site-metadata-ownership';
|
|
12
|
+
import { resolveSeoLocales } from './seo-utils.js';
|
|
13
|
+
const STARLIGHT_PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function absoluteSite(site) {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = new URL(site);
|
|
20
|
+
return ['http:', 'https:'].includes(parsed.protocol) && !parsed.username && !parsed.password ? parsed.href : undefined;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function trimmedText(value) {
|
|
27
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Starlight locale routes become metadata locales. The root route uses its language tag as the code. */
|
|
30
|
+
function toMetadataLocales(locales) {
|
|
31
|
+
const routes = new Map();
|
|
32
|
+
const entries = locales.map(({ route, lang }) => {
|
|
33
|
+
const code = route === 'root' ? lang : route;
|
|
34
|
+
routes.set(route, code);
|
|
35
|
+
return { code, lang, path: route === 'root' ? '/' : `/${route}/` };
|
|
36
|
+
});
|
|
37
|
+
return { locales: entries, routes };
|
|
38
|
+
}
|
|
39
|
+
/** Starlight's default locale is the route named in `defaultLocale`, or `root`. */
|
|
40
|
+
function defaultRoute(defaultLocale, locales) {
|
|
41
|
+
const configured = isRecord(defaultLocale) ? defaultLocale.locale ?? defaultLocale.lang : defaultLocale;
|
|
42
|
+
if (typeof configured !== 'string' || !configured.trim())
|
|
43
|
+
return locales.find(({ route }) => route === 'root')?.route ?? locales[0]?.route ?? 'root';
|
|
44
|
+
const match = locales.find(({ route, lang }) => route === configured || lang === configured);
|
|
45
|
+
return match?.route ?? configured;
|
|
46
|
+
}
|
|
47
|
+
/** Starlight's title, read for the default locale. A localized record uses `root` or its first entry. */
|
|
48
|
+
function siteTitle(title, route) {
|
|
49
|
+
if (typeof title === 'string')
|
|
50
|
+
return trimmedText(title);
|
|
51
|
+
if (!isRecord(title))
|
|
52
|
+
return undefined;
|
|
53
|
+
return trimmedText(title[route] ?? title.root ?? Object.values(title)[0]);
|
|
54
|
+
}
|
|
55
|
+
/** Build and write the metadata file for a Starlight build. Skips with a warning when no absolute site URL exists. */
|
|
56
|
+
export function writeStarlightSiteMetadata({ outDir, setup, build, logger }) {
|
|
57
|
+
const siteUrl = absoluteSite(setup.site);
|
|
58
|
+
if (!siteUrl) {
|
|
59
|
+
logger.warn(`${SITE_METADATA_PATH} requires an absolute Astro site URL, so it was not generated.`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const seoLocales = resolveSeoLocales(setup.locales);
|
|
63
|
+
const { locales, routes } = toMetadataLocales(seoLocales);
|
|
64
|
+
const defaultCode = routes.get(defaultRoute(setup.defaultLocale, seoLocales));
|
|
65
|
+
const route = defaultRoute(setup.defaultLocale, seoLocales);
|
|
66
|
+
const base = `${setup.base || '/'}`.replace(/\/?$/u, '/');
|
|
67
|
+
const metadata = buildSiteMetadata({
|
|
68
|
+
generator: { name: '@hagicode/hagilight-starlight', version: STARLIGHT_PACKAGE_VERSION },
|
|
69
|
+
site: {
|
|
70
|
+
name: setup.name ?? siteTitle(setup.title, route),
|
|
71
|
+
url: siteUrl,
|
|
72
|
+
base,
|
|
73
|
+
description: setup.description ?? trimmedText(setup.siteDescription),
|
|
74
|
+
},
|
|
75
|
+
locales,
|
|
76
|
+
defaultLocale: defaultCode,
|
|
77
|
+
fallbackLang: seoLocales.find((entry) => entry.route === route)?.lang,
|
|
78
|
+
build,
|
|
79
|
+
discovery: findDiscoveryOutputs(outDir),
|
|
80
|
+
analytics: setup.analytics,
|
|
81
|
+
features: setup.features,
|
|
82
|
+
});
|
|
83
|
+
writeSiteMetadata({
|
|
84
|
+
outDir,
|
|
85
|
+
metadata,
|
|
86
|
+
siteOwnedFiles: [join(fileURLToPath(setup.publicDir), SITE_METADATA_PATH)],
|
|
87
|
+
logger,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Integration appended after this plugin's sitemap, so the write runs after
|
|
92
|
+
* `@astrojs/sitemap` has written its index. The owner's write also waits for any
|
|
93
|
+
* deferring plain-Astro peer that reported before it.
|
|
94
|
+
*/
|
|
95
|
+
export function createStarlightMetadataFinalizer(setup, buildInfo) {
|
|
96
|
+
return {
|
|
97
|
+
name: '@hagicode/hagilight-starlight:metadata',
|
|
98
|
+
hooks: {
|
|
99
|
+
'astro:build:done'({ dir, logger }) {
|
|
100
|
+
completeSiteMetadataOwnerBuild(() => {
|
|
101
|
+
const build = buildInfo();
|
|
102
|
+
if (!build)
|
|
103
|
+
return;
|
|
104
|
+
writeStarlightSiteMetadata({ outDir: fileURLToPath(dir), setup, build, logger });
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hagicode/hagilight-starlight",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1-dev.133.1.338317a",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Starlight plugin and components for HagiCode documentation",
|
|
6
6
|
"repository": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"assets/showcase/*"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@hagicode/hagilight-core": "0.11.
|
|
56
|
+
"@hagicode/hagilight-core": "0.11.1-dev.133.1.338317a"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@astrojs/starlight": "^0.42.4",
|