@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 +1 -1
- package/promotions.ts +3 -3
- package/related-sites.json +44 -0
- package/site-links.ts +82 -41
package/package.json
CHANGED
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
|
+
}
|
package/related-sites.json
CHANGED
|
@@ -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': '
|
|
87
|
-
'ja-JP': '
|
|
88
|
-
'de-DE': '
|
|
89
|
-
'es-ES': 'Descargar
|
|
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': '
|
|
159
|
-
'
|
|
160
|
-
'
|
|
161
|
-
'
|
|
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:
|
|
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', '
|
|
219
|
-
community: ['github', 'discord', 'issueFeedback', 'contactEmail', 'qqGroup'
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
350
|
+
return [group, uniqueLinks];
|
|
314
351
|
}),
|
|
315
352
|
) as Record<LinkGroup, SiteLink[]>;
|
|
316
353
|
|
|
317
|
-
const renderedUrls = new Set(
|
|
318
|
-
|
|
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
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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,
|