@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
@@ -3,8 +3,10 @@ import { dynamicIconImports } from 'lucide-react/dynamic';
3
3
  import { sectionConfig } from './config';
4
4
  import { sections } from './sources';
5
5
  import { datasetId } from './tree';
6
+ import { createAsyncCache } from '@terrafirmagreg-team/data/lib/async-cache';
6
7
  import { loadLocaleRegistry, sortLangCodes } from '@terrafirmagreg-team/translate/server';
7
- import { parseDatasetRef } from './location';
8
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
9
+ import { sectionTabs } from '@terrafirmagreg-team/sites/sections';
8
10
  import type {
9
11
  DatasetCatalog,
10
12
  SectionDataset,
@@ -14,42 +16,53 @@ import type { SectionDump } from './config';
14
16
  import type { IconName } from 'lucide-react/dynamic';
15
17
  import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
16
18
 
17
- const sectionMeta = new Map<SectionKey, Map<string, Record<string, unknown>>>();
19
+ const sectionMeta = new Map<
20
+ SectionKey,
21
+ Promise<Map<string, Record<string, unknown>>>
22
+ >();
18
23
 
19
24
  function staticSectionMeta(
20
25
  section: SectionKey,
21
- ): Map<string, Record<string, unknown>> {
26
+ ): Promise<Map<string, Record<string, unknown>>> {
22
27
  let meta = sectionMeta.get(section);
23
28
  if (meta) return meta;
24
- meta = new Map();
25
- const source = sections[section]?.source;
26
- if (source) {
27
- for (const node of source.getPageTree().children) {
28
- if (node.type !== 'folder') continue;
29
- const id = datasetId(node);
30
- const data = id
31
- ? (source.getNodeMeta?.(node) as { data?: Record<string, unknown> })
32
- ?.data
33
- : undefined;
34
- if (id && data) meta.set(id, data);
29
+ meta = (async () => {
30
+ const map = new Map<string, Record<string, unknown>>();
31
+ const source = await sections[section]?.getSource();
32
+ if (source) {
33
+ for (const node of source.getPageTree().children) {
34
+ if (node.type !== 'folder') continue;
35
+ const id = datasetId(node);
36
+ const data = id
37
+ ? (source.getNodeMeta?.(node) as { data?: Record<string, unknown> })
38
+ ?.data
39
+ : undefined;
40
+ if (id && data) map.set(id, data);
41
+ }
35
42
  }
36
- }
43
+ return map;
44
+ })();
37
45
  sectionMeta.set(section, meta);
46
+ meta.catch(() => {
47
+ if (sectionMeta.get(section) === meta) sectionMeta.delete(section);
48
+ });
38
49
  return meta;
39
50
  }
40
51
 
41
- function contentSectionDatasets(section: SectionKey): SectionDataset[] {
42
- const source = sections[section]?.source;
52
+ async function contentSectionDatasets(
53
+ section: SectionKey,
54
+ ): Promise<SectionDataset[]> {
55
+ const source = await sections[section]?.getSource();
43
56
  if (!source) return [];
44
57
  const languages = new Map<string, Set<string>>();
45
58
  for (const page of source.getPages()) {
46
- const [dataset, lang] = page.slugs;
59
+ const [dataset, lang] = page.path.split('/');
47
60
  if (!dataset || !lang) continue;
48
61
  let set = languages.get(dataset);
49
62
  if (!set) languages.set(dataset, (set = new Set()));
50
63
  set.add(lang);
51
64
  }
52
- const meta = staticSectionMeta(section);
65
+ const meta = await staticSectionMeta(section);
53
66
  return [...languages.keys()].map((id) => ({
54
67
  id,
55
68
  languages: datasetLanguages([...(languages.get(id) ?? [])]),
@@ -58,11 +71,11 @@ function contentSectionDatasets(section: SectionKey): SectionDataset[] {
58
71
  }));
59
72
  }
60
73
 
61
- function datasetMeta(
74
+ async function datasetMeta(
62
75
  section: SectionKey,
63
76
  id: string,
64
- ): Omit<SectionDataset, 'id' | 'languages'> {
65
- const meta = staticSectionMeta(section).get(id);
77
+ ): Promise<Omit<SectionDataset, 'id' | 'languages'>> {
78
+ const meta = (await staticSectionMeta(section)).get(id);
66
79
  return meta ? metaFields(meta) : {};
67
80
  }
68
81
 
@@ -89,7 +102,23 @@ function datasetLanguages(languages: readonly string[]): Lang[] {
89
102
  return [loadLocaleRegistry().defaultLanguage as Lang];
90
103
  }
91
104
 
92
- const bySection = new Map<SectionKey, Promise<SectionDataset[]>>();
105
+ const DUMP_DATASETS_TTL = 60_000;
106
+
107
+ const dumpSectionDatasets = createAsyncCache(
108
+ async (section: SectionKey, hasContent: (dump: SectionDump) => boolean) =>
109
+ dumpDatasets(section, hasContent),
110
+ (section) => section,
111
+ DUMP_DATASETS_TTL,
112
+ );
113
+
114
+ const contentSectionDatasetsCached = createAsyncCache(
115
+ async (section: SectionKey): Promise<SectionDataset[]> => {
116
+ const rows = await contentSectionDatasets(section);
117
+ rows.sort(datasetOrder);
118
+ return rows;
119
+ },
120
+ (section) => section,
121
+ );
93
122
 
94
123
  function datasetOrder(a: SectionDataset, b: SectionDataset): number {
95
124
  const cfg = sectionConfig().datasetConfig;
@@ -110,6 +139,7 @@ function compareVersions(a: string, b: string): number {
110
139
  }
111
140
 
112
141
  async function dumpDatasets(
142
+ section: SectionKey,
113
143
  hasContent: (dump: SectionDump) => boolean,
114
144
  ): Promise<SectionDataset[]> {
115
145
  const dump = sectionConfig().dump;
@@ -117,17 +147,18 @@ async function dumpDatasets(
117
147
  const refs = await dump.discoverDatasets();
118
148
  const versions = new Map<string, string[]>();
119
149
  for (const ref of refs) {
120
- const { id, version } = parseDatasetRef(ref);
150
+ const { id, version } = ref;
121
151
  if (version) {
122
152
  const list = versions.get(id) ?? [];
123
153
  list.push(version);
124
154
  versions.set(id, list);
125
155
  }
126
156
  }
127
- const ids = refs.filter((ref) => !parseDatasetRef(ref).version);
157
+ const ids = refs.filter((ref) => !ref.version);
128
158
  const usable = await Promise.all(
129
- ids.map(async (id) => {
130
- const index = await dump.loadDataset(id);
159
+ ids.map(async (ref) => {
160
+ const { id } = ref;
161
+ const index = await dump.loadDataset(ref);
131
162
  if (!index || !hasContent(index)) return null;
132
163
  return {
133
164
  id,
@@ -135,37 +166,34 @@ async function dumpDatasets(
135
166
  };
136
167
  }),
137
168
  );
138
- return usable
139
- .filter((row): row is { id: string; languages: Lang[] } => row !== null)
140
- .map(({ id, languages }) => ({
141
- id,
142
- languages,
143
- ...(versions.get(id)?.length
144
- ? { versions: versions.get(id)!.sort(compareVersions).reverse() }
145
- : {}),
146
- ...datasetMeta('docs', id),
147
- ...datasetAppearance(id),
148
- }))
149
- .sort(datasetOrder);
169
+ return (
170
+ await Promise.all(
171
+ usable
172
+ .filter((row): row is { id: string; languages: Lang[] } => row !== null)
173
+ .map(async ({ id, languages }) => ({
174
+ id,
175
+ languages,
176
+ ...(versions.get(id)?.length
177
+ ? { versions: versions.get(id)!.sort(compareVersions).reverse() }
178
+ : {}),
179
+ ...(await datasetMeta(section, id).then((meta) =>
180
+ Object.values(meta).some((value) => value !== undefined)
181
+ ? meta
182
+ : datasetMeta('docs', id),
183
+ )),
184
+ ...datasetAppearance(id),
185
+ })),
186
+ )
187
+ ).sort(datasetOrder);
150
188
  }
151
189
 
152
190
  export function sectionDatasets(
153
191
  section: SectionKey,
154
192
  ): Promise<SectionDataset[]> {
155
- let cached = bySection.get(section);
156
- if (!cached) {
157
- const hasContent = sectionConfig().dump?.hasContent[section];
158
- if (hasContent) {
159
- cached = dumpDatasets(hasContent);
160
- bySection.set(section, cached);
161
- return cached;
162
- }
163
- const rows = contentSectionDatasets(section);
164
- rows.sort(datasetOrder);
165
- cached = Promise.resolve(rows);
166
- bySection.set(section, cached);
167
- }
168
- return cached;
193
+ const hasContent = sectionConfig().dump?.hasContent[section];
194
+ return hasContent
195
+ ? dumpSectionDatasets(section, hasContent)
196
+ : contentSectionDatasetsCached(section);
169
197
  }
170
198
 
171
199
  export async function defaultDataset(
@@ -176,36 +204,38 @@ export async function defaultDataset(
176
204
 
177
205
  export function findDataset(
178
206
  datasets: readonly SectionDataset[],
179
- ref: string,
207
+ ref: DatasetRef,
180
208
  ): SectionDataset | undefined {
181
- const { id, version } = parseDatasetRef(ref);
209
+ const { id, version } = ref;
182
210
  const meta = datasets.find((row) => row.id === id);
183
211
  if (!meta) return undefined;
184
212
  if (version && !meta.versions?.includes(version)) return undefined;
185
213
  return meta;
186
214
  }
187
215
 
188
- export async function resolveDatasetRef(
216
+ export async function resolveDatasetMeta(
189
217
  section: SectionKey,
190
- ref: string,
218
+ ref: DatasetRef,
191
219
  ): Promise<SectionDataset | undefined> {
192
220
  return findDataset(await sectionDatasets(section), ref);
193
221
  }
194
222
 
195
223
  export async function datasetSlugs(section: SectionKey): Promise<string[]> {
196
- return (await sectionDatasets(section)).flatMap(({ id, versions }) => [
197
- id,
198
- ...(versions ?? []).map((version) => `${id}@${version}`),
199
- ]);
224
+ return (await sectionDatasets(section)).map(({ id }) => id);
200
225
  }
201
226
 
202
- let catalogCached: Promise<DatasetCatalog> | undefined;
227
+ const datasetCatalogCached = createAsyncCache(
228
+ async (): Promise<DatasetCatalog> =>
229
+ Promise.all(
230
+ sectionTabs.map(
231
+ async ({ key }) => [key, await sectionDatasets(key)] as const,
232
+ ),
233
+ ).then((rows) => Object.fromEntries(rows) as DatasetCatalog),
234
+ () => 'catalog',
235
+ DUMP_DATASETS_TTL,
236
+ );
203
237
 
204
238
  export function datasetCatalog(): Promise<DatasetCatalog> {
205
- catalogCached ??= Promise.all(
206
- sectionConfig().sectionTabs.map(
207
- async ({ key }) => [key, await sectionDatasets(key)] as const,
208
- ),
209
- ).then((rows) => Object.fromEntries(rows) as DatasetCatalog);
210
- return catalogCached;
239
+ return datasetCatalogCached();
211
240
  }
241
+
@@ -16,16 +16,17 @@ export function getDatasetLanding(
16
16
  ): DatasetLanding | undefined {
17
17
  if (!slug || slug.length !== 1) return undefined;
18
18
  const [ref] = slug;
19
- const meta = findDataset(datasets, ref);
19
+ const meta = findDataset(datasets, { id: ref });
20
20
  if (!meta) return undefined;
21
21
  const { messages, defaultLanguage } = loadLocaleRegistry();
22
22
  const table = messages[lang] ?? messages[defaultLanguage] ?? {};
23
23
  return {
24
24
  title: table[`sections.${section}`] ?? section,
25
25
  description: table[`sections.desc.${section}`],
26
- dataset: ref,
26
+ dataset: { id: ref },
27
27
  datasetTitle: meta.title ?? meta.id,
28
28
  lang,
29
+ ...(meta.versions ? { versions: meta.versions } : {}),
29
30
  };
30
31
  }
31
32
 
@@ -44,3 +45,4 @@ export async function generateDatasetLandingParams(
44
45
  ): Promise<{ slug: string[] }[]> {
45
46
  return (await datasetSlugs(section)).map((ref) => ({ slug: [ref] }));
46
47
  }
48
+
package/src/lib/layout.ts CHANGED
@@ -13,3 +13,4 @@ export function baseOptions(): BaseLayoutProps {
13
13
  slots: { navTitle: NavTitle },
14
14
  };
15
15
  }
16
+
@@ -0,0 +1,49 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
3
+
4
+ export type LocaleScope = { section: string; slug?: string[] };
5
+
6
+ export type LocaleResolve = (
7
+ slug: string[] | undefined,
8
+ lang: Lang,
9
+ ) => Promise<{ page: unknown; isFallback: boolean } | undefined>;
10
+
11
+ export type LocaleRender = (
12
+ page: unknown,
13
+ slug: string[] | undefined,
14
+ lang: Lang,
15
+ ) => ReactNode | Promise<ReactNode>;
16
+
17
+ export type LocaleLoadResult = { node: ReactNode; missing: boolean } | null;
18
+
19
+ export type LocaleLoader = (
20
+ scope: LocaleScope,
21
+ lang: Lang,
22
+ ) => Promise<LocaleLoadResult>;
23
+
24
+ const renderers = new Map<
25
+ string,
26
+ { resolve: LocaleResolve; render: LocaleRender }
27
+ >();
28
+
29
+ export function registerLocaleRenderer(
30
+ section: string,
31
+ entry: { resolve: LocaleResolve; render: LocaleRender },
32
+ ): void {
33
+ renderers.set(section, entry);
34
+ }
35
+
36
+ export function localeRenderer(section: string) {
37
+ return renderers.get(section);
38
+ }
39
+
40
+ let loader: LocaleLoader | undefined;
41
+
42
+ export function registerLocaleLoader(next: LocaleLoader): void {
43
+ loader = next;
44
+ }
45
+
46
+ export function localeLoader(): LocaleLoader | undefined {
47
+ return loader;
48
+ }
49
+
package/src/lib/order.ts CHANGED
@@ -86,3 +86,4 @@ export function orderPlugin(): LoaderPlugin {
86
86
  },
87
87
  };
88
88
  }
89
+
package/src/lib/page.ts CHANGED
@@ -52,7 +52,7 @@ export async function generateDatasetParams(
52
52
  section: SectionKey,
53
53
  source: SectionSource,
54
54
  ): Promise<{ slug: string[] }[]> {
55
- const params = generateParams(source);
55
+ const params = generatePageParams(source);
56
56
  const seen = new Set(params.map((row) => row.slug.join('/')));
57
57
  for (const { id } of await sectionDatasets(section)) {
58
58
  if (!seen.has(id)) {
@@ -63,7 +63,7 @@ export async function generateDatasetParams(
63
63
  return params.length ? params : [{ slug: ['_'] }];
64
64
  }
65
65
 
66
- function generateParams(source: SectionSource): { slug: string[] }[] {
66
+ export function generatePageParams(source: SectionSource): { slug: string[] }[] {
67
67
  const languages = [...sectionConfig().knownLanguages];
68
68
  const seen = new Set<string>();
69
69
  const params: { slug: string[] }[] = [];
@@ -106,3 +106,4 @@ export async function sectionMetadata(
106
106
  },
107
107
  };
108
108
  }
109
+
@@ -1,11 +1,14 @@
1
1
  import type * as PageTree from 'fumadocs-core/page-tree';
2
2
 
3
- function pinUrl(url: string, from: string, to: string): string {
3
+ function pinUrl(url: string, from: string, version: string): string {
4
4
  const next = url[from.length];
5
- return url.startsWith(from) &&
6
- (next === undefined || next === '/' || next === '?' || next === '#')
7
- ? `${to}${url.slice(from.length)}`
8
- : url;
5
+ if (!url.startsWith(from) ||
6
+ (next !== undefined && next !== '/' && next !== '?' && next !== '#')) return url;
7
+ const [pathQuery, hash] = url.split('#');
8
+ const [path, query] = pathQuery.split('?');
9
+ const params = new URLSearchParams(query);
10
+ params.set('version', version);
11
+ return `${path}?${params}${hash === undefined ? '' : `#${hash}`}`;
9
12
  }
10
13
 
11
14
  export function pinTreeDataset(
@@ -15,16 +18,15 @@ export function pinTreeDataset(
15
18
  version: string,
16
19
  ): PageTree.Root {
17
20
  const from = `/${section}/${datasetId}`;
18
- const to = `${from}@${version}`;
19
21
  const pinNode = (node: PageTree.Node): PageTree.Node => {
20
22
  if (node.type === 'page') {
21
- return { ...node, url: pinUrl(node.url, from, to) };
23
+ return { ...node, url: pinUrl(node.url, from, version) };
22
24
  }
23
25
  if (node.type === 'folder') {
24
26
  return {
25
27
  ...node,
26
28
  index: node.index
27
- ? { ...node.index, url: pinUrl(node.index.url, from, to) }
29
+ ? { ...node.index, url: pinUrl(node.index.url, from, version) }
28
30
  : node.index,
29
31
  children: node.children.map(pinNode),
30
32
  };
@@ -33,3 +35,4 @@ export function pinTreeDataset(
33
35
  };
34
36
  return { ...tree, children: tree.children.map(pinNode) };
35
37
  }
38
+
@@ -1,10 +1,10 @@
1
1
  import 'server-only';
2
2
  import { resolveLang } from '@terrafirmagreg-team/translate/lib/code';
3
3
  import { loadLocaleRegistry } from '@terrafirmagreg-team/translate/server';
4
- import { sectionConfig } from '../config';
5
- import { isSectionKey } from '../location';
6
- import { resolvePage } from '../page';
7
- import { sections } from '../sources';
4
+ import { sectionConfig } from '@terrafirmagreg-team/kit/lib/config';
5
+ import { isSectionKey } from '@terrafirmagreg-team/sites/location';
6
+ import { resolvePage } from '@terrafirmagreg-team/kit/lib/page';
7
+ import { sections } from '@terrafirmagreg-team/kit/lib/sources';
8
8
  import type {
9
9
  DatasetCatalog,
10
10
  SectionKey,
@@ -37,7 +37,9 @@ export function lookupPagePreview(
37
37
  dataset?.languages ?? [...sectionConfig().knownLanguages],
38
38
  defaultLanguage,
39
39
  );
40
- const resolved = resolvePage(section.source, parts.slice(1), pageLang);
40
+ const source = section.getSourceSync?.();
41
+ if (!source) return undefined;
42
+ const resolved = resolvePage(source, parts.slice(1), pageLang);
41
43
  const page = resolved?.page as SectionPage | undefined;
42
44
  if (!page) return undefined;
43
45
  return {
@@ -46,3 +48,4 @@ export function lookupPagePreview(
46
48
  icon: dataset?.icon ? { type: 'icon', name: dataset.icon } : undefined,
47
49
  };
48
50
  }
51
+
@@ -1,11 +1,7 @@
1
1
  import { metaSchema, pageSchema } from 'fumadocs-core/source/schema';
2
2
  import { z, type ZodRawShape } from 'zod';
3
3
 
4
- export function sectionDocsConfig(
5
- dir: string,
6
- schemaFields: ZodRawShape = {},
7
- ): {
8
- dir: string;
4
+ export function sectionDocsConfig(schemaFields: ZodRawShape = {}): {
9
5
  docs: {
10
6
  schema: ReturnType<typeof pageSchema.extend>;
11
7
  postprocess: { includeProcessedMarkdown: true };
@@ -13,7 +9,6 @@ export function sectionDocsConfig(
13
9
  meta: { schema: typeof metaSchema };
14
10
  } {
15
11
  return {
16
- dir,
17
12
  docs: {
18
13
  schema: pageSchema.extend({
19
14
  order: z.number().optional(),
@@ -34,3 +29,4 @@ export function sectionDocsConfig(
34
29
  },
35
30
  };
36
31
  }
32
+
@@ -1,40 +1,77 @@
1
- import { llms, loader, type StaticSource } from 'fumadocs-core/source';
1
+ import {
2
+ dynamicLoader,
3
+ llms,
4
+ type DynamicSource,
5
+ type LoaderConfig,
6
+ type LoaderOutput,
7
+ } from 'fumadocs-core/source';
2
8
  import { sectionDatasets } from '@terrafirmagreg-team/kit/lib/datasets';
9
+ import { sectionConfig } from '@terrafirmagreg-team/kit/lib/config';
3
10
  import { orderPlugin } from '@terrafirmagreg-team/kit/lib/order';
4
- import { renderSectionMarkdown } from '@terrafirmagreg-team/kit/lib/markdown';
11
+ import { renderSectionMarkdown } from '@terrafirmagreg-team/mdx/lib/markdown';
5
12
  import { localizedDatasetTrees } from '@terrafirmagreg-team/kit/lib/tree';
6
13
  import type { SectionTrees } from '@terrafirmagreg-team/kit/types';
14
+ import type { SectionKey } from '@terrafirmagreg-team/sites/location';
7
15
 
8
- type SectionSource = StaticSource;
16
+ const LOCALE_SEGMENT = /^[a-z]{2,3}_[a-z]{2,4}$/;
9
17
 
10
- export function createSectionSource<S extends SectionSource>(options: {
18
+ export function createSectionSource(options: {
11
19
  route: string;
12
20
 
13
- section: string;
14
- source: S;
21
+ section: SectionKey;
22
+ source: DynamicSource;
15
23
 
16
24
  postTrees?: (trees: SectionTrees) => SectionTrees;
17
25
  }) {
18
- const { route, section, source: fumadocsSource, postTrees } = options;
19
- const source = loader({
26
+ const { route, section, source: dynamicSource, postTrees } = options;
27
+ const source: DynamicSource = {
28
+ ...dynamicSource,
29
+ files: async () => {
30
+ const known = new Set(sectionConfig().knownLanguages);
31
+ return (await dynamicSource.files()).filter(
32
+ (file) =>
33
+ !file.path
34
+ .split('/')
35
+ .some((seg) => LOCALE_SEGMENT.test(seg) && !known.has(seg)),
36
+ );
37
+ },
38
+ };
39
+ const loader = dynamicLoader(source, {
20
40
  baseUrl: route,
21
- source: fumadocsSource,
22
41
  plugins: [orderPlugin()],
23
42
  });
24
- const markdown = llms(source as never, {
25
- renderPage: renderSectionMarkdown as (
26
- page: unknown,
27
- ) => Promise<string>,
28
- }) as { page(page: unknown): Promise<string> };
43
+ let resolved: LoaderOutput<LoaderConfig> | undefined;
44
+ const getSource = async () => {
45
+ resolved = (await loader.get()) as unknown as LoaderOutput<LoaderConfig>;
46
+ return resolved;
47
+ };
48
+ const getSourceSync = () => resolved;
49
+ const markdown = {
50
+ page: async (page: unknown) =>
51
+ llms((await getSource()) as never, {
52
+ renderPage: renderSectionMarkdown as (
53
+ page: unknown,
54
+ ) => Promise<string>,
55
+ }).page(page as never),
56
+ };
29
57
 
30
- let cached: Promise<SectionTrees> | undefined;
31
- const trees = (): Promise<SectionTrees> => {
32
- cached ??= sectionDatasets(section).then((datasets) => {
33
- const built = localizedDatasetTrees(source, datasets);
34
- return postTrees ? postTrees(built) : built;
58
+ let cached:
59
+ | { source: LoaderOutput<LoaderConfig>; trees: Promise<SectionTrees> }
60
+ | undefined;
61
+ const trees = (): Promise<SectionTrees> =>
62
+ getSource().then((source) => {
63
+ if (cached?.source === source) return cached.trees;
64
+ const built = (async () => {
65
+ const result = localizedDatasetTrees(
66
+ source,
67
+ await sectionDatasets(section),
68
+ );
69
+ return postTrees ? postTrees(result) : result;
70
+ })();
71
+ cached = { source, trees: built };
72
+ return built;
35
73
  });
36
- return cached;
37
- };
38
74
 
39
- return { route, source, markdown, trees };
75
+ return { route, getSource, getSourceSync, markdown, trees };
40
76
  }
77
+
@@ -19,3 +19,4 @@ export const sections: Partial<Record<SectionKey, SectionSourceEntry>> =
19
19
  : undefined,
20
20
  },
21
21
  );
22
+
package/src/lib/tree.ts CHANGED
@@ -58,3 +58,4 @@ export function localizedDatasetTrees(
58
58
  }
59
59
  return trees;
60
60
  }
61
+
package/src/lib/urls.ts CHANGED
@@ -14,3 +14,4 @@ export function getDatasetImageUrl(section: string, dataset: string) {
14
14
  url: createGetUrl(sectionConfig().previewRoute)(segments),
15
15
  };
16
16
  }
17
+
@@ -1,10 +1,7 @@
1
- {
2
- "ui": {
3
- "select_section": "Select section",
4
- "select_dataset": "Select dataset",
5
- "view_markdown": "View as Markdown",
6
- "copy_error": "Failed to copy",
7
- "search_everywhere": "Everywhere",
8
- "version_latest": "latest"
9
- }
10
- }
1
+ {
2
+ "Failed to copy(ui.copy_error)": "Failed to copy",
3
+ "Select dataset(ui.select_dataset)": "Select dataset",
4
+ "Select section(ui.select_section)": "Select section",
5
+ "View as Markdown(ui.view_markdown)": "View as Markdown",
6
+ "latest(ui.version_latest)": "latest"
7
+ }
@@ -1,10 +1,7 @@
1
- {
2
- "ui": {
3
- "select_section": "Выберите раздел",
4
- "select_dataset": "Выберите набор данных",
5
- "view_markdown": "Просмотр Markdown",
6
- "copy_error": "Не удалось скопировать",
7
- "search_everywhere": "Везде",
8
- "version_latest": "последняя"
9
- }
10
- }
1
+ {
2
+ "Failed to copy(ui.copy_error)": "Не удалось скопировать",
3
+ "Select dataset(ui.select_dataset)": "Выберите набор данных",
4
+ "Select section(ui.select_section)": "Выберите раздел",
5
+ "View as Markdown(ui.view_markdown)": "Просмотр Markdown",
6
+ "latest(ui.version_latest)": "последняя"
7
+ }
package/src/locales.ts CHANGED
@@ -3,3 +3,4 @@ import { createLocaleCatalog } from '@terrafirmagreg-team/translate/lib/locales'
3
3
  export const locales = createLocaleCatalog(
4
4
  import.meta.glob('./locales/*.json', { eager: true, import: 'default' }),
5
5
  );
6
+