@terrafirmagreg-team/kit 1.2.2 → 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 +1 -1
- package/src/components/views/routes.tsx +276 -0
package/package.json
CHANGED
|
@@ -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
|
+
}
|