@terrafirmagreg-team/kit 0.1.0 → 1.2.0

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.
Files changed (81) hide show
  1. package/package.json +15 -27
  2. package/src/components/blocks/nav-title.tsx +22 -25
  3. package/src/components/outline/dropdown.tsx +3 -2
  4. package/src/components/outline/index.ts +2 -1
  5. package/src/components/outline/list.tsx +3 -2
  6. package/src/components/outline/outline.tsx +4 -3
  7. package/src/components/outline/popover.tsx +3 -2
  8. package/src/components/page-actions.tsx +10 -9
  9. package/src/components/relative-link.tsx +1 -0
  10. package/src/components/toc/base.tsx +2 -1
  11. package/src/components/toc/block.tsx +1 -0
  12. package/src/components/toc/clerk.tsx +2 -1
  13. package/src/components/toc/default.tsx +3 -2
  14. package/src/components/version-menu.tsx +19 -17
  15. package/src/components/views/dataset.tsx +11 -2
  16. package/src/components/views/section.tsx +88 -44
  17. package/src/components/workspace/content.tsx +1 -0
  18. package/src/components/workspace/index.ts +1 -0
  19. package/src/components/workspace/pagination.tsx +2 -1
  20. package/src/components/workspace/workspace.tsx +1 -0
  21. package/src/hooks/use-catalog.ts +1 -0
  22. package/src/hooks/use-current-dataset.ts +15 -5
  23. package/src/hooks/use-dataset-accent.ts +1 -0
  24. package/src/hooks/use-nav-options.ts +20 -17
  25. package/src/hooks/use-section-names.ts +40 -0
  26. package/src/hooks/{toc.ts → use-toc.ts} +1 -0
  27. package/src/hooks/use-version-pref.ts +1 -0
  28. package/src/hooks/use-versioned-dataset.ts +21 -0
  29. package/src/lib/breadcrumb.ts +3 -16
  30. package/src/lib/config.ts +30 -28
  31. package/src/lib/dataset-links.ts +1 -0
  32. package/src/lib/datasets.ts +98 -68
  33. package/src/lib/landing.ts +4 -2
  34. package/src/lib/layout.ts +1 -0
  35. package/src/lib/locale/renderer.ts +49 -0
  36. package/src/lib/order.ts +1 -0
  37. package/src/lib/page.ts +3 -2
  38. package/src/lib/pin-tree.ts +11 -8
  39. package/src/lib/preview/lookup.ts +8 -5
  40. package/src/lib/section/schema.ts +2 -6
  41. package/src/lib/section/source.ts +59 -22
  42. package/src/lib/sources.ts +1 -0
  43. package/src/lib/tree.ts +1 -0
  44. package/src/lib/urls.ts +1 -0
  45. package/src/locales/en_us.json +7 -10
  46. package/src/locales/ru_ru.json +7 -10
  47. package/src/locales.ts +1 -0
  48. package/src/types/embeds.ts +54 -0
  49. package/src/types/index.ts +13 -9
  50. package/src/{components/outline/types.ts → types/outline.ts} +1 -0
  51. package/src/components/docs-image.tsx +0 -49
  52. package/src/components/search/dialog.tsx +0 -145
  53. package/src/lib/legacy.ts +0 -18
  54. package/src/lib/location.ts +0 -63
  55. package/src/lib/markdown/dom.ts +0 -90
  56. package/src/lib/markdown/index.ts +0 -13
  57. package/src/lib/markdown/providers.ts +0 -25
  58. package/src/lib/mdx/accordions.ts +0 -93
  59. package/src/lib/mdx/banner.ts +0 -52
  60. package/src/lib/mdx/directive-utils.ts +0 -125
  61. package/src/lib/mdx/directives.ts +0 -141
  62. package/src/lib/mdx/game-export.ts +0 -44
  63. package/src/lib/mdx/html-style.ts +0 -135
  64. package/src/lib/mdx/preview.ts +0 -252
  65. package/src/lib/mdx/references.ts +0 -56
  66. package/src/lib/mdx/tabs.ts +0 -80
  67. package/src/lib/mdx-components.tsx +0 -139
  68. package/src/lib/pack-assets.ts +0 -45
  69. package/src/lib/preview/index.ts +0 -8
  70. package/src/lib/preview/og.tsx +0 -115
  71. package/src/lib/preview/resolvers.ts +0 -98
  72. package/src/lib/search/config.ts +0 -13
  73. package/src/lib/search/filters.ts +0 -22
  74. package/src/lib/search/index.ts +0 -19
  75. package/src/lib/search/indexes.ts +0 -43
  76. package/src/lib/search/merge.ts +0 -22
  77. package/src/lib/search/server.ts +0 -18
  78. package/src/lib/search/sources.ts +0 -19
  79. package/src/lib/search/structured.ts +0 -23
  80. package/src/types/mdx.ts +0 -55
  81. package/src/types/turndown-plugin-gfm.d.ts +0 -9
@@ -13,9 +13,11 @@ import { langContent } from '@terrafirmagreg-team/translate/server';
13
13
  import { loadLocaleRegistry } from '@terrafirmagreg-team/translate/server';
14
14
  import { readPageSource, translationState } from '@terrafirmagreg-team/translate/server';
15
15
  import { resolvePage, type SectionSource } from '@terrafirmagreg-team/kit/lib/page';
16
+ import { localeLoader } from '@terrafirmagreg-team/kit/lib/locale/renderer';
16
17
  import { findNeighbour } from 'fumadocs-core/page-tree';
17
18
  import type {
18
19
  DatasetCatalog,
20
+ SectionKey,
19
21
  SectionPage,
20
22
  SectionTrees,
21
23
  } from '@terrafirmagreg-team/kit/types';
@@ -27,6 +29,7 @@ import type {
27
29
  ComponentType,
28
30
  ReactNode,
29
31
  } from 'react';
32
+ import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
30
33
 
31
34
  export interface SectionWorkspaceProps {
32
35
  page: SectionPage;
@@ -39,7 +42,7 @@ export interface SectionWorkspaceProps {
39
42
  history?: boolean;
40
43
  }
41
44
 
42
- export function SectionView({
45
+ export function renderSectionWorkspace({
43
46
  source,
44
47
  slug,
45
48
  contentDir,
@@ -47,6 +50,8 @@ export function SectionView({
47
50
  catalog,
48
51
  history,
49
52
  workspace: Workspace,
53
+ page,
54
+ lang,
50
55
  }: {
51
56
  source: SectionSource;
52
57
  slug: string[] | undefined;
@@ -55,54 +60,93 @@ export function SectionView({
55
60
  catalog: DatasetCatalog;
56
61
  history?: boolean;
57
62
  workspace: ComponentType<SectionWorkspaceProps>;
63
+ page: SectionPage;
64
+ lang: Lang;
58
65
  }) {
59
66
  const { defaultLanguage } = loadLocaleRegistry();
67
+ let copied: 'untranslated' | 'partial' | undefined;
68
+ if (lang !== defaultLanguage) {
69
+ const fallback = resolvePage(source, slug, defaultLanguage)?.page as
70
+ | SectionPage
71
+ | undefined;
72
+ if (fallback && fallback !== page) {
73
+ const own = readPageSource(contentDir, page.path);
74
+ const original = readPageSource(contentDir, fallback.path);
75
+ if (own && original) {
76
+ const state = translationState(own, original);
77
+ if (state !== 'ok') copied = state;
78
+ }
79
+ }
80
+ }
81
+ const crumbs = sectionBreadcrumbItems(source, page, lang);
82
+ const datasetId = page.url.split('/').filter(Boolean)[1];
83
+ const tree = datasetId ? trees?.[lang]?.[datasetId] : undefined;
84
+ const neighbours = tree ? findNeighbour(tree, page.url) : undefined;
85
+ return (
86
+ <Workspace
87
+ page={page}
88
+ crumbs={crumbs}
89
+ footer={neighbours}
90
+ previews={sectionBreadcrumbPreviews(crumbs, lang, catalog)}
91
+ link={createRelativeLink(
92
+ source as LoaderOutput<LoaderConfig>,
93
+ page as unknown as Page,
94
+ (props) => (
95
+ <LinkPreview
96
+ preview={lookupPagePreview(catalog, props.href, lang)}
97
+ {...props}
98
+ />
99
+ ),
100
+ )}
101
+ contentDir={contentDir}
102
+ copied={copied}
103
+ history={history}
104
+ />
105
+ );
106
+ }
107
+
108
+ export async function SectionView({
109
+ section,
110
+ getSource,
111
+ slug,
112
+ contentDir,
113
+ trees,
114
+ catalog,
115
+ history,
116
+ workspace,
117
+ }: {
118
+ section: SectionKey;
119
+ getSource: () => Promise<SectionSource>;
120
+ slug: string[] | undefined;
121
+ contentDir: string;
122
+ trees?: SectionTrees;
123
+ catalog: DatasetCatalog;
124
+ history?: boolean;
125
+ workspace: ComponentType<SectionWorkspaceProps>;
126
+ }) {
127
+ const source = await getSource();
60
128
  const content = langContent(
61
129
  slug,
62
130
  (s, l) => resolvePage(source, s, l),
63
- (page, lang) => {
64
- const self = page as SectionPage;
65
- let copied: 'untranslated' | 'partial' | undefined;
66
- if (lang !== defaultLanguage) {
67
- const fallback = resolvePage(source, slug, defaultLanguage)?.page as
68
- | SectionPage
69
- | undefined;
70
- if (fallback && fallback !== self) {
71
- const own = readPageSource(contentDir, self.path);
72
- const original = readPageSource(contentDir, fallback.path);
73
- if (own && original) {
74
- const state = translationState(own, original);
75
- if (state !== 'ok') copied = state;
76
- }
77
- }
78
- }
79
- const crumbs = sectionBreadcrumbItems(source, self, lang);
80
- const datasetId = self.url.split('/').filter(Boolean)[1];
81
- const tree = datasetId ? trees?.[lang]?.[datasetId] : undefined;
82
- const neighbours = tree ? findNeighbour(tree, self.url) : undefined;
83
- return (
84
- <Workspace
85
- page={self}
86
- crumbs={crumbs}
87
- footer={neighbours}
88
- previews={sectionBreadcrumbPreviews(crumbs, lang, catalog)}
89
- link={createRelativeLink(
90
- source as LoaderOutput<LoaderConfig>,
91
- page as Page,
92
- (props) => (
93
- <LinkPreview
94
- preview={lookupPagePreview(catalog, props.href, lang)}
95
- {...props}
96
- />
97
- ),
98
- )}
99
- contentDir={contentDir}
100
- copied={copied}
101
- history={history}
102
- />
103
- );
104
- },
131
+ (page, lang) =>
132
+ renderSectionWorkspace({
133
+ source,
134
+ slug,
135
+ contentDir,
136
+ trees,
137
+ catalog,
138
+ history,
139
+ workspace,
140
+ page: page as SectionPage,
141
+ lang,
142
+ }),
105
143
  );
106
144
  if (!content) notFound();
107
- return <LangContentSwitch content={content} />;
145
+ return (
146
+ <LangContentSwitch
147
+ content={content}
148
+ loadLang={localeLoader()?.bind(null, { section, slug })}
149
+ />
150
+ );
108
151
  }
152
+
@@ -51,3 +51,4 @@ export function WorkspaceFooter({
51
51
  />
52
52
  );
53
53
  }
54
+
@@ -10,3 +10,4 @@ export {
10
10
  WorkspaceFooter,
11
11
  } from './content';
12
12
  export { WorkspacePagination } from './pagination';
13
+
@@ -62,7 +62,7 @@ function WorkspacePaginationItem({
62
62
  {!next && (
63
63
  <Icon className="size-3.5 transition-transform group-hover:-translate-x-0.5 rtl:rotate-180" />
64
64
  )}
65
- {next ? t('ui.next_page') : t('ui.prev_page')}
65
+ {next ? t('Next Page', { note: 'ui.next_page' }) : t('Previous Page', { note: 'ui.prev_page' })}
66
66
  {next && (
67
67
  <Icon className="size-3.5 transition-transform group-hover:translate-x-0.5 rtl:rotate-180" />
68
68
  )}
@@ -71,3 +71,4 @@ function WorkspacePaginationItem({
71
71
  </Link>
72
72
  );
73
73
  }
74
+
@@ -146,3 +146,4 @@ export function Workspace({
146
146
  </TOCProvider>
147
147
  );
148
148
  }
149
+
@@ -9,3 +9,4 @@ export function useSectionCatalog(): DatasetCatalog {
9
9
  if (!catalog) throw new Error('SectionCatalogContext is not provided');
10
10
  return catalog;
11
11
  }
12
+
@@ -1,14 +1,24 @@
1
1
  'use client';
2
+ import { useMemo } from 'react';
2
3
  import { usePathname } from 'next/navigation';
3
- import { parseDatasetRef, parseSectionLocation } from '@terrafirmagreg-team/kit/lib/location';
4
+ import { useLocationSearchParams } from '@terrafirmagreg-team/ui/hooks/page/use-location-search';
5
+ import {
6
+ type DatasetRef,
7
+ parseSectionLocation,
8
+ resolveDatasetRef,
9
+ } from '@terrafirmagreg-team/sites/location';
4
10
  import type { SectionDataset } from '@terrafirmagreg-team/kit/types';
5
11
 
6
12
  export function useCurrentDataset(
7
13
  datasets: readonly SectionDataset[],
8
- ): string | undefined {
14
+ ): DatasetRef | undefined {
9
15
  const pathname = usePathname();
16
+ const versionParam = useLocationSearchParams().get('version') ?? undefined;
10
17
  const { dataset } = parseSectionLocation(pathname);
11
- if (!dataset) return undefined;
12
- const { id } = parseDatasetRef(dataset);
13
- return datasets.some((row) => row.id === id) ? dataset : undefined;
18
+ const meta = datasets.find((row) => row.id === dataset);
19
+ return useMemo(
20
+ () => dataset && meta ? resolveDatasetRef(dataset, versionParam, meta).ref : undefined,
21
+ [dataset, versionParam, meta],
22
+ );
14
23
  }
24
+
@@ -11,3 +11,4 @@ export function useDatasetAccent(accent?: string) {
11
11
  };
12
12
  }, [accent]);
13
13
  }
14
+
@@ -2,14 +2,14 @@
2
2
  import { type ReactNode, createElement, useMemo } from 'react';
3
3
  import { DynamicIcon, type IconName } from 'lucide-react/dynamic';
4
4
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
5
- import { sectionConfig } from '@terrafirmagreg-team/kit/lib/config';
5
+ import { sectionTabs } from '@terrafirmagreg-team/sites/sections';
6
6
  import {
7
- datasetRef,
8
7
  isDumpSection,
9
- parseDatasetRef,
8
+ resolveDatasetRef,
10
9
  sectionUrl,
11
- } from '@terrafirmagreg-team/kit/lib/location';
10
+ } from '@terrafirmagreg-team/sites/location';
12
11
  import { useCurrentDataset } from '@terrafirmagreg-team/kit/hooks/use-current-dataset';
12
+ import { useSectionNames } from '@terrafirmagreg-team/kit/hooks/use-section-names';
13
13
  import { useVersionPreference } from '@terrafirmagreg-team/kit/hooks/use-version-pref';
14
14
  import type { LeftSidebarScope } from '@terrafirmagreg-team/kit/types';
15
15
  import type {
@@ -30,31 +30,33 @@ export function useNavOptions({ section, catalog }: LeftSidebarScope): {
30
30
  dataset: SwitcherProps;
31
31
  } {
32
32
  const t = useTranslations();
33
+ const sectionNames = useSectionNames();
33
34
  const current = useCurrentDataset(catalog[section]);
34
- const baseId = current ? parseDatasetRef(current).id : undefined;
35
- const pinned = current ? parseDatasetRef(current).version : undefined;
35
+ const baseId = current ? current.id : undefined;
36
+ const pinned = current ? current.version : undefined;
36
37
  const preferred = useVersionPreference(baseId);
37
38
  const version = pinned ?? preferred;
38
39
 
39
40
  const sectionOptions: SwitcherOption[] = useMemo(
40
41
  () =>
41
- sectionConfig().sectionTabs.map(({ key, icon: Icon }) => {
42
+ sectionTabs.map(({ key, icon: Icon }) => {
42
43
  const meta = baseId
43
44
  ? catalog[key].find(({ id }) => id === baseId)
44
45
  : undefined;
45
- const pinnedRef =
46
- isDumpSection(key) && version && meta?.versions?.includes(version)
47
- ? datasetRef(baseId!, version)
48
- : baseId;
46
+ const pinned =
47
+ isDumpSection(key) && baseId
48
+ ? resolveDatasetRef(baseId, version, meta).pinned
49
+ : undefined;
50
+ const suffix = pinned ? `?version=${encodeURIComponent(pinned)}` : '';
49
51
  return {
50
52
  value: key,
51
- title: t(`sections.${key}`),
52
- description: t(`sections.desc.${key}`),
53
+ title: sectionNames[key]?.title ?? key,
54
+ description: sectionNames[key]?.desc,
53
55
  icon: Icon ? createElement(Icon) : undefined,
54
- url: meta ? sectionUrl(key, pinnedRef) : sectionUrl(key),
56
+ url: meta ? `${sectionUrl(key, baseId)}${suffix}` : sectionUrl(key),
55
57
  };
56
58
  }),
57
- [t, baseId, version, catalog],
59
+ [sectionNames, baseId, version, catalog],
58
60
  );
59
61
 
60
62
  const datasetOptions: SwitcherOption[] = useMemo(
@@ -73,12 +75,13 @@ export function useNavOptions({ section, catalog }: LeftSidebarScope): {
73
75
  section: {
74
76
  options: sectionOptions,
75
77
  selected: section,
76
- placeholder: t('ui.select_section'),
78
+ placeholder: t('Select section', { note: 'ui.select_section' }),
77
79
  },
78
80
  dataset: {
79
81
  options: datasetOptions,
80
82
  selected: baseId,
81
- placeholder: t('ui.select_dataset'),
83
+ placeholder: t('Select dataset', { note: 'ui.select_dataset' }),
82
84
  },
83
85
  };
84
86
  }
87
+
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+ import { useMemo } from 'react';
3
+ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
4
+
5
+ export function useSectionNames(): Record<
6
+ string,
7
+ { title: string; desc: string }
8
+ > {
9
+ const t = useTranslations();
10
+ return useMemo(
11
+ () => ({
12
+ docs: {
13
+ title: t('Docs', { note: 'sections.docs' }),
14
+ desc: t('Documentation', { note: 'sections.desc.docs' }),
15
+ },
16
+ changelog: {
17
+ title: t('Changelog', { note: 'sections.changelog' }),
18
+ desc: t('Release history and version notes', {
19
+ note: 'sections.desc.changelog',
20
+ }),
21
+ },
22
+ recipe: {
23
+ title: t('Recipes', { note: 'sections.recipe' }),
24
+ desc: t('Crafting and processing recipes', {
25
+ note: 'sections.desc.recipe',
26
+ }),
27
+ },
28
+ quest: {
29
+ title: t('Quests', { note: 'sections.quest' }),
30
+ desc: t('Quest chapters and objectives', { note: 'sections.desc.quest' }),
31
+ },
32
+ guide: {
33
+ title: t('Guide', { note: 'sections.guide' }),
34
+ desc: t('Gameplay guide', { note: 'sections.desc.guide' }),
35
+ },
36
+ }),
37
+ [t],
38
+ );
39
+ }
40
+
@@ -7,3 +7,4 @@ export const TOCContext = createContext<TOCItemType[]>([]);
7
7
  export function useTOCItems(): TOCItemType[] {
8
8
  return use(TOCContext);
9
9
  }
10
+
@@ -28,3 +28,4 @@ export function useVersionPreference(datasetId?: string): string | undefined {
28
28
  () => undefined,
29
29
  );
30
30
  }
31
+
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import { useLocationSearchParams } from '@terrafirmagreg-team/ui/hooks/page/use-location-search';
3
+ import {
4
+ resolveDatasetRef,
5
+ type DatasetVersionInfo,
6
+ type DatasetRef,
7
+ } from '@terrafirmagreg-team/sites/location';
8
+
9
+ const refs = new Map<string, DatasetRef | undefined>();
10
+
11
+ export function useVersionedDataset(
12
+ id: string | undefined,
13
+ info?: DatasetVersionInfo,
14
+ ): DatasetRef | undefined {
15
+ const version = useLocationSearchParams().get('version') ?? undefined;
16
+ const key = `${id ?? ''} ${version ?? ''} ${info?.latest ?? ''} ${info?.versions?.join(',') ?? ''}`;
17
+ if (!refs.has(key))
18
+ refs.set(key, id ? resolveDatasetRef(id, version, info).ref : undefined);
19
+ return refs.get(key);
20
+ }
21
+
@@ -1,5 +1,6 @@
1
1
  import 'server-only';
2
2
  import type * as PageTree from 'fumadocs-core/page-tree';
3
+ import { searchPath } from 'fumadocs-core/breadcrumb';
3
4
  import { stripLangFromUrl } from '@terrafirmagreg-team/translate/lib/slug';
4
5
  import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
5
6
  import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
@@ -13,21 +14,6 @@ import type { DatasetCatalog, SectionPage } from '@terrafirmagreg-team/kit/types
13
14
 
14
15
  export type SectionCrumb = { name: string; url?: string };
15
16
 
16
- function findTrail(
17
- nodes: readonly PageTree.Node[],
18
- url: string,
19
- ): PageTree.Node[] | undefined {
20
- for (const node of nodes) {
21
- if (node.type === 'page' && node.url === url) return [node];
22
- if (node.type === 'folder') {
23
- if (node.index?.url === url) return [node];
24
- const inner = findTrail(node.children, url);
25
- if (inner) return [node, ...inner];
26
- }
27
- }
28
- return undefined;
29
- }
30
-
31
17
  function nodeName(name: unknown): string {
32
18
  return typeof name === 'string' ? name : '';
33
19
  }
@@ -52,7 +38,7 @@ export function sectionBreadcrumbItems(
52
38
  const pageUrl = stripLangFromUrl(page.url, languages);
53
39
  if (folder.index?.url === pageUrl) return [];
54
40
 
55
- const trail = findTrail(folder.children, pageUrl);
41
+ const trail = searchPath(folder.children, pageUrl);
56
42
  const items: SectionCrumb[] = [];
57
43
  for (const node of trail?.slice(0, -1) ?? []) {
58
44
  if (node.type === 'folder') {
@@ -76,3 +62,4 @@ export function sectionBreadcrumbPreviews(
76
62
  }
77
63
  return previews;
78
64
  }
65
+
package/src/lib/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ComponentType } from 'react';
1
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
2
2
  import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
3
3
  import type {
4
4
  SectionKey,
@@ -6,13 +6,17 @@ import type {
6
6
  } from '@terrafirmagreg-team/kit/types';
7
7
 
8
8
  export type SectionDump = {
9
- manifest: { languages?: readonly string[] };
10
- };
11
-
12
- export type SectionLegacyRoute = {
13
- dataset: string;
14
- path?: string;
15
- overrides?: Record<string, string>;
9
+ manifest: {
10
+ languages?: readonly string[];
11
+ pack?: { version?: string | null } | null;
12
+ };
13
+ mods: readonly {
14
+ namespace: string;
15
+ books: readonly unknown[];
16
+ questData: string | null;
17
+ recipeCategories: string | null;
18
+ recipeIndex: string | null;
19
+ }[];
16
20
  };
17
21
 
18
22
  export type SectionConfigInput = {
@@ -21,15 +25,6 @@ export type SectionConfigInput = {
21
25
  siteUrl: string;
22
26
  previewRoute: string;
23
27
  docsRoute: string;
24
- legacyRoutes: Record<string, SectionLegacyRoute>;
25
-
26
- sectionTabs: readonly {
27
- key: SectionKey;
28
- url: string;
29
- icon?: ComponentType<{ className?: string }>;
30
- }[];
31
-
32
- dumpSections?: readonly SectionKey[];
33
28
 
34
29
  datasetConfig: Record<
35
30
  string,
@@ -39,6 +34,8 @@ export type SectionConfigInput = {
39
34
 
40
35
  repo?: `${string}/${string}`;
41
36
 
37
+ discussions?: { repoId: string; categoryId: string };
38
+
42
39
  crowdin?: string;
43
40
 
44
41
  links?: readonly {
@@ -54,33 +51,37 @@ export type SectionConfigInput = {
54
51
  sources: Partial<Record<SectionKey, SectionSourceEntry>>;
55
52
 
56
53
  dump?: {
57
- discoverDatasets(): Promise<string[]>;
58
- loadDataset(id: string): Promise<SectionDump | null | undefined>;
54
+ discoverDatasets(): Promise<DatasetRef[]>;
55
+ loadDataset(ref: DatasetRef): Promise<SectionDump | null | undefined>;
59
56
  hasContent: Record<string, (dump: SectionDump) => boolean>;
60
57
  };
61
58
  };
62
59
 
63
- let config: SectionConfigInput | undefined;
60
+ const registry = ((globalThis as Record<symbol, unknown>)[
61
+ Symbol.for('terrafirmagreg.config')
62
+ ] ??= new Map<string, unknown>()) as Map<string, unknown>;
64
63
 
65
64
  export function registerSectionConfig(
66
65
  input: Partial<SectionConfigInput>,
67
66
  ): void {
68
- config = { ...config, ...input } as SectionConfigInput;
67
+ registry.set('section', {
68
+ ...(registry.get('section') as SectionConfigInput | undefined),
69
+ ...input,
70
+ });
69
71
  }
70
72
 
71
73
  export function sectionConfig(): SectionConfigInput {
74
+ const config = registry.get('section') as SectionConfigInput | undefined;
72
75
  if (!config) {
73
76
  throw new Error(
74
- 'Section config is not registered — call registerSectionConfig() at app startup.',
77
+ 'Section config is not registered: call registerSectionConfig() at app startup.',
75
78
  );
76
79
  }
77
80
  return config;
78
81
  }
79
82
 
80
83
  export type ClientDumpLoader = {
81
- loadDump(dataset: string): Promise<{
82
- manifest: { pack?: { version?: string | null } | null };
83
- } | null | undefined>;
84
+ loadDump(dataset: DatasetRef): Promise<SectionDump | null | undefined>;
84
85
  loadVersions(
85
86
  datasetId: string,
86
87
  ): Promise<{ latest?: string; versions: string[] } | null>;
@@ -92,18 +93,19 @@ export const {
92
93
  } = createConfig<ClientDumpLoader>('ClientDump');
93
94
 
94
95
  export function createConfig<T>(label: string) {
95
- let config: T | undefined;
96
96
  return {
97
97
  register(input: T) {
98
- config = input;
98
+ registry.set(label, input);
99
99
  },
100
100
  get(): T {
101
+ const config = registry.get(label) as T | undefined;
101
102
  if (!config) {
102
103
  throw new Error(
103
- `${label} config is not registered — call register${label}Config() at app startup.`,
104
+ `${label} config is not registered: call register${label}Config() at app startup.`,
104
105
  );
105
106
  }
106
107
  return config;
107
108
  },
108
109
  };
109
110
  }
111
+
@@ -28,3 +28,4 @@ export const datasetLink = {
28
28
  icon: 'discord',
29
29
  }),
30
30
  };
31
+