@hagicode/hagilight 0.2.2-dev.17.1.e7bfdb3 → 0.2.2-dev.19.1.ce103b7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/hagilight",
3
- "version": "0.2.2-dev.17.1.e7bfdb3",
3
+ "version": "0.2.2-dev.19.1.ce103b7",
4
4
  "type": "module",
5
5
  "description": "Shared Astro components for HagiCode sites",
6
6
  "repository": {
package/promotions.ts CHANGED
@@ -154,9 +154,9 @@ function localeCandidates(locale: string | null | undefined): string[] {
154
154
  const normalized = canonical.toLowerCase();
155
155
  const candidates = [normalized, normalized.split('-')[0] ?? normalized];
156
156
  if (normalized.startsWith('zh-hant') || ['zh-tw', 'zh-hk', 'zh-mo'].includes(normalized)) {
157
- candidates.push('zh-hant', 'zh');
157
+ candidates.push('zh-hant', 'zh-cn', 'zh');
158
158
  } else if (normalized.startsWith('zh')) {
159
- candidates.push('zh-cn', 'zh');
159
+ candidates.push('zh-cn', 'zh-hant', 'zh');
160
160
  }
161
161
  candidates.push('en-us', 'en');
162
162
  return [...new Set(candidates)];
@@ -260,4 +260,4 @@ export async function loadActivePromotions(options: {
260
260
  } catch {
261
261
  return [];
262
262
  }
263
- }
263
+ }
@@ -1,4 +1,48 @@
1
1
  [
2
+ {
3
+ "id": "hagitask",
4
+ "name": {
5
+ "zh-CN": "HagiTask",
6
+ "zh-Hant": "HagiTask",
7
+ "en-US": "HagiTask",
8
+ "ja-JP": "HagiTask",
9
+ "ko-KR": "HagiTask",
10
+ "de-DE": "HagiTask",
11
+ "fr-FR": "HagiTask",
12
+ "es-ES": "HagiTask",
13
+ "pt-BR": "HagiTask",
14
+ "ru-RU": "HagiTask"
15
+ },
16
+ "description": {
17
+ "zh-CN": "任务与工作流管理",
18
+ "zh-Hant": "任務與工作流程管理",
19
+ "en-US": "Task and workflow management.",
20
+ "ja-JP": "タスクとワークフローを管理します。",
21
+ "ko-KR": "작업과 워크플로를 관리합니다.",
22
+ "de-DE": "Aufgaben und Workflows verwalten.",
23
+ "fr-FR": "Gestion des tâches et des workflows.",
24
+ "es-ES": "Gestión de tareas y flujos de trabajo.",
25
+ "pt-BR": "Gerenciamento de tarefas e fluxos de trabalho.",
26
+ "ru-RU": "Управление задачами и рабочими процессами."
27
+ },
28
+ "url": "https://tasks.hagicode.com/"
29
+ },
30
+ {
31
+ "id": "costCalculator",
32
+ "name": {
33
+ "zh-CN": "算一算,AI会不会淘汰我",
34
+ "zh-Hant": "算一算,AI 會不會淘汰我",
35
+ "en-US": "Will AI Replace Me?",
36
+ "ja-JP": "AI に置き換えられるか診断",
37
+ "ko-KR": "AI가 나를 대체할까?",
38
+ "de-DE": "Wird KI mich ersetzen?",
39
+ "fr-FR": "L'IA va-t-elle me remplacer ?",
40
+ "es-ES": "Me reemplazará la IA?",
41
+ "pt-BR": "A IA vai me substituir?",
42
+ "ru-RU": "Заменит ли меня ИИ?"
43
+ },
44
+ "url": "https://cost.hagicode.com"
45
+ },
2
46
  {
3
47
  "id": "hagicode-main",
4
48
  "name": {
package/site-links.ts CHANGED
@@ -21,6 +21,7 @@ export type SiteLinkKey =
21
21
  | 'publicSecurityFiling';
22
22
 
23
23
  export type LinkGroup = 'header' | 'quick' | 'community' | 'filings';
24
+ export type FooterLinkSection = 'relatedSites' | 'quick' | 'community';
24
25
  type LocalizedText = string | Readonly<Record<string, string>>;
25
26
 
26
27
  export interface SiteLink {
@@ -39,6 +40,7 @@ export interface SiteLinkOverride {
39
40
  }
40
41
 
41
42
  export interface ExtraSiteLink extends SiteLinkOverride {
43
+ id?: string;
42
44
  href: LocalizedText;
43
45
  label: LocalizedText;
44
46
  }
@@ -53,8 +55,12 @@ export interface RelatedSite {
53
55
 
54
56
  export interface SiteLinksOptions {
55
57
  overrides?: Partial<Record<SiteLinkKey, SiteLinkOverride>>;
56
- extraLinks?: Partial<Record<LinkGroup, readonly ExtraSiteLink[]>>;
58
+ extraLinks?: Partial<Record<LinkGroup, readonly ExtraSiteLink[]>> & {
59
+ relatedSites?: readonly RelatedSite[];
60
+ };
61
+ removeLinks?: Partial<Record<FooterLinkSection, readonly string[]>>;
57
62
  relatedSites?: readonly RelatedSite[];
63
+ rssFeedUrl?: string;
58
64
  siteId?: string;
59
65
  siteUrl?: string;
60
66
  }
@@ -83,10 +89,10 @@ const translations = {
83
89
  'fr-FR': 'Assistance', 'es-ES': 'Soporte', 'pt-BR': 'Suporte', 'ru-RU': 'Поддержка',
84
90
  },
85
91
  downloadClient: {
86
- 'zh-CN': '下载客户端', 'zh-Hant': '下載客戶端', 'en-US': 'Download Client',
87
- 'ja-JP': 'クライアントをダウンロード', 'ko-KR': '클라이언트 다운로드',
88
- 'de-DE': 'Client herunterladen', 'fr-FR': 'Télécharger le client',
89
- 'es-ES': 'Descargar cliente', 'pt-BR': 'Baixar cliente', 'ru-RU': 'Скачать клиент',
92
+ 'zh-CN': '下载 Hagicode', 'zh-Hant': '下載 Hagicode', 'en-US': 'Download Hagicode',
93
+ 'ja-JP': 'Hagicode をダウンロード', 'ko-KR': 'Hagicode 다운로드',
94
+ 'de-DE': 'Hagicode herunterladen', 'fr-FR': 'Télécharger Hagicode',
95
+ 'es-ES': 'Descargar Hagicode', 'pt-BR': 'Baixar Hagicode', 'ru-RU': 'Скачать Hagicode',
90
96
  },
91
97
  about: {
92
98
  'zh-CN': '关于 HagiCode', 'zh-Hant': '關於 HagiCode', 'en-US': 'About HagiCode',
@@ -155,10 +161,11 @@ const translations = {
155
161
  'pt-BR': 'Grupo QQ 610394020', 'ru-RU': 'Группа QQ 610394020',
156
162
  },
157
163
  microsoftStore: {
158
- 'zh-CN': 'Microsoft Store', 'zh-Hant': 'Microsoft Store', 'en-US': 'Microsoft Store',
159
- 'ja-JP': 'Microsoft Store', 'ko-KR': 'Microsoft Store', 'de-DE': 'Microsoft Store',
160
- 'fr-FR': 'Microsoft Store', 'es-ES': 'Microsoft Store', 'pt-BR': 'Microsoft Store',
161
- 'ru-RU': 'Microsoft Store',
164
+ 'zh-CN': '下载 Hagicode Windows 版本', 'zh-Hant': '下載 Hagicode Windows 版本',
165
+ 'en-US': 'Download Hagicode for Windows', 'ja-JP': 'Hagicode for Windows をダウンロード',
166
+ 'ko-KR': 'Windows용 Hagicode 다운로드', 'de-DE': 'Hagicode für Windows herunterladen',
167
+ 'fr-FR': 'Télécharger Hagicode pour Windows', 'es-ES': 'Descargar Hagicode para Windows',
168
+ 'pt-BR': 'Baixar Hagicode para Windows', 'ru-RU': 'Скачать Hagicode для Windows',
162
169
  },
163
170
  icpFiling: '闽ICP备2026004153号-1',
164
171
  publicSecurityFiling: '闽公网安备35011102351148号',
@@ -173,7 +180,7 @@ const defaultLinks: Record<SiteLinkKey, LinkDefinition> = {
173
180
  dockerCompose: { label: translations.dockerCompose, href: (locale) => docsPath(locale, '/installation/docker-compose/') },
174
181
  productDocs: { label: translations.productDocs, href: (locale) => docsPath(locale, '/product-overview/') },
175
182
  blogPosts: { label: translations.blogPosts, href: (locale) => docsPath(locale, '/blog/') },
176
- rss: { label: translations.rss, href: (locale) => `https://docs.hagicode.com/blog/rss.${locale}.xml` },
183
+ rss: { label: translations.rss, href: '' },
177
184
  costCalculator: { label: translations.costCalculator, href: 'https://cost.hagicode.com', external: true },
178
185
  github: { label: translations.github, href: 'https://github.com/HagiCode-org/site', external: true },
179
186
  discord: { label: translations.discord, href: 'https://discord.gg/qY662sJK', external: true },
@@ -215,8 +222,8 @@ const defaultLinks: Record<SiteLinkKey, LinkDefinition> = {
215
222
 
216
223
  const groups: Record<LinkGroup, readonly SiteLinkKey[]> = {
217
224
  header: ['home', 'blog', 'support'],
218
- quick: ['downloadClient', 'microsoftStore', 'about', 'dockerCompose', 'productDocs', 'blogPosts', 'rss'],
219
- community: ['github', 'discord', 'issueFeedback', 'contactEmail', 'qqGroup', 'costCalculator'],
225
+ quick: ['downloadClient', 'microsoftStore', 'dockerCompose', 'productDocs', 'blogPosts', 'rss', 'about'],
226
+ community: ['github', 'discord', 'issueFeedback', 'contactEmail', 'qqGroup'],
220
227
  filings: ['icpFiling', 'publicSecurityFiling'],
221
228
  };
222
229
 
@@ -295,12 +302,26 @@ function resolveLink(
295
302
 
296
303
  export function resolveSiteLinks(localeInput?: string | null, options: SiteLinksOptions = {}) {
297
304
  const locale = normalizeLocale(localeInput);
305
+ if (options.rssFeedUrl !== undefined && typeof options.rssFeedUrl !== 'string') {
306
+ throw new TypeError('RSS feed URL must be a string.');
307
+ }
298
308
  const resolvedGroups = Object.fromEntries(
299
309
  (Object.keys(groups) as LinkGroup[]).map((group) => {
300
310
  const catalogLinks = groups[group].map((key) => {
311
+ if (key === 'rss' && options.rssFeedUrl === undefined
312
+ && options.overrides?.rss?.href === undefined) {
313
+ return undefined;
314
+ }
315
+ if ((group === 'quick' || group === 'community')
316
+ && options.removeLinks?.[group]?.includes(key)) {
317
+ return undefined;
318
+ }
301
319
  const override = options.overrides?.[key]
302
320
  ?? (key === 'blogPosts' ? options.overrides?.blog : undefined);
303
- return resolveLink(key, locale, defaultLinks[key], override);
321
+ const definition = key === 'rss'
322
+ ? { ...defaultLinks[key], href: options.rssFeedUrl ?? '' }
323
+ : defaultLinks[key];
324
+ return resolveLink(key, locale, definition, override);
304
325
  });
305
326
  const extraLinks = (options.extraLinks?.[group] ?? []).map((entry, index) => {
306
327
  const definition: LinkDefinition = {
@@ -308,42 +329,62 @@ export function resolveSiteLinks(localeInput?: string | null, options: SiteLinks
308
329
  href: resolveLocalized(entry.href, locale, ''),
309
330
  external: entry.external,
310
331
  };
311
- return resolveLink(`${group}-custom-${index + 1}`, locale, definition);
332
+ const id = entry.id ?? `${group}-custom-${index + 1}`;
333
+ if (typeof id !== 'string' || !id.trim()) {
334
+ throw new TypeError('Extra link IDs must be non-empty strings.');
335
+ }
336
+ return resolveLink(id, locale, definition);
337
+ });
338
+ const links = [...catalogLinks, ...extraLinks].filter((link): link is SiteLink => link !== undefined);
339
+ if (group !== 'quick' && group !== 'community') return [group, links];
340
+
341
+ const ids = new Set<string>();
342
+ const urls = new Set<string>();
343
+ const uniqueLinks = links.filter((link) => {
344
+ const url = normalizeUrl(link.href);
345
+ if (ids.has(link.id) || urls.has(url)) return false;
346
+ ids.add(link.id);
347
+ urls.add(url);
348
+ return true;
312
349
  });
313
- return [group, [...catalogLinks, ...extraLinks]];
350
+ return [group, uniqueLinks];
314
351
  }),
315
352
  ) as Record<LinkGroup, SiteLink[]>;
316
353
 
317
- const renderedUrls = new Set(
318
- [...resolvedGroups.quick, ...resolvedGroups.community, ...resolvedGroups.filings]
319
- .map((link) => normalizeUrl(link.href)),
320
- );
354
+ const renderedUrls = new Set([...resolvedGroups.quick, ...resolvedGroups.community, ...resolvedGroups.filings]
355
+ .map((link) => normalizeUrl(link.href)));
321
356
  if (options.siteUrl) renderedUrls.add(normalizeUrl(options.siteUrl));
322
357
 
323
358
  const relatedIds = new Set<string>();
324
359
  const relatedUrls = new Set<string>();
325
- const relatedSites = (options.relatedSites ?? docsRelatedSites).flatMap((site) => {
326
- const href = site.supportsLocalePath
327
- ? new URL(`${locale}/`, site.url.endsWith('/') ? site.url : `${site.url}/`).toString()
328
- : site.url;
329
- const normalizedUrl = normalizeUrl(href);
330
- if (site.id === options.siteId || renderedUrls.has(normalizedUrl)
331
- || relatedIds.has(site.id) || relatedUrls.has(normalizedUrl)) {
332
- return [];
333
- }
334
- relatedIds.add(site.id);
335
- relatedUrls.add(normalizedUrl);
336
- return [{
337
- id: site.id,
338
- name: resolveLocalized(site.name, locale, site.id),
339
- description: site.description
340
- ? resolveLocalized(site.description, locale, '')
341
- : undefined,
342
- href,
343
- target: '_blank' as const,
344
- rel: 'noopener noreferrer' as const,
345
- }];
346
- });
360
+ const bundledOrReplacementSites = (options.relatedSites ?? docsRelatedSites)
361
+ .filter((site) => !options.removeLinks?.relatedSites?.includes(site.id));
362
+ const relatedSites = [...bundledOrReplacementSites, ...(options.extraLinks?.relatedSites ?? [])]
363
+ .flatMap((site) => {
364
+ if (typeof site.id !== 'string' || !site.id.trim()) {
365
+ throw new TypeError('Related site IDs must be non-empty strings.');
366
+ }
367
+ const href = site.supportsLocalePath
368
+ ? new URL(`${locale}/`, site.url.endsWith('/') ? site.url : `${site.url}/`).toString()
369
+ : site.url;
370
+ const normalizedUrl = normalizeUrl(href);
371
+ if (site.id === options.siteId || renderedUrls.has(normalizedUrl)
372
+ || relatedIds.has(site.id) || relatedUrls.has(normalizedUrl)) {
373
+ return [];
374
+ }
375
+ relatedIds.add(site.id);
376
+ relatedUrls.add(normalizedUrl);
377
+ return [{
378
+ id: site.id,
379
+ name: resolveLocalized(site.name, locale, site.id),
380
+ description: site.description
381
+ ? resolveLocalized(site.description, locale, '')
382
+ : undefined,
383
+ href,
384
+ target: '_blank' as const,
385
+ rel: 'noopener noreferrer' as const,
386
+ }];
387
+ });
347
388
 
348
389
  return {
349
390
  locale,