@hagicode/hagilight-starlight 0.1.2-dev.5.1.c41051d → 0.1.2-dev.7.1.c02207e
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 +111 -0
- package/Header.astro +129 -0
- package/LanguageChooser.astro +357 -0
- package/index.mjs +153 -3
- package/language-routing.mjs +114 -0
- package/package.json +4 -2
package/Footer.astro
CHANGED
|
@@ -1,12 +1,123 @@
|
|
|
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
|
+
<div class="hagilight-site-links">
|
|
17
|
+
{resolvedLinks.relatedSites.length > 0 && (
|
|
18
|
+
<section>
|
|
19
|
+
<h2>{resolvedLinks.labels.relatedSites}</h2>
|
|
20
|
+
<nav aria-label={resolvedLinks.labels.navigation.relatedSites}>
|
|
21
|
+
<ul>
|
|
22
|
+
{resolvedLinks.relatedSites.map((site) => (
|
|
23
|
+
<li>
|
|
24
|
+
<a href={site.href} target={site.target} rel={site.rel}>
|
|
25
|
+
{site.name}
|
|
26
|
+
{site.description && <span class="hagilight-site-description">{site.description}</span>}
|
|
27
|
+
</a>
|
|
28
|
+
</li>
|
|
29
|
+
))}
|
|
30
|
+
</ul>
|
|
31
|
+
</nav>
|
|
32
|
+
</section>
|
|
33
|
+
)}
|
|
34
|
+
<section>
|
|
35
|
+
<h2>{resolvedLinks.labels.quickLinks}</h2>
|
|
36
|
+
<nav aria-label={resolvedLinks.labels.navigation.quickLinks}>
|
|
37
|
+
<ul>
|
|
38
|
+
{resolvedLinks.quick.map((link) => (
|
|
39
|
+
<li><a href={link.href} target={link.target} rel={link.rel}>{link.label}</a></li>
|
|
40
|
+
))}
|
|
41
|
+
</ul>
|
|
42
|
+
</nav>
|
|
43
|
+
</section>
|
|
44
|
+
<section>
|
|
45
|
+
<h2>{resolvedLinks.labels.community}</h2>
|
|
46
|
+
<nav aria-label={resolvedLinks.labels.navigation.community}>
|
|
47
|
+
<ul>
|
|
48
|
+
{resolvedLinks.community.map((link) => (
|
|
49
|
+
<li><a href={link.href} target={link.target} rel={link.rel}>{link.label}</a></li>
|
|
50
|
+
))}
|
|
51
|
+
</ul>
|
|
52
|
+
</nav>
|
|
53
|
+
</section>
|
|
54
|
+
<nav class="hagilight-filings" aria-label={resolvedLinks.labels.navigation.filings}>
|
|
55
|
+
{resolvedLinks.filings.map((link) => (
|
|
56
|
+
<a
|
|
57
|
+
href={link.href}
|
|
58
|
+
target={link.target}
|
|
59
|
+
rel={link.rel}
|
|
60
|
+
aria-label={link.ariaLabel}
|
|
61
|
+
>{link.label}</a>
|
|
62
|
+
))}
|
|
63
|
+
</nav>
|
|
64
|
+
</div>
|
|
7
65
|
<p class="hagilight-copyright"><Copyright name="HagiCode" /></p>
|
|
8
66
|
|
|
9
67
|
<style>
|
|
68
|
+
.hagilight-site-links {
|
|
69
|
+
display: grid;
|
|
70
|
+
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
|
71
|
+
gap: 1.5rem;
|
|
72
|
+
margin: 2rem auto 1rem;
|
|
73
|
+
max-width: 72rem;
|
|
74
|
+
padding-inline: 1rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.hagilight-site-links section {
|
|
78
|
+
min-width: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.hagilight-site-links h2 {
|
|
82
|
+
margin: 0 0 0.75rem;
|
|
83
|
+
color: var(--sl-color-white);
|
|
84
|
+
font-size: var(--sl-text-sm);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.hagilight-site-links ul {
|
|
88
|
+
display: grid;
|
|
89
|
+
gap: 0.5rem;
|
|
90
|
+
margin: 0;
|
|
91
|
+
padding: 0;
|
|
92
|
+
list-style: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.hagilight-site-links a {
|
|
96
|
+
color: var(--sl-color-gray-3);
|
|
97
|
+
font-size: var(--sl-text-sm);
|
|
98
|
+
text-decoration: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.hagilight-site-links a:hover {
|
|
102
|
+
color: var(--sl-color-white);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.hagilight-site-description {
|
|
106
|
+
display: block;
|
|
107
|
+
margin-top: 0.25rem;
|
|
108
|
+
font-size: var(--sl-text-xs);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.hagilight-filings {
|
|
112
|
+
grid-column: 1 / -1;
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-wrap: wrap;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
gap: 0.75rem 1.5rem;
|
|
117
|
+
border-top: 1px solid var(--sl-color-gray-5);
|
|
118
|
+
padding-top: 1rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
10
121
|
.hagilight-copyright {
|
|
11
122
|
margin-block: 1rem;
|
|
12
123
|
color: var(--sl-color-gray-3);
|
package/Header.astro
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:starlight/user-config';
|
|
3
|
+
import Search from 'virtual:starlight/components/Search';
|
|
4
|
+
import SiteTitle from 'virtual:starlight/components/SiteTitle';
|
|
5
|
+
import SocialIcons from 'virtual:starlight/components/SocialIcons';
|
|
6
|
+
import ThemeSelect from 'virtual:starlight/components/ThemeSelect';
|
|
7
|
+
import { resolveSiteLinks, type SiteLinksOptions } from '@hagicode/hagilight/site-links';
|
|
8
|
+
import LanguageChooser from './LanguageChooser.astro';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
links?: SiteLinksOptions;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { links } = Astro.props;
|
|
15
|
+
const locale = Astro.locals.starlightRoute.locale;
|
|
16
|
+
const resolvedLinks = resolveSiteLinks(locale, links);
|
|
17
|
+
const shouldRenderSearch =
|
|
18
|
+
config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro';
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<div class="header">
|
|
22
|
+
<div class="title-wrapper sl-flex">
|
|
23
|
+
<SiteTitle />
|
|
24
|
+
</div>
|
|
25
|
+
<div class="sl-flex print:hidden">
|
|
26
|
+
{shouldRenderSearch && <Search />}
|
|
27
|
+
</div>
|
|
28
|
+
<div class="sl-hidden md:sl-flex print:hidden right-group">
|
|
29
|
+
<nav class="header-links" aria-label="Site navigation">
|
|
30
|
+
{resolvedLinks.header.map((link) => (
|
|
31
|
+
<a href={link.href} target={link.target} rel={link.rel}>{link.label}</a>
|
|
32
|
+
))}
|
|
33
|
+
</nav>
|
|
34
|
+
<div class="sl-flex social-icons">
|
|
35
|
+
<SocialIcons />
|
|
36
|
+
</div>
|
|
37
|
+
<ThemeSelect />
|
|
38
|
+
<LanguageChooser />
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<style>
|
|
43
|
+
@layer starlight.core {
|
|
44
|
+
.header {
|
|
45
|
+
display: flex;
|
|
46
|
+
gap: var(--sl-nav-gap);
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
align-items: center;
|
|
49
|
+
height: 100%;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.title-wrapper {
|
|
53
|
+
overflow: clip;
|
|
54
|
+
padding: 0.25rem;
|
|
55
|
+
margin: -0.25rem;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.right-group,
|
|
60
|
+
.social-icons {
|
|
61
|
+
gap: 1rem;
|
|
62
|
+
align-items: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.right-group {
|
|
66
|
+
flex-wrap: nowrap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.social-icons:has(a)::after {
|
|
70
|
+
content: '';
|
|
71
|
+
height: 2rem;
|
|
72
|
+
border-inline-end: 1px solid var(--sl-color-gray-5);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.header-links {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 0.5rem;
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.header-links a {
|
|
83
|
+
padding: 0.375rem 0.5rem;
|
|
84
|
+
border-radius: var(--sl-radius-sm);
|
|
85
|
+
color: var(--sl-color-text-high);
|
|
86
|
+
font-size: var(--sl-text-sm);
|
|
87
|
+
text-decoration: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.header-links a:hover {
|
|
91
|
+
background: var(--sl-color-gray-6);
|
|
92
|
+
color: var(--sl-color-white);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@media (max-width: 72rem) {
|
|
96
|
+
.header-links {
|
|
97
|
+
display: none;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@media (min-width: 50rem) {
|
|
102
|
+
:global(:root[data-has-sidebar]) {
|
|
103
|
+
--__sidebar-pad: calc(2 * var(--sl-nav-pad-x));
|
|
104
|
+
}
|
|
105
|
+
:global(:root:not([data-has-toc])) {
|
|
106
|
+
--__toc-width: 0rem;
|
|
107
|
+
}
|
|
108
|
+
.header {
|
|
109
|
+
--__sidebar-width: max(0rem, var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x));
|
|
110
|
+
--__main-column-fr: calc(
|
|
111
|
+
(
|
|
112
|
+
100% + var(--__sidebar-pad, 0rem) - var(--__toc-width, var(--sl-sidebar-width)) -
|
|
113
|
+
(2 * var(--__toc-width, var(--sl-nav-pad-x))) - var(--sl-content-inline-start, 0rem) -
|
|
114
|
+
var(--sl-content-width)
|
|
115
|
+
) / 2
|
|
116
|
+
);
|
|
117
|
+
display: grid;
|
|
118
|
+
grid-template-columns:
|
|
119
|
+
minmax(
|
|
120
|
+
calc(var(--__sidebar-width) + max(0rem, var(--__main-column-fr) - var(--sl-nav-gap))),
|
|
121
|
+
auto
|
|
122
|
+
)
|
|
123
|
+
1fr
|
|
124
|
+
auto;
|
|
125
|
+
align-content: center;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
</style>
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:starlight/user-config';
|
|
3
|
+
import context from 'virtual:starlight/project-context';
|
|
4
|
+
import {
|
|
5
|
+
buildLocaleNavigationTarget,
|
|
6
|
+
getConfiguredLanguageOptions,
|
|
7
|
+
} from './language-routing.mjs';
|
|
8
|
+
|
|
9
|
+
const currentLocale = Astro.locals.starlightRoute.locale ?? config.defaultLocale?.locale ?? 'root';
|
|
10
|
+
const options = getConfiguredLanguageOptions(config.locales, currentLocale);
|
|
11
|
+
const currentOption = options.find((option) => option.selected) ?? options[0];
|
|
12
|
+
const accessibleLabel = Astro.locals.t('languageSelect.accessibleLabel');
|
|
13
|
+
const copy = {
|
|
14
|
+
'zh-cn': ['当前语言', '已选', '关闭语言选择'],
|
|
15
|
+
'en-us': ['Current language', 'Selected', 'Close language selector'],
|
|
16
|
+
'zh-hant': ['目前語言', '已選取', '關閉語言選擇'],
|
|
17
|
+
'fr-fr': ['Langue actuelle', 'Sélectionné', 'Fermer le sélecteur de langue'],
|
|
18
|
+
'de-de': ['Aktuelle Sprache', 'Ausgewählt', 'Sprachauswahl schließen'],
|
|
19
|
+
'es-es': ['Idioma actual', 'Seleccionado', 'Cerrar selector de idioma'],
|
|
20
|
+
'ja-jp': ['現在の言語', '選択中', '言語選択を閉じる'],
|
|
21
|
+
'ko-kr': ['현재 언어', '선택됨', '언어 선택 닫기'],
|
|
22
|
+
'pt-br': ['Idioma atual', 'Selecionado', 'Fechar seletor de idioma'],
|
|
23
|
+
'ru-ru': ['Текущий язык', 'Выбрано', 'Закрыть выбор языка'],
|
|
24
|
+
};
|
|
25
|
+
const langKey = (currentOption?.lang ?? currentLocale).toLowerCase();
|
|
26
|
+
const languagePrefix = langKey.split('-')[0];
|
|
27
|
+
const fallbackCopy = copy[langKey]
|
|
28
|
+
?? Object.entries(copy).find(([key]) => key.startsWith(`${languagePrefix}-`))?.[1]
|
|
29
|
+
?? copy['en-us'];
|
|
30
|
+
const currentLabel = fallbackCopy[0];
|
|
31
|
+
const selectedLabel = fallbackCopy[1];
|
|
32
|
+
const closeLabel = fallbackCopy[2];
|
|
33
|
+
const basePath = import.meta.env.BASE_URL;
|
|
34
|
+
const routeLocales = Object.keys(config.locales ?? {});
|
|
35
|
+
const navigationOptions = options.map((option) => ({
|
|
36
|
+
...option,
|
|
37
|
+
href: buildLocaleNavigationTarget(
|
|
38
|
+
Astro.url,
|
|
39
|
+
option.code,
|
|
40
|
+
routeLocales,
|
|
41
|
+
basePath,
|
|
42
|
+
context.trailingSlash,
|
|
43
|
+
).pathname,
|
|
44
|
+
}));
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
config.isMultilingual && navigationOptions.length > 1 && (
|
|
49
|
+
<hagilight-language-chooser class="language-switcher">
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
class="language-trigger"
|
|
53
|
+
aria-haspopup="dialog"
|
|
54
|
+
aria-controls="hagilight-language-dialog"
|
|
55
|
+
aria-expanded="false"
|
|
56
|
+
aria-label={`${accessibleLabel}: ${currentOption?.label ?? currentLocale}`}
|
|
57
|
+
title={currentOption?.label ?? currentLocale}
|
|
58
|
+
>
|
|
59
|
+
<span class="trigger-label">{currentOption?.label ?? currentLocale}</span>
|
|
60
|
+
<span class="trigger-chevron" aria-hidden="true">▾</span>
|
|
61
|
+
</button>
|
|
62
|
+
<dialog
|
|
63
|
+
id="hagilight-language-dialog"
|
|
64
|
+
class="language-dialog"
|
|
65
|
+
aria-modal="true"
|
|
66
|
+
aria-labelledby="language-dialog-title"
|
|
67
|
+
>
|
|
68
|
+
<div class="dialog-header">
|
|
69
|
+
<div>
|
|
70
|
+
<h2 id="language-dialog-title" class="dialog-title">{accessibleLabel}</h2>
|
|
71
|
+
<p class="dialog-current">
|
|
72
|
+
{currentLabel}: <strong>{currentOption?.label ?? currentLocale}</strong>
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
<button type="button" class="dialog-close" aria-label={closeLabel}>×</button>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="language-grid-scroller">
|
|
78
|
+
<div class="language-grid" role="listbox" aria-label={accessibleLabel}>
|
|
79
|
+
{navigationOptions.map((option) => (
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
role="option"
|
|
83
|
+
data-locale={option.code}
|
|
84
|
+
data-href={option.href}
|
|
85
|
+
lang={option.lang}
|
|
86
|
+
aria-selected={option.selected ? 'true' : 'false'}
|
|
87
|
+
class:list={['language-option', { 'language-option-selected': option.selected }]}
|
|
88
|
+
>
|
|
89
|
+
<span class="language-option-label">{option.label}</span>
|
|
90
|
+
{option.selected && <span class="selected-badge">{selectedLabel}</span>}
|
|
91
|
+
</button>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</dialog>
|
|
96
|
+
</hagilight-language-chooser>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
<script>
|
|
101
|
+
import {
|
|
102
|
+
getKeyboardTargetIndex,
|
|
103
|
+
persistStarlightLocaleSelection,
|
|
104
|
+
} from './language-routing.mjs';
|
|
105
|
+
|
|
106
|
+
class HagilightLanguageChooser extends HTMLElement {
|
|
107
|
+
connectedCallback() {
|
|
108
|
+
if (this.dataset.initialized) return;
|
|
109
|
+
this.dataset.initialized = 'true';
|
|
110
|
+
|
|
111
|
+
const trigger = this.querySelector('.language-trigger');
|
|
112
|
+
const dialog = this.querySelector('.language-dialog');
|
|
113
|
+
const closeButton = this.querySelector('.dialog-close');
|
|
114
|
+
const options = [...this.querySelectorAll('[role="option"]')];
|
|
115
|
+
if (!(trigger instanceof HTMLButtonElement)
|
|
116
|
+
|| !(dialog instanceof HTMLDialogElement)
|
|
117
|
+
|| !(closeButton instanceof HTMLButtonElement)) return;
|
|
118
|
+
|
|
119
|
+
const closeDialog = () => {
|
|
120
|
+
if (dialog.open) dialog.close();
|
|
121
|
+
else trigger.focus();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
trigger.addEventListener('click', () => {
|
|
125
|
+
if (dialog.open) {
|
|
126
|
+
closeDialog();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
dialog.showModal();
|
|
130
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
131
|
+
const activeOption = options.find((option) => option.getAttribute('aria-selected') === 'true');
|
|
132
|
+
(activeOption ?? options[0])?.focus();
|
|
133
|
+
});
|
|
134
|
+
closeButton.addEventListener('click', closeDialog);
|
|
135
|
+
dialog.addEventListener('cancel', (event) => {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
closeDialog();
|
|
138
|
+
});
|
|
139
|
+
dialog.addEventListener('close', () => {
|
|
140
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
141
|
+
trigger.focus();
|
|
142
|
+
});
|
|
143
|
+
dialog.addEventListener('click', (event) => {
|
|
144
|
+
if (event.target === dialog) closeDialog();
|
|
145
|
+
});
|
|
146
|
+
dialog.querySelector('[role="listbox"]')?.addEventListener('keydown', (event) => {
|
|
147
|
+
if (!(event.target instanceof HTMLButtonElement)) return;
|
|
148
|
+
const index = options.indexOf(event.target);
|
|
149
|
+
const nextIndex = getKeyboardTargetIndex(index, event.key, options.length);
|
|
150
|
+
if (nextIndex !== index) {
|
|
151
|
+
event.preventDefault();
|
|
152
|
+
options[nextIndex]?.focus();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
for (const option of options) {
|
|
156
|
+
option.addEventListener('click', () => {
|
|
157
|
+
const locale = option.dataset.locale;
|
|
158
|
+
const href = option.dataset.href;
|
|
159
|
+
if (!locale || !href) return;
|
|
160
|
+
closeDialog();
|
|
161
|
+
if (option.getAttribute('aria-selected') === 'true') return;
|
|
162
|
+
persistStarlightLocaleSelection(locale);
|
|
163
|
+
const target = new URL(href, window.location.origin);
|
|
164
|
+
target.search = window.location.search;
|
|
165
|
+
target.hash = window.location.hash;
|
|
166
|
+
window.location.assign(target.toString());
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!customElements.get('hagilight-language-chooser')) {
|
|
173
|
+
customElements.define('hagilight-language-chooser', HagilightLanguageChooser);
|
|
174
|
+
}
|
|
175
|
+
</script>
|
|
176
|
+
|
|
177
|
+
<style>
|
|
178
|
+
.language-switcher {
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.language-trigger {
|
|
184
|
+
display: inline-flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
justify-content: center;
|
|
187
|
+
gap: 0.55rem;
|
|
188
|
+
min-width: 7.25rem;
|
|
189
|
+
min-height: 2.125rem;
|
|
190
|
+
padding: 0 0.8rem;
|
|
191
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
192
|
+
border-radius: 999px;
|
|
193
|
+
background: color-mix(in srgb, var(--sl-color-black) 8%, transparent);
|
|
194
|
+
color: var(--sl-color-text-high);
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
font: inherit;
|
|
197
|
+
transition: background-color var(--sl-transition-fast), border-color var(--sl-transition-fast);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.language-trigger:hover,
|
|
201
|
+
.dialog-close:hover,
|
|
202
|
+
.language-option:hover {
|
|
203
|
+
background: var(--sl-color-gray-6);
|
|
204
|
+
border-color: var(--sl-color-accent-high);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.language-trigger:focus-visible,
|
|
208
|
+
.dialog-close:focus-visible,
|
|
209
|
+
.language-option:focus-visible {
|
|
210
|
+
outline: 2px solid var(--sl-color-accent-high);
|
|
211
|
+
outline-offset: 2px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.trigger-label {
|
|
215
|
+
max-width: 10rem;
|
|
216
|
+
overflow: hidden;
|
|
217
|
+
text-overflow: ellipsis;
|
|
218
|
+
white-space: nowrap;
|
|
219
|
+
font-size: var(--sl-text-sm);
|
|
220
|
+
font-weight: var(--sl-font-weight-medium);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.trigger-chevron {
|
|
224
|
+
font-size: 0.75rem;
|
|
225
|
+
transition: transform var(--sl-transition-fast);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.language-switcher:has(.language-dialog[open]) .trigger-chevron {
|
|
229
|
+
transform: rotate(180deg);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.language-dialog {
|
|
233
|
+
width: min(54rem, calc(100vw - 3rem));
|
|
234
|
+
max-width: none;
|
|
235
|
+
max-height: min(30rem, calc(100dvh - 3rem));
|
|
236
|
+
padding: 1.1rem 1.1rem 1rem;
|
|
237
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
238
|
+
border-radius: 1rem;
|
|
239
|
+
background: color-mix(in srgb, var(--sl-color-bg) 94%, transparent);
|
|
240
|
+
color: var(--sl-color-text);
|
|
241
|
+
box-shadow: 0 24px 64px rgba(2, 6, 23, 0.32);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.language-dialog::backdrop {
|
|
245
|
+
background: rgba(15, 23, 42, 0.52);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.dialog-header {
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: flex-start;
|
|
251
|
+
justify-content: space-between;
|
|
252
|
+
gap: 1rem;
|
|
253
|
+
margin-bottom: 1rem;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.dialog-title {
|
|
257
|
+
margin: 0;
|
|
258
|
+
color: var(--sl-color-white);
|
|
259
|
+
font-size: 1rem;
|
|
260
|
+
font-weight: 700;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.dialog-current {
|
|
264
|
+
margin: 0.35rem 0 0;
|
|
265
|
+
font-size: var(--sl-text-sm);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.dialog-close {
|
|
269
|
+
display: inline-flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
justify-content: center;
|
|
272
|
+
width: 2.25rem;
|
|
273
|
+
height: 2.25rem;
|
|
274
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
275
|
+
border-radius: 999px;
|
|
276
|
+
background: transparent;
|
|
277
|
+
color: var(--sl-color-text-high);
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
font: inherit;
|
|
280
|
+
font-size: 1.125rem;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.language-grid-scroller {
|
|
284
|
+
max-height: min(20rem, calc(100dvh - 11rem));
|
|
285
|
+
overflow: auto;
|
|
286
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
287
|
+
border-radius: 0.875rem;
|
|
288
|
+
background: color-mix(in srgb, var(--sl-color-black) 6%, transparent);
|
|
289
|
+
padding: 0.95rem 1rem;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.language-grid {
|
|
293
|
+
display: grid;
|
|
294
|
+
grid-template-columns: repeat(5, minmax(7.75rem, 8.75rem));
|
|
295
|
+
justify-content: center;
|
|
296
|
+
gap: 0.7rem;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.language-option {
|
|
300
|
+
display: flex;
|
|
301
|
+
min-height: 4.2rem;
|
|
302
|
+
width: 100%;
|
|
303
|
+
flex-direction: column;
|
|
304
|
+
align-items: center;
|
|
305
|
+
justify-content: center;
|
|
306
|
+
gap: 0.45rem;
|
|
307
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
308
|
+
border-radius: 0.875rem;
|
|
309
|
+
background: color-mix(in srgb, var(--sl-color-bg-nav) 88%, transparent);
|
|
310
|
+
color: var(--sl-color-text-high);
|
|
311
|
+
cursor: pointer;
|
|
312
|
+
font: inherit;
|
|
313
|
+
font-size: 0.95rem;
|
|
314
|
+
font-weight: 700;
|
|
315
|
+
padding: 0.75rem 0.65rem;
|
|
316
|
+
text-align: center;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.language-option-selected {
|
|
320
|
+
border-color: var(--sl-color-accent-high);
|
|
321
|
+
background: color-mix(in srgb, var(--sl-color-accent-high) 18%, transparent);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.selected-badge {
|
|
325
|
+
display: inline-flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
justify-content: center;
|
|
328
|
+
min-height: 1.45rem;
|
|
329
|
+
padding: 0 0.55rem;
|
|
330
|
+
border-radius: 999px;
|
|
331
|
+
background: color-mix(in srgb, var(--sl-color-accent-high) 18%, transparent);
|
|
332
|
+
color: var(--sl-color-accent-high);
|
|
333
|
+
font-size: 0.75rem;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@media (max-width: 72rem) {
|
|
337
|
+
.language-dialog {
|
|
338
|
+
width: min(46rem, calc(100vw - 2.5rem));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.language-grid {
|
|
342
|
+
grid-template-columns: repeat(4, minmax(7.35rem, 8.35rem));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@media (max-width: 50rem) {
|
|
347
|
+
.language-dialog {
|
|
348
|
+
width: calc(100vw - 2rem);
|
|
349
|
+
max-height: calc(100dvh - 2rem);
|
|
350
|
+
padding: 1rem;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.language-grid {
|
|
354
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
</style>
|
package/index.mjs
CHANGED
|
@@ -1,19 +1,169 @@
|
|
|
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
|
+
const defaultLogo = fileURLToPath(import.meta.resolve('@hagicode/hagilight/logo.png'));
|
|
7
|
+
|
|
8
|
+
function resolveProvider(config, { name, idKey, pattern }) {
|
|
9
|
+
if (config === undefined) return undefined;
|
|
10
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
11
|
+
throw new TypeError(`Hagilight ${name} analytics options must be an object.`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const enabled = config.enabled ?? Boolean(config[idKey]);
|
|
15
|
+
if (!enabled) return undefined;
|
|
16
|
+
|
|
17
|
+
const id = config[idKey];
|
|
18
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
19
|
+
throw new Error(`Hagilight ${name} analytics is enabled but its ${idKey} is missing.`);
|
|
20
|
+
}
|
|
21
|
+
if (!pattern.test(id)) {
|
|
22
|
+
throw new Error(`Hagilight ${name} analytics ${idKey} has an invalid format.`);
|
|
23
|
+
}
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createConfiguredIntegration(instanceId, serializedOptions, componentIds) {
|
|
28
|
+
const optionsId = `virtual:hagilight-starlight/${instanceId}/options`;
|
|
29
|
+
const headerPath = componentIds.header
|
|
30
|
+
? fileURLToPath(new URL('./Header.astro', import.meta.url))
|
|
31
|
+
: undefined;
|
|
32
|
+
const footerPath = fileURLToPath(new URL('./Footer.astro', import.meta.url));
|
|
33
|
+
const headerSource = headerPath
|
|
34
|
+
? `---
|
|
35
|
+
import Header from ${JSON.stringify(headerPath)};
|
|
36
|
+
import options from '${optionsId}';
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<Header links={options.links} />
|
|
40
|
+
`
|
|
41
|
+
: undefined;
|
|
42
|
+
const promotionImport = serializedOptions.promotoEnabled
|
|
43
|
+
? "import PromotoBanner from '@hagicode/hagilight/PromotoBanner';"
|
|
44
|
+
: '';
|
|
45
|
+
const promotionRender = serializedOptions.promotoEnabled ? '<PromotoBanner />' : '';
|
|
46
|
+
const analyticsImport = serializedOptions.fiftyOneLaId
|
|
47
|
+
? "import Analytics51LA from '@hagicode/hagilight/Analytics51LA';"
|
|
48
|
+
: '';
|
|
49
|
+
const analyticsRender = serializedOptions.fiftyOneLaId
|
|
50
|
+
? '<Analytics51LA siteId={options.fiftyOneLaId} />'
|
|
51
|
+
: '';
|
|
52
|
+
const footerSource = `---
|
|
53
|
+
import Footer from ${JSON.stringify(footerPath)};
|
|
54
|
+
${promotionImport}
|
|
55
|
+
${analyticsImport}
|
|
56
|
+
import options from '${optionsId}';
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<Footer locale={Astro.locals?.starlightRoute?.locale} links={options.links} />
|
|
60
|
+
${promotionRender}
|
|
61
|
+
${analyticsRender}
|
|
62
|
+
`;
|
|
63
|
+
const headSource = `---
|
|
64
|
+
import DefaultHead from '@astrojs/starlight/components/Head.astro';
|
|
65
|
+
import GoogleAnalytics from '@hagicode/hagilight/GoogleAnalytics';
|
|
66
|
+
import options from '${optionsId}';
|
|
67
|
+
const isNotFound = /(?:^|\\/)404(?:\\.html)?\\/?$/u.test(Astro.url.pathname);
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
<DefaultHead />
|
|
71
|
+
{!isNotFound && options.googleAnalyticsMeasurementId && (
|
|
72
|
+
<GoogleAnalytics measurementId={options.googleAnalyticsMeasurementId} />
|
|
73
|
+
)}
|
|
74
|
+
`;
|
|
75
|
+
const modules = new Map([
|
|
76
|
+
...(componentIds.header ? [[componentIds.header, headerSource]] : []),
|
|
77
|
+
[componentIds.footer, footerSource],
|
|
78
|
+
...(componentIds.head ? [[componentIds.head, headSource]] : []),
|
|
79
|
+
[optionsId, `export default ${JSON.stringify(serializedOptions)};`],
|
|
80
|
+
]);
|
|
81
|
+
const vitePlugin = {
|
|
82
|
+
name: `@hagicode/hagilight-starlight:${instanceId}`,
|
|
83
|
+
resolveId(id) {
|
|
84
|
+
return modules.has(id) ? `\0${id}` : null;
|
|
85
|
+
},
|
|
86
|
+
load(id) {
|
|
87
|
+
return id.startsWith('\0') ? modules.get(id.slice(1)) ?? null : null;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
name: `@hagicode/hagilight-starlight:${instanceId}`,
|
|
93
|
+
hooks: {
|
|
94
|
+
'astro:config:setup'({ updateConfig }) {
|
|
95
|
+
updateConfig({ vite: { plugins: [vitePlugin] } });
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
3
101
|
export default function hagilight(options = {}) {
|
|
4
|
-
|
|
102
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
103
|
+
throw new TypeError('Hagilight Starlight options must be an object.');
|
|
104
|
+
}
|
|
105
|
+
if (options.header !== undefined
|
|
106
|
+
&& (!options.header || typeof options.header !== 'object' || Array.isArray(options.header))) {
|
|
107
|
+
throw new TypeError('Hagilight header options must be an object.');
|
|
108
|
+
}
|
|
109
|
+
if (options.header?.enabled !== undefined && typeof options.header.enabled !== 'boolean') {
|
|
110
|
+
throw new TypeError('Hagilight header enabled option must be a boolean.');
|
|
111
|
+
}
|
|
112
|
+
if (options.analytics !== undefined
|
|
113
|
+
&& (!options.analytics || typeof options.analytics !== 'object' || Array.isArray(options.analytics))) {
|
|
114
|
+
throw new TypeError('Hagilight analytics options must be an object.');
|
|
115
|
+
}
|
|
116
|
+
const analytics = options.analytics ?? {};
|
|
117
|
+
const googleAnalyticsMeasurementId = resolveProvider(analytics.googleAnalytics, {
|
|
118
|
+
name: 'Google Analytics',
|
|
119
|
+
idKey: 'measurementId',
|
|
120
|
+
pattern: GOOGLE_ID_PATTERN,
|
|
121
|
+
});
|
|
122
|
+
const fiftyOneLaId = resolveProvider(analytics.fiftyOneLa, {
|
|
123
|
+
name: '51LA',
|
|
124
|
+
idKey: 'siteId',
|
|
125
|
+
pattern: FIFTY_ONE_LA_ID_PATTERN,
|
|
126
|
+
});
|
|
127
|
+
const instanceId = randomUUID();
|
|
128
|
+
const headerEnabled = options.header?.enabled !== false;
|
|
129
|
+
const componentIds = {
|
|
130
|
+
header: headerEnabled
|
|
131
|
+
? `virtual:hagilight-starlight/${instanceId}/Header.astro`
|
|
132
|
+
: undefined,
|
|
133
|
+
footer: `virtual:hagilight-starlight/${instanceId}/Footer.astro`,
|
|
134
|
+
head: googleAnalyticsMeasurementId
|
|
135
|
+
? `virtual:hagilight-starlight/${instanceId}/Head.astro`
|
|
136
|
+
: undefined,
|
|
137
|
+
};
|
|
138
|
+
const serializedOptions = {
|
|
139
|
+
promotoEnabled: options.promoto?.enabled !== false,
|
|
140
|
+
links: options.links ?? {},
|
|
141
|
+
googleAnalyticsMeasurementId,
|
|
142
|
+
fiftyOneLaId,
|
|
143
|
+
};
|
|
144
|
+
|
|
5
145
|
return {
|
|
6
146
|
name: '@hagicode/hagilight-starlight',
|
|
7
147
|
hooks: {
|
|
8
|
-
'config:setup'({ config, updateConfig }) {
|
|
148
|
+
'config:setup'({ config, updateConfig, addIntegration }) {
|
|
149
|
+
if (headerEnabled && config.components?.Header) {
|
|
150
|
+
throw new Error('Hagilight cannot replace an existing Starlight Header override. Set header: { enabled: false } to keep it, or compose @hagicode/hagilight-starlight/Header directly.');
|
|
151
|
+
}
|
|
9
152
|
if (config.components?.Footer) {
|
|
10
153
|
throw new Error('Hagilight cannot replace an existing Starlight Footer override.');
|
|
11
154
|
}
|
|
155
|
+
if (googleAnalyticsMeasurementId && config.components?.Head) {
|
|
156
|
+
throw new Error('Hagilight Google Analytics cannot replace an existing Starlight Head override. Compose @hagicode/hagilight/GoogleAnalytics in your Head instead.');
|
|
157
|
+
}
|
|
12
158
|
|
|
159
|
+
addIntegration(createConfiguredIntegration(instanceId, serializedOptions, componentIds));
|
|
13
160
|
updateConfig({
|
|
161
|
+
...(config.logo === undefined ? { logo: { src: defaultLogo, alt: 'HagiCode' } } : {}),
|
|
14
162
|
components: {
|
|
15
163
|
...config.components,
|
|
16
|
-
|
|
164
|
+
...(componentIds.header ? { Header: componentIds.header } : {}),
|
|
165
|
+
Footer: componentIds.footer,
|
|
166
|
+
...(componentIds.head ? { Head: componentIds.head } : {}),
|
|
17
167
|
},
|
|
18
168
|
});
|
|
19
169
|
},
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export const DEFAULT_LANGUAGE_OPTIONS = [
|
|
2
|
+
{ code: 'root', label: '简体中文', lang: 'zh-CN' },
|
|
3
|
+
{ code: 'en-US', label: 'English', lang: 'en-US' },
|
|
4
|
+
{ code: 'zh-Hant', label: '繁體中文', lang: 'zh-Hant' },
|
|
5
|
+
{ code: 'fr-FR', label: 'Français', lang: 'fr-FR' },
|
|
6
|
+
{ code: 'de-DE', label: 'Deutsch', lang: 'de-DE' },
|
|
7
|
+
{ code: 'es-ES', label: 'Español (España)', lang: 'es-ES' },
|
|
8
|
+
{ code: 'ja-JP', label: '日本語', lang: 'ja-JP' },
|
|
9
|
+
{ code: 'ko-KR', label: '한국어', lang: 'ko-KR' },
|
|
10
|
+
{ code: 'pt-BR', label: 'Português (Brasil)', lang: 'pt-BR' },
|
|
11
|
+
{ code: 'ru-RU', label: 'Русский', lang: 'ru-RU' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const normalizeLang = (lang) => lang?.replaceAll('_', '-').toLowerCase();
|
|
15
|
+
|
|
16
|
+
export function getConfiguredLanguageOptions(locales, currentLocale) {
|
|
17
|
+
const catalog = DEFAULT_LANGUAGE_OPTIONS;
|
|
18
|
+
return Object.entries(locales ?? {})
|
|
19
|
+
.map(([code, locale]) => {
|
|
20
|
+
const lang = typeof locale === 'string' ? locale : locale?.lang ?? code;
|
|
21
|
+
const catalogEntry = catalog.find((entry) =>
|
|
22
|
+
entry.code === code || normalizeLang(entry.lang) === normalizeLang(lang),
|
|
23
|
+
);
|
|
24
|
+
return {
|
|
25
|
+
code,
|
|
26
|
+
label: typeof locale === 'object' && locale?.label
|
|
27
|
+
? locale.label
|
|
28
|
+
: catalogEntry?.label ?? code,
|
|
29
|
+
lang,
|
|
30
|
+
selected: code === currentLocale,
|
|
31
|
+
catalogOrder: catalogEntry ? catalog.indexOf(catalogEntry) : catalog.length,
|
|
32
|
+
};
|
|
33
|
+
})
|
|
34
|
+
.sort((left, right) => left.catalogOrder - right.catalogOrder)
|
|
35
|
+
.map(({ catalogOrder, ...option }) => option);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getKeyboardTargetIndex(index, key, count) {
|
|
39
|
+
if (count < 1) return -1;
|
|
40
|
+
switch (key) {
|
|
41
|
+
case 'ArrowDown':
|
|
42
|
+
case 'ArrowRight':
|
|
43
|
+
return (index + 1) % count;
|
|
44
|
+
case 'ArrowUp':
|
|
45
|
+
case 'ArrowLeft':
|
|
46
|
+
return (index - 1 + count) % count;
|
|
47
|
+
case 'Home':
|
|
48
|
+
return 0;
|
|
49
|
+
case 'End':
|
|
50
|
+
return count - 1;
|
|
51
|
+
default:
|
|
52
|
+
return index;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function buildLocaleNavigationTarget(
|
|
57
|
+
currentUrl,
|
|
58
|
+
targetLocale,
|
|
59
|
+
configuredLocales,
|
|
60
|
+
basePath = '/',
|
|
61
|
+
trailingSlash = 'ignore',
|
|
62
|
+
) {
|
|
63
|
+
const targetUrl = new URL(currentUrl);
|
|
64
|
+
const base = basePath.replace(/\/+$/u, '');
|
|
65
|
+
const hasBase = base === ''
|
|
66
|
+
? true
|
|
67
|
+
: targetUrl.pathname === base || targetUrl.pathname.startsWith(`${base}/`);
|
|
68
|
+
let pathname = hasBase ? targetUrl.pathname.slice(base.length) || '/' : targetUrl.pathname;
|
|
69
|
+
const [, firstSegment = '', remainder = ''] = pathname.match(/^\/([^/]*)(.*)$/u) ?? [];
|
|
70
|
+
const isHtmlRoute = firstSegment.endsWith('.html');
|
|
71
|
+
const currentSlug = isHtmlRoute ? firstSegment.slice(0, -5) : firstSegment;
|
|
72
|
+
const currentLocale = configuredLocales.find((locale) => locale !== 'root' && locale === currentSlug);
|
|
73
|
+
|
|
74
|
+
if (currentLocale) {
|
|
75
|
+
if (targetLocale === 'root') {
|
|
76
|
+
pathname = isHtmlRoute ? '/index.html' : remainder || '/';
|
|
77
|
+
} else {
|
|
78
|
+
pathname = `/${targetLocale}${isHtmlRoute ? '.html' : remainder}`;
|
|
79
|
+
}
|
|
80
|
+
} else if (targetLocale !== 'root') {
|
|
81
|
+
pathname = firstSegment === 'index.html'
|
|
82
|
+
? `/${targetLocale}.html`
|
|
83
|
+
: `/${targetLocale}${pathname}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
targetUrl.pathname = `${hasBase ? base : ''}${pathname}` || '/';
|
|
87
|
+
if (trailingSlash === 'never') {
|
|
88
|
+
targetUrl.pathname = targetUrl.pathname.replace(/\/+$/u, '') || '/';
|
|
89
|
+
}
|
|
90
|
+
return targetUrl;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function persistStarlightLocaleSelection(locale) {
|
|
94
|
+
try {
|
|
95
|
+
const storedValue = window.localStorage.getItem('starlight-route');
|
|
96
|
+
let routePreference = {};
|
|
97
|
+
if (storedValue) {
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(storedValue);
|
|
100
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
101
|
+
routePreference = parsed;
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
routePreference = {};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
window.localStorage.setItem(
|
|
108
|
+
'starlight-route',
|
|
109
|
+
JSON.stringify({ ...routePreference, lang: locale }),
|
|
110
|
+
);
|
|
111
|
+
} catch {
|
|
112
|
+
// Language navigation must remain available when browser storage is blocked.
|
|
113
|
+
}
|
|
114
|
+
}
|
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.7.1.c02207e",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Starlight customizations for HagiCode documentation",
|
|
6
6
|
"repository": {
|
|
@@ -14,15 +14,17 @@
|
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
16
|
".": "./index.mjs",
|
|
17
|
+
"./Header": "./Header.astro",
|
|
17
18
|
"./Footer": "./Footer.astro",
|
|
18
19
|
"./PromotoFooter": "./PromotoFooter.astro"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"index.mjs",
|
|
23
|
+
"*.mjs",
|
|
22
24
|
"*.astro"
|
|
23
25
|
],
|
|
24
26
|
"dependencies": {
|
|
25
|
-
"@hagicode/hagilight": "0.1.2-dev.
|
|
27
|
+
"@hagicode/hagilight": "0.1.2-dev.7.1.c02207e"
|
|
26
28
|
},
|
|
27
29
|
"peerDependencies": {
|
|
28
30
|
"@astrojs/starlight": "^0.42.4",
|