@terrafirmagreg-team/kit 1.2.1 → 1.2.3

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": "@terrafirmagreg-team/kit",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
5
  "description": "Fumadocs content engine",
6
6
  "license": "LGPL-3.0-only",
@@ -19,8 +19,7 @@
19
19
  "./src/components/*.tsx",
20
20
  "./src/components/*/index.tsx",
21
21
  "./src/components/*/index.ts"
22
- ],
23
- "./locales": "./src/locales.ts"
22
+ ]
24
23
  },
25
24
  "dependencies": {
26
25
  "@base-ui/react": "^1.9.0",
@@ -28,8 +27,8 @@
28
27
  "@terrafirmagreg-team/data": "1.2.1",
29
28
  "@terrafirmagreg-team/mdx": "1.2.1",
30
29
  "@terrafirmagreg-team/sites": "1.2.1",
31
- "@terrafirmagreg-team/translate": "1.2.1",
32
- "@terrafirmagreg-team/ui": "1.2.1",
30
+ "@terrafirmagreg-team/translate": "1.2.3",
31
+ "@terrafirmagreg-team/ui": "1.2.2",
33
32
  "fumadocs-core": "16.17.8",
34
33
  "lucide-react": "^0.577.0",
35
34
  "server-only": "^0.0.1",
@@ -11,7 +11,10 @@ import { sectionDatasets } from '@terrafirmagreg-team/kit/lib/datasets';
11
11
  import { sectionUrl } from '@terrafirmagreg-team/sites/location';
12
12
  import { canonicalUrl } from '@terrafirmagreg-team/kit/lib/urls';
13
13
  import type { DatasetLanding, SectionKey } from '@terrafirmagreg-team/kit/types';
14
- import { localeLoader } from '@terrafirmagreg-team/kit/lib/locale/renderer';
14
+ import {
15
+ localeLoader,
16
+ registerLocaleRenderer,
17
+ } from '@terrafirmagreg-team/kit/lib/locale/renderer';
15
18
  import { langContent } from '@terrafirmagreg-team/translate/server';
16
19
 
17
20
  interface DatasetPageParams {
@@ -23,6 +26,12 @@ export function createDatasetLandingPage<S extends SectionKey>(
23
26
  section: S,
24
27
  render: (page: DatasetLanding) => ReactNode,
25
28
  ) {
29
+ registerLocaleRenderer(section, {
30
+ resolve: async (slug, lang) =>
31
+ resolveDatasetLanding(section, slug, lang, await sectionDatasets(section)),
32
+ render: (page) => render(page as DatasetLanding),
33
+ });
34
+
26
35
  async function Page({ params }: DatasetPageParams) {
27
36
  const { slug } = await params;
28
37
  const datasets = await sectionDatasets(section);
@@ -0,0 +1,276 @@
1
+ import type { ComponentProps, ComponentType, ReactNode } from 'react';
2
+ import { notFound } from 'next/navigation';
3
+ import type { Metadata } from 'next';
4
+ import type * as PageTree from 'fumadocs-core/page-tree';
5
+ import { flattenTree } from 'fumadocs-core/page-tree';
6
+ import { LangRedirect } from '@terrafirmagreg-team/ui/components/blocks/lang/redirect';
7
+ import { LangContentSwitch } from '@terrafirmagreg-team/ui/components/blocks/lang/content';
8
+ import {
9
+ Workspace,
10
+ type WorkspaceProps,
11
+ } from '@terrafirmagreg-team/kit/components/workspace';
12
+ import { baseOptions } from '@terrafirmagreg-team/kit/lib/layout';
13
+ import {
14
+ datasetCatalog,
15
+ defaultDataset,
16
+ findDataset,
17
+ sectionDatasets,
18
+ } from '@terrafirmagreg-team/kit/lib/datasets';
19
+ import {
20
+ generateDatasetLandingParams,
21
+ getDatasetLanding,
22
+ } from '@terrafirmagreg-team/kit/lib/landing';
23
+ import {
24
+ generateDatasetParams,
25
+ getPage,
26
+ resolvePage,
27
+ sectionMetadata,
28
+ } from '@terrafirmagreg-team/kit/lib/page';
29
+ import {
30
+ localeLoader,
31
+ registerLocaleRenderer,
32
+ } from '@terrafirmagreg-team/kit/lib/locale/renderer';
33
+ import { canonicalUrl } from '@terrafirmagreg-team/kit/lib/urls';
34
+ import {
35
+ renderSectionWorkspace,
36
+ type SectionWorkspaceProps,
37
+ } from '@terrafirmagreg-team/kit/components/views/section';
38
+ import { sectionUrl } from '@terrafirmagreg-team/sites/location';
39
+ import { langContent, loadLocaleRegistry } from '@terrafirmagreg-team/translate/server';
40
+ import type {
41
+ DatasetCatalog,
42
+ DatasetLanding,
43
+ SectionDataset,
44
+ SectionKey,
45
+ SectionPage,
46
+ SectionSource,
47
+ SectionTrees,
48
+ } from '@terrafirmagreg-team/kit/types';
49
+ import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
50
+
51
+ export interface SectionLayoutViewProps {
52
+ section: SectionKey;
53
+ trees: SectionTrees;
54
+ catalog: DatasetCatalog;
55
+ useTree?: (tree: PageTree.Root) => PageTree.Root;
56
+ sidebar?: { feature?: ComponentType };
57
+ navTitle?: ComponentType<ComponentProps<'a'>>;
58
+ children?: ReactNode;
59
+ }
60
+
61
+ export interface SectionLandingView {
62
+ node: ReactNode;
63
+ workspace?: WorkspaceProps;
64
+ }
65
+
66
+ export interface SectionRoutesOptions<S extends SectionKey> {
67
+ section: S;
68
+ trees?: () => Promise<SectionTrees>;
69
+ source?: {
70
+ getSource: () => Promise<SectionSource>;
71
+ contentDir: string;
72
+ history?: boolean;
73
+ };
74
+ sidebar?: {
75
+ feature?: ComponentType;
76
+ useTree?: (tree: PageTree.Root) => PageTree.Root;
77
+ };
78
+ layout: ComponentType<SectionLayoutViewProps>;
79
+ landing?: (
80
+ page: DatasetLanding,
81
+ lang: Lang,
82
+ ) => SectionLandingView | Promise<SectionLandingView>;
83
+ page?: ComponentType<SectionWorkspaceProps>;
84
+ }
85
+
86
+ interface RouteParams {
87
+ params: Promise<{ slug?: string[] }>;
88
+ }
89
+
90
+ type Resolved = { page: unknown; isFallback: boolean } | undefined;
91
+
92
+ export function createSectionRedirect(section: SectionKey) {
93
+ return async function Page() {
94
+ const dataset = await defaultDataset(section);
95
+ if (!dataset) notFound();
96
+ return <LangRedirect to={sectionUrl(section, dataset)} />;
97
+ };
98
+ }
99
+
100
+ export function createSectionRoutes<S extends SectionKey>({
101
+ section,
102
+ trees,
103
+ source,
104
+ sidebar,
105
+ layout: LayoutView,
106
+ landing,
107
+ page: PageView,
108
+ }: SectionRoutesOptions<S>) {
109
+ const resolve =
110
+ (src: SectionSource | undefined, datasets: readonly SectionDataset[]) =>
111
+ (slug: string[] | undefined, lang: Lang): Resolved => {
112
+ if (slug?.length === 1) {
113
+ if (!findDataset(datasets, { id: slug[0] })) return undefined;
114
+ if (landing) {
115
+ const page = getDatasetLanding(section, slug, lang, datasets);
116
+ if (!page) return undefined;
117
+ if (!src) return { page, isFallback: false };
118
+ const own = src
119
+ .getPages()
120
+ .some(
121
+ (p) =>
122
+ p.slugs[0] === slug[0] &&
123
+ ((p as SectionPage).locale ?? p.slugs[1]) === lang,
124
+ );
125
+ return { page, isFallback: !own };
126
+ }
127
+ }
128
+ return src ? resolvePage(src, slug, lang) : undefined;
129
+ };
130
+
131
+ const render = async (
132
+ src: SectionSource | undefined,
133
+ slug: string[] | undefined,
134
+ resolvedTrees: SectionTrees | undefined,
135
+ catalog: DatasetCatalog,
136
+ page: unknown,
137
+ lang: Lang,
138
+ ): Promise<ReactNode> => {
139
+ if (slug?.length === 1 && landing) {
140
+ const view = await landing(page as DatasetLanding, lang);
141
+ return <Workspace {...view.workspace}>{view.node}</Workspace>;
142
+ }
143
+ if (!src || !PageView) return null;
144
+ return renderSectionWorkspace({
145
+ source: src,
146
+ slug,
147
+ contentDir: source?.contentDir ?? `content/${section}`,
148
+ trees: resolvedTrees,
149
+ catalog,
150
+ history: source?.history,
151
+ workspace: PageView,
152
+ page: page as SectionPage,
153
+ lang,
154
+ });
155
+ };
156
+
157
+ registerLocaleRenderer(section, {
158
+ resolve: async (slug, lang) =>
159
+ resolve(await source?.getSource(), await sectionDatasets(section))(
160
+ slug,
161
+ lang,
162
+ ),
163
+ render: async (page, slug, lang) =>
164
+ render(
165
+ await source?.getSource(),
166
+ slug,
167
+ await trees?.(),
168
+ await datasetCatalog(),
169
+ page,
170
+ lang,
171
+ ),
172
+ });
173
+
174
+ async function SectionLayout({ children }: { children?: ReactNode }) {
175
+ const { slots } = baseOptions();
176
+ const [resolvedTrees, catalog] = await Promise.all([
177
+ trees ? trees() : Promise.resolve<SectionTrees>({}),
178
+ datasetCatalog(),
179
+ ]);
180
+ return (
181
+ <LayoutView
182
+ section={section}
183
+ trees={resolvedTrees}
184
+ catalog={catalog}
185
+ useTree={sidebar?.useTree}
186
+ sidebar={{ feature: sidebar?.feature }}
187
+ navTitle={slots?.navTitle}
188
+ >
189
+ {children}
190
+ </LayoutView>
191
+ );
192
+ }
193
+
194
+ async function Page({ params }: RouteParams) {
195
+ const { slug } = await params;
196
+ const [src, datasets] = await Promise.all([
197
+ source?.getSource(),
198
+ sectionDatasets(section),
199
+ ]);
200
+
201
+ if (slug?.length === 1 && src && !landing) {
202
+ const { defaultLanguage } = loadLocaleRegistry();
203
+ if (!getPage(src, slug, defaultLanguage)) {
204
+ const resolvedTrees = trees ? await trees() : undefined;
205
+ const first = flattenTree(
206
+ resolvedTrees?.[defaultLanguage]?.[slug[0]]?.children ?? [],
207
+ )[0];
208
+ if (first) return <LangRedirect to={first.url} />;
209
+ }
210
+ }
211
+
212
+ const [resolvedTrees, catalog] = await Promise.all([
213
+ trees?.(),
214
+ datasetCatalog(),
215
+ ]);
216
+ const content = langContent(
217
+ slug,
218
+ resolve(src, datasets),
219
+ (page, lang) =>
220
+ render(src, slug, resolvedTrees, catalog, page, lang) as ReactNode,
221
+ );
222
+ if (!content) notFound();
223
+ return (
224
+ <LangContentSwitch
225
+ key={[section, ...(slug ?? [])].join('/')}
226
+ content={content}
227
+ loadLang={localeLoader()?.bind(null, { section, slug })}
228
+ />
229
+ );
230
+ }
231
+
232
+ async function Redirect() {
233
+ const dataset = await defaultDataset(section);
234
+ if (!dataset) notFound();
235
+ return <LangRedirect to={sectionUrl(section, dataset)} />;
236
+ }
237
+
238
+ function generateStaticParams() {
239
+ if (source) {
240
+ return source.getSource().then((src) =>
241
+ generateDatasetParams(section, src),
242
+ );
243
+ }
244
+ return generateDatasetLandingParams(section);
245
+ }
246
+
247
+ async function generateMetadata({ params }: RouteParams): Promise<Metadata> {
248
+ const { slug } = await params;
249
+ if (slug?.length === 1 && landing) {
250
+ const page = getDatasetLanding(
251
+ section,
252
+ slug,
253
+ 'en_us',
254
+ await sectionDatasets(section),
255
+ );
256
+ if (!page) notFound();
257
+ return {
258
+ title: page.title,
259
+ description: page.description,
260
+ alternates: {
261
+ canonical: canonicalUrl(`${sectionUrl(section)}/${slug.join('/')}`),
262
+ },
263
+ };
264
+ }
265
+ const src = await source?.getSource();
266
+ if (src) {
267
+ const { defaultLanguage } = loadLocaleRegistry();
268
+ if (slug?.length === 1 && !landing && !getPage(src, slug, defaultLanguage))
269
+ return {};
270
+ return sectionMetadata(section, src, slug);
271
+ }
272
+ return {};
273
+ }
274
+
275
+ return { SectionLayout, Page, Redirect, generateStaticParams, generateMetadata };
276
+ }
@@ -1,7 +0,0 @@
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,7 +0,0 @@
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 DELETED
@@ -1,6 +0,0 @@
1
- import { createLocaleCatalog } from '@terrafirmagreg-team/translate/lib/locales';
2
-
3
- export const locales = createLocaleCatalog(
4
- import.meta.glob('./locales/*.json', { eager: true, import: 'default' }),
5
- );
6
-