@hagicode/hagilight 0.1.2-dev.4.1.974ccd5 → 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.
@@ -0,0 +1,17 @@
1
+ ---
2
+ interface Props {
3
+ siteId: string;
4
+ }
5
+
6
+ const { siteId } = Astro.props;
7
+ if (!siteId?.trim()) throw new Error('Analytics51LA requires a siteId prop.');
8
+ ---
9
+
10
+ {import.meta.env.PROD && (
11
+ <>
12
+ <script is:inline charset="UTF-8" id="LA_COLLECT" src="https://sdk.51.la/js-sdk-pro.min.js"></script>
13
+ <script is:inline define:vars={{ siteId }}>
14
+ LA.init({ id: siteId, ck: siteId, autoTrack: true, hashMode: true, screenRecord: true });
15
+ </script>
16
+ </>
17
+ )}
@@ -0,0 +1,21 @@
1
+ ---
2
+ interface Props {
3
+ measurementId: string;
4
+ }
5
+
6
+ const { measurementId } = Astro.props;
7
+ if (!measurementId?.trim()) throw new Error('GoogleAnalytics requires a measurementId prop.');
8
+ const scriptUrl = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`;
9
+ ---
10
+
11
+ {import.meta.env.PROD && (
12
+ <>
13
+ <script async src={scriptUrl}></script>
14
+ <script is:inline define:vars={{ measurementId }}>
15
+ window.dataLayer = window.dataLayer || [];
16
+ function gtag(){dataLayer.push(arguments);}
17
+ gtag('js', new Date());
18
+ gtag('config', measurementId);
19
+ </script>
20
+ </>
21
+ )}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/hagilight",
3
- "version": "0.1.2-dev.4.1.974ccd5",
3
+ "version": "0.1.2-dev.6.1.f3f7b3d",
4
4
  "type": "module",
5
5
  "description": "Shared Astro components for HagiCode sites",
6
6
  "repository": {
@@ -14,7 +14,10 @@
14
14
  },
15
15
  "exports": {
16
16
  "./Copyright": "./Copyright.astro",
17
- "./PromotoBanner": "./PromotoBanner.astro"
17
+ "./PromotoBanner": "./PromotoBanner.astro",
18
+ "./site-links": "./site-links.ts",
19
+ "./GoogleAnalytics": "./GoogleAnalytics.astro",
20
+ "./Analytics51LA": "./Analytics51LA.astro"
18
21
  },
19
22
  "files": [
20
23
  "*.astro",
package/site-links.ts ADDED
@@ -0,0 +1,228 @@
1
+ export type SiteLinkKey =
2
+ | 'home'
3
+ | 'blog'
4
+ | 'support'
5
+ | 'downloadClient'
6
+ | 'about'
7
+ | 'dockerCompose'
8
+ | 'productDocs'
9
+ | 'rss'
10
+ | 'costCalculator'
11
+ | 'github'
12
+ | 'discord'
13
+ | 'qqGroup'
14
+ | 'steam';
15
+
16
+ export type LinkGroup = 'header' | 'quick' | 'community';
17
+ type LocalizedText = string | Readonly<Record<string, string>>;
18
+
19
+ export interface SiteLink {
20
+ id: SiteLinkKey | string;
21
+ label: string;
22
+ href: string;
23
+ target?: '_blank';
24
+ rel?: 'noopener noreferrer';
25
+ }
26
+
27
+ export interface SiteLinkOverride {
28
+ href?: LocalizedText;
29
+ label?: LocalizedText;
30
+ external?: boolean;
31
+ }
32
+
33
+ export interface ExtraSiteLink extends SiteLinkOverride {
34
+ href: LocalizedText;
35
+ label: LocalizedText;
36
+ }
37
+
38
+ export interface RelatedSite {
39
+ id: string;
40
+ name: LocalizedText;
41
+ url: string;
42
+ description?: LocalizedText;
43
+ }
44
+
45
+ export interface SiteLinksOptions {
46
+ overrides?: Partial<Record<SiteLinkKey, SiteLinkOverride>>;
47
+ extraLinks?: Partial<Record<LinkGroup, readonly ExtraSiteLink[]>>;
48
+ relatedSites?: readonly RelatedSite[];
49
+ siteId?: string;
50
+ siteUrl?: string;
51
+ }
52
+
53
+ interface LinkDefinition {
54
+ label: LocalizedText;
55
+ href: string | ((locale: string) => string);
56
+ external?: boolean;
57
+ }
58
+
59
+ const translations = {
60
+ home: { 'zh-CN': '首页', 'zh-Hant': '首頁', 'en-US': 'Home' },
61
+ blog: { 'zh-CN': '博客', 'zh-Hant': '部落格', 'en-US': 'Blog' },
62
+ support: { 'zh-CN': '获取技术支持', 'zh-Hant': '獲取技術支援', 'en-US': 'Get Support' },
63
+ downloadClient: { 'zh-CN': '下载客户端', 'zh-Hant': '下載客戶端', 'en-US': 'Download Client' },
64
+ about: { 'zh-CN': '关于 HagiCode', 'zh-Hant': '關於 HagiCode', 'en-US': 'About HagiCode' },
65
+ dockerCompose: { 'zh-CN': 'Docker Compose 安装', 'zh-Hant': 'Docker Compose 安裝', 'en-US': 'Docker Compose Installation' },
66
+ productDocs: { 'zh-CN': '产品文档', 'zh-Hant': '產品文件', 'en-US': 'Product Docs' },
67
+ rss: { 'zh-CN': 'RSS 订阅', 'zh-Hant': 'RSS 訂閱', 'en-US': 'RSS Feed' },
68
+ costCalculator: { 'zh-CN': '算一算,AI 会不会淘汰我', 'zh-Hant': '算一算,AI 會不會淘汰我', 'en-US': 'Will AI Replace Me?' },
69
+ github: { 'zh-CN': 'GitHub', 'zh-Hant': 'GitHub', 'en-US': 'GitHub' },
70
+ discord: { 'zh-CN': 'Discord 社区', 'zh-Hant': 'Discord 社群', 'en-US': 'Discord Community' },
71
+ qqGroup: { 'zh-CN': 'QQ 群 610394020', 'zh-Hant': 'QQ 群 610394020', 'en-US': 'QQ Group 610394020' },
72
+ steam: { 'zh-CN': 'Steam', 'zh-Hant': 'Steam', 'en-US': 'Steam' },
73
+ } satisfies Record<SiteLinkKey, LocalizedText>;
74
+
75
+ const defaultLinks: Record<SiteLinkKey, LinkDefinition> = {
76
+ home: { label: translations.home, href: 'https://www.hagicode.com/' },
77
+ blog: { label: translations.blog, href: (locale) => docsPath(locale, '/blog/') },
78
+ support: { label: translations.support, href: 'https://www.hagicode.com/about/' },
79
+ downloadClient: { label: translations.downloadClient, href: 'https://www.hagicode.com/desktop/' },
80
+ about: { label: translations.about, href: 'https://www.hagicode.com/about/' },
81
+ dockerCompose: { label: translations.dockerCompose, href: (locale) => docsPath(locale, '/installation/docker-compose/') },
82
+ productDocs: { label: translations.productDocs, href: (locale) => docsPath(locale, '/product-overview/') },
83
+ rss: { label: translations.rss, href: (locale) => docsPath(locale, '/blog/rss.zh-CN.xml') },
84
+ costCalculator: { label: translations.costCalculator, href: 'https://cost.hagicode.com', external: true },
85
+ github: { label: translations.github, href: 'https://github.com/HagiCode-org/site', external: true },
86
+ discord: { label: translations.discord, href: 'https://discord.gg/qY662sJK', external: true },
87
+ qqGroup: { label: translations.qqGroup, href: 'https://qm.qq.com/q/Fwb0o094kw', external: true },
88
+ steam: { label: translations.steam, href: 'https://store.steampowered.com/app/4625540/Hagicode/', external: true },
89
+ };
90
+
91
+ const groups: Record<LinkGroup, readonly SiteLinkKey[]> = {
92
+ header: ['home', 'blog', 'support'],
93
+ quick: ['downloadClient', 'about', 'dockerCompose', 'productDocs', 'blog', 'rss', 'costCalculator'],
94
+ community: ['github', 'discord', 'qqGroup', 'steam'],
95
+ };
96
+
97
+ function normalizeLocale(locale?: string | null): string {
98
+ const normalized = locale?.trim().replaceAll('_', '-').toLowerCase();
99
+ if (normalized === 'zh' || normalized === 'zh-cn' || normalized === 'zh-hans' || normalized === 'root') {
100
+ return 'zh-CN';
101
+ }
102
+ if (normalized === 'zh-hant' || normalized === 'zh-tw' || normalized === 'zh-hk') {
103
+ return 'zh-Hant';
104
+ }
105
+ if (normalized === 'en' || normalized === 'en-us' || !normalized) {
106
+ return 'en-US';
107
+ }
108
+ const canonical = [
109
+ 'ja-JP', 'ko-KR', 'de-DE', 'fr-FR', 'es-ES', 'pt-BR', 'ru-RU',
110
+ ].find((candidate) => candidate.toLowerCase() === normalized);
111
+ if (canonical) return canonical;
112
+ return locale!.trim();
113
+ }
114
+
115
+ function docsPath(locale: string, pathname: string): string {
116
+ if (pathname.startsWith('/blog/rss.')) {
117
+ const language = locale.startsWith('zh') ? 'zh-CN' : 'en-US';
118
+ return `https://docs.hagicode.com/blog/rss.${language}.xml`;
119
+ }
120
+ const supportedRouteLocales = new Set([
121
+ 'en-US', 'zh-CN', 'zh-Hant', 'ja-JP', 'ko-KR', 'de-DE', 'fr-FR', 'es-ES', 'pt-BR', 'ru-RU',
122
+ ]);
123
+ const routeLocale = supportedRouteLocales.has(locale) ? locale : 'en-US';
124
+ const prefix = routeLocale === 'zh-CN' ? '' : `/${routeLocale}`;
125
+ return `https://docs.hagicode.com${prefix}${pathname}`;
126
+ }
127
+
128
+ function resolveLocalized(value: LocalizedText, locale: string, fallback: string): string {
129
+ if (typeof value === 'string') return value;
130
+ for (const candidate of [locale, ...(locale === 'zh-Hant' ? ['zh-CN', 'en-US'] : ['en-US'])]) {
131
+ const text = value[candidate];
132
+ if (typeof text === 'string' && text.trim()) return text;
133
+ }
134
+ const firstValue = Object.values(value).find((text) => typeof text === 'string' && text.trim());
135
+ return firstValue ?? fallback;
136
+ }
137
+
138
+ function normalizeUrl(value: string): string {
139
+ const input = value.trim();
140
+ if (!input) throw new TypeError('Link destinations must not be empty.');
141
+ const url = new URL(input, 'https://hagilight.invalid');
142
+ if (!['http:', 'https:', 'mailto:', 'tel:'].includes(url.protocol)) {
143
+ throw new TypeError(`Unsupported link protocol: ${url.protocol}`);
144
+ }
145
+ url.hash = '';
146
+ url.search = '';
147
+ url.pathname = url.pathname.replace(/\/+$/u, '') || '/';
148
+ return url.origin === 'https://hagilight.invalid' ? url.pathname : url.toString();
149
+ }
150
+
151
+ function resolveLink(
152
+ key: SiteLinkKey | string,
153
+ locale: string,
154
+ definition: LinkDefinition,
155
+ override?: SiteLinkOverride,
156
+ ): SiteLink {
157
+ const defaultHref = typeof definition.href === 'function' ? definition.href(locale) : definition.href;
158
+ const href = override?.href === undefined ? defaultHref : resolveLocalized(override.href, locale, defaultHref);
159
+ const label = override?.label === undefined
160
+ ? resolveLocalized(definition.label, locale, key)
161
+ : resolveLocalized(override.label, locale, resolveLocalized(definition.label, locale, key));
162
+ normalizeUrl(href);
163
+ const external = override?.external ?? definition.external ?? false;
164
+ return {
165
+ id: key,
166
+ label,
167
+ href,
168
+ ...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {}),
169
+ };
170
+ }
171
+
172
+ export function resolveSiteLinks(localeInput?: string | null, options: SiteLinksOptions = {}) {
173
+ const locale = normalizeLocale(localeInput);
174
+ const resolvedGroups = Object.fromEntries(
175
+ (Object.keys(groups) as LinkGroup[]).map((group) => {
176
+ const catalogLinks = groups[group].map((key) =>
177
+ resolveLink(key, locale, defaultLinks[key], options.overrides?.[key]),
178
+ );
179
+ const extraLinks = (options.extraLinks?.[group] ?? []).map((entry, index) => {
180
+ const definition: LinkDefinition = {
181
+ label: entry.label,
182
+ href: resolveLocalized(entry.href, locale, ''),
183
+ external: entry.external,
184
+ };
185
+ return resolveLink(`${group}-custom-${index + 1}`, locale, definition);
186
+ });
187
+ return [group, [...catalogLinks, ...extraLinks]];
188
+ }),
189
+ ) as Record<LinkGroup, SiteLink[]>;
190
+
191
+ const renderedUrls = new Set(
192
+ Object.values(resolvedGroups).flat().map((link) => normalizeUrl(link.href)),
193
+ );
194
+ if (options.siteUrl) renderedUrls.add(normalizeUrl(options.siteUrl));
195
+
196
+ const relatedIds = new Set<string>();
197
+ const relatedUrls = new Set<string>();
198
+ const relatedSites = (options.relatedSites ?? []).flatMap((site) => {
199
+ const normalizedUrl = normalizeUrl(site.url);
200
+ if (site.id === options.siteId || renderedUrls.has(normalizedUrl)
201
+ || relatedIds.has(site.id) || relatedUrls.has(normalizedUrl)) {
202
+ return [];
203
+ }
204
+ relatedIds.add(site.id);
205
+ relatedUrls.add(normalizedUrl);
206
+ return [{
207
+ id: site.id,
208
+ name: resolveLocalized(site.name, locale, site.id),
209
+ description: site.description
210
+ ? resolveLocalized(site.description, locale, '')
211
+ : undefined,
212
+ href: site.url,
213
+ }];
214
+ });
215
+
216
+ return {
217
+ locale,
218
+ labels: {
219
+ relatedSites: resolveLocalized({ 'zh-CN': '相关站点', 'zh-Hant': '相關站點', 'en-US': 'Related sites' }, locale, 'Related sites'),
220
+ quickLinks: resolveLocalized({ 'zh-CN': '快捷链接', 'zh-Hant': '快速連結', 'en-US': 'Quick links' }, locale, 'Quick links'),
221
+ community: resolveLocalized({ 'zh-CN': '社区与支持', 'zh-Hant': '社群與支援', 'en-US': 'Community & support' }, locale, 'Community & support'),
222
+ },
223
+ header: resolvedGroups.header,
224
+ quick: resolvedGroups.quick,
225
+ community: resolvedGroups.community,
226
+ relatedSites,
227
+ };
228
+ }