@terrafirmagreg-team/guide 1.2.2 → 1.2.4

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/guide",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "type": "module",
5
5
  "description": "Guide/books section",
6
6
  "license": "LGPL-3.0-only",
@@ -24,17 +24,17 @@
24
24
  "dependencies": {
25
25
  "@terrafirmagreg-team/data": "1.2.1",
26
26
  "@terrafirmagreg-team/discussions": "1.2.3",
27
- "@terrafirmagreg-team/dump": "1.2.1",
28
- "@terrafirmagreg-team/game": "1.2.2",
29
- "@terrafirmagreg-team/kit": "1.2.2",
30
- "@terrafirmagreg-team/mdx": "1.2.1",
27
+ "@terrafirmagreg-team/dump": "1.2.2",
28
+ "@terrafirmagreg-team/game": "1.2.3",
29
+ "@terrafirmagreg-team/kit": "1.2.4",
30
+ "@terrafirmagreg-team/mdx": "1.2.2",
31
31
  "@terrafirmagreg-team/preview": "1.2.1",
32
- "@terrafirmagreg-team/search": "1.2.2",
32
+ "@terrafirmagreg-team/search": "1.2.3",
33
33
  "@terrafirmagreg-team/sites": "1.2.1",
34
- "@terrafirmagreg-team/translate": "1.2.3",
35
- "@terrafirmagreg-team/ui": "1.2.2",
36
- "fumadocs-core": "16.17.8",
37
- "lucide-react": "^0.577.0"
34
+ "@terrafirmagreg-team/translate": "1.2.4",
35
+ "@terrafirmagreg-team/ui": "1.2.3",
36
+ "fumadocs-core": "16.17.10",
37
+ "lucide-react": "^1.63.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "next": ">=15",
@@ -22,7 +22,7 @@ export type MdxMultiblockProps = {
22
22
  name?: string;
23
23
  spec?: string;
24
24
  label?: string;
25
- dataset?: DatasetRef;
25
+ dataset?: DatasetRef | string;
26
26
  title?: string;
27
27
  };
28
28
 
@@ -36,10 +36,17 @@ export function MdxMultiblock({
36
36
  }: MdxMultiblockProps) {
37
37
  const pathname = usePathname();
38
38
  const locId = parseSectionLocation(pathname).dataset;
39
- const dataset = useMemo<DatasetRef>(
40
- () => datasetProp ?? { id: locId ?? '' },
41
- [datasetProp, locId],
42
- );
39
+ const dataset = useMemo<DatasetRef>(() => {
40
+ if (typeof datasetProp === 'string') {
41
+ const trimmed = datasetProp.trim();
42
+ try {
43
+ return JSON.parse(trimmed) as DatasetRef;
44
+ } catch {
45
+ return { id: trimmed };
46
+ }
47
+ }
48
+ return datasetProp ?? { id: locId ?? '' };
49
+ }, [datasetProp, locId]);
43
50
  const lang = useContext(ResolvedLangContext)?.lang;
44
51
  const dumpLoc = useDumpLocation(dataset);
45
52
  const resolveResource = useMemo(
@@ -5,25 +5,40 @@ import { useLocationSearchParams } from '@terrafirmagreg-team/ui/hooks/page/use-
5
5
  import { useAsyncValue } from '@terrafirmagreg-team/ui/hooks/use-async-value';
6
6
  import { useVersionedDataset } from '@terrafirmagreg-team/kit/hooks/use-versioned-dataset';
7
7
  import { useLangMap } from '@terrafirmagreg-team/game/hooks/use-lang-map';
8
+ import { useResolvedLang } from '@terrafirmagreg-team/translate/hooks/use-resolved-lang';
8
9
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
9
10
  import { Skeleton } from '@terrafirmagreg-team/ui/components/ui/skeleton';
10
- import {
11
- Workspace,
12
- WorkspaceBody,
13
- } from '@terrafirmagreg-team/kit/components/workspace';
11
+ import { WorkspaceBody } from '@terrafirmagreg-team/ui/components/layouts/workspace';
14
12
  import { GuidePageView } from '@terrafirmagreg-team/guide/components/views/page/view';
15
13
  import {
16
14
  firstGuideEntryId,
17
15
  guideQueryFromParams,
18
16
  } from '@terrafirmagreg-team/guide/lib/query';
19
- import { loadClientGuidePayload } from '@terrafirmagreg-team/guide/lib/client-payload';
20
- import { listGuideBooks } from '@terrafirmagreg-team/guide/lib/books';
21
- import { loadClientGuideBook } from '@terrafirmagreg-team/guide/lib/client/guides';
17
+ import { getGuideSource } from '@terrafirmagreg-team/guide/lib/source';
22
18
  import { guideHref } from '@terrafirmagreg-team/guide/lib/links';
23
19
  import { guideConfig } from '@terrafirmagreg-team/guide/lib/config';
24
20
  import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
25
21
  import type { GuideMessages } from '@terrafirmagreg-team/guide/types';
26
22
 
23
+ export function GuideBrowserFallback() {
24
+ return (
25
+ <WorkspaceBody className="flex flex-col gap-3">
26
+ <Skeleton className="h-3.5 w-44" />
27
+ <Skeleton className="mt-1 h-8 w-2/3 max-w-sm" />
28
+ <Skeleton className="h-4 w-full max-w-md" />
29
+ <Skeleton className="h-4 w-4/5 max-w-sm" />
30
+ <div className="mt-6 flex flex-col gap-2.5">
31
+ <Skeleton className="h-4 w-full" />
32
+ <Skeleton className="h-4 w-[92%]" />
33
+ <Skeleton className="h-4 w-[96%]" />
34
+ <Skeleton className="h-32 w-full" />
35
+ <Skeleton className="h-4 w-[88%]" />
36
+ <Skeleton className="h-4 w-3/4" />
37
+ </div>
38
+ </WorkspaceBody>
39
+ );
40
+ }
41
+
27
42
  export function GuideBrowser({
28
43
  dataset: id,
29
44
  datasetTitle,
@@ -43,8 +58,9 @@ export function GuideBrowser({
43
58
  () => guideQueryFromParams(params),
44
59
  [params],
45
60
  );
61
+ const source = getGuideSource();
46
62
  const books = useAsyncValue(
47
- () => listGuideBooks(dataset, lang),
63
+ () => source.books(dataset, lang),
48
64
  [dataset, lang],
49
65
  );
50
66
  const langMap = useLangMap(dataset, lang);
@@ -69,17 +85,18 @@ export function GuideBrowser({
69
85
  [t],
70
86
  );
71
87
 
88
+ const resolved = useResolvedLang();
72
89
  const canonical = useAsyncValue(
73
90
  books && !bookId
74
91
  ? async () => {
75
92
  const book = books[0];
76
93
  if (!book) return undefined;
77
- const index = await loadClientGuideBook(dataset, lang, book.bookId);
94
+ const index = await source.book(dataset, lang, book.bookId);
78
95
  const first = index ? firstGuideEntryId(index) : undefined;
79
- return guideHref(dataset, lang, book.bookId, first);
96
+ return guideHref(dataset, resolved, book.bookId, first);
80
97
  }
81
98
  : undefined,
82
- [books, bookId, dataset, lang],
99
+ [books, bookId, dataset, lang, resolved],
83
100
  );
84
101
 
85
102
  useEffect(() => {
@@ -89,7 +106,7 @@ export function GuideBrowser({
89
106
  const payload = useAsyncValue(
90
107
  langMap && bookId
91
108
  ? () =>
92
- loadClientGuidePayload(
109
+ source.payload(
93
110
  dataset,
94
111
  lang,
95
112
  bookId,
@@ -109,24 +126,7 @@ export function GuideBrowser({
109
126
  }, [payload?.title, datasetTitle]);
110
127
 
111
128
  if (!bookId || !payload || !langMap) {
112
- return (
113
- <Workspace toc={[]}>
114
- <WorkspaceBody className="flex flex-col gap-3">
115
- <Skeleton className="h-3.5 w-44" />
116
- <Skeleton className="mt-1 h-8 w-2/3 max-w-sm" />
117
- <Skeleton className="h-4 w-full max-w-md" />
118
- <Skeleton className="h-4 w-4/5 max-w-sm" />
119
- <div className="mt-6 flex flex-col gap-2.5">
120
- <Skeleton className="h-4 w-full" />
121
- <Skeleton className="h-4 w-[92%]" />
122
- <Skeleton className="h-4 w-[96%]" />
123
- <Skeleton className="h-32 w-full" />
124
- <Skeleton className="h-4 w-[88%]" />
125
- <Skeleton className="h-4 w-3/4" />
126
- </div>
127
- </WorkspaceBody>
128
- </Workspace>
129
- );
129
+ return <GuideBrowserFallback />;
130
130
  }
131
131
  return <GuidePageView payload={payload} langMap={langMap} />;
132
132
  }
@@ -18,12 +18,12 @@ export function RelationPage(props: PageRendererProps) {
18
18
  if (!ids.length) return null;
19
19
  return (
20
20
  <ul className="space-y-1">
21
- {ids.map((id) => {
21
+ {ids.map((id, index) => {
22
22
  const resolved = referenceHref(props, id);
23
23
  const ref = entryRef(props.entryRefs, id);
24
24
  const name = entryLabel(props.entryRefs, props.langMap, id);
25
25
  return (
26
- <li key={id} className="flex items-center gap-2">
26
+ <li key={`${id}:${index}`} className="flex items-center gap-2">
27
27
  {ref?.icon ? (
28
28
  <Entry
29
29
  id={ref.icon.id}
@@ -90,10 +90,10 @@ export function TablePage(props: PageRendererProps) {
90
90
  style={{ backgroundColor: cssColor(value?.fill) }}
91
91
  >
92
92
  <span className="inline-flex flex-wrap items-center gap-1">
93
- {items.map((id) => (
93
+ {items.map((id, index) => (
94
94
  <Entry
95
95
  kind="item"
96
- key={id}
96
+ key={`${id}:${index}`}
97
97
  id={id}
98
98
  name={id}
99
99
  size="sm"
@@ -0,0 +1,32 @@
1
+ import { Suspense } from 'react';
2
+ import { WorkspaceBody } from '@terrafirmagreg-team/ui/components/layouts/workspace';
3
+ import type {
4
+ SectionLandingProps,
5
+ SectionLandingView,
6
+ } from '@terrafirmagreg-team/kit/components/views/routes';
7
+ import type { GuideSource } from '@terrafirmagreg-team/guide/lib/source';
8
+ import {
9
+ GuideBrowser,
10
+ GuideBrowserFallback,
11
+ } from '@terrafirmagreg-team/guide/components/views/browser';
12
+
13
+ export function guideLanding({
14
+ page,
15
+ lang,
16
+ }: SectionLandingProps<GuideSource>): SectionLandingView {
17
+ return {
18
+ node: (
19
+ <WorkspaceBody className="viewport-body">
20
+ <Suspense fallback={<GuideBrowserFallback />}>
21
+ <GuideBrowser
22
+ dataset={page.dataset.id}
23
+ datasetTitle={page.datasetTitle ?? page.dataset.id}
24
+ lang={lang}
25
+ versions={page.versions}
26
+ />
27
+ </Suspense>
28
+ </WorkspaceBody>
29
+ ),
30
+ workspace: { pagination: false },
31
+ };
32
+ }
@@ -0,0 +1,14 @@
1
+ import { Layout } from '@terrafirmagreg-team/kit/components/layouts/layout';
2
+ import type { SectionLayoutViewProps } from '@terrafirmagreg-team/kit/components/views/routes';
3
+ import { GuideBookSwitcher } from '@terrafirmagreg-team/guide/components/book-switcher';
4
+ import { useGuideLeftSidebarTree } from '@terrafirmagreg-team/guide/hooks/use-left-sidebar-tree';
5
+
6
+ export function GuideLayout(props: SectionLayoutViewProps) {
7
+ return (
8
+ <Layout
9
+ {...props}
10
+ useTree={useGuideLeftSidebarTree}
11
+ sidebar={{ feature: GuideBookSwitcher }}
12
+ />
13
+ );
14
+ }
@@ -1,11 +1,16 @@
1
+ 'use client';
2
+ import { useMemo } from 'react';
1
3
  import { EntryProvider } from '@terrafirmagreg-team/game/hooks/use-entry-context';
2
4
  import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
3
5
  import {
4
- Workspace,
6
+ useWorkspaceSlots,
7
+ type WorkspaceSlots,
8
+ } from '@terrafirmagreg-team/kit/components/views/frame';
9
+ import {
5
10
  WorkspaceBody,
6
11
  WorkspaceDescription,
7
12
  WorkspaceTitle,
8
- } from '@terrafirmagreg-team/kit/components/workspace';
13
+ } from '@terrafirmagreg-team/ui/components/layouts/workspace';
9
14
  import { guideConfig } from '@terrafirmagreg-team/guide/lib/config';
10
15
  import { GithubInfo } from '@terrafirmagreg-team/ui/components/blocks/git/github-info';
11
16
  import { sectionConfig } from '@terrafirmagreg-team/kit/lib/config';
@@ -203,14 +208,21 @@ export function GuidePageView({
203
208
  UntranslatedCallout,
204
209
  } = guideConfig().components;
205
210
 
211
+ const slots = useMemo<WorkspaceSlots>(
212
+ () => ({
213
+ toc: toc ?? [],
214
+ breadcrumb: { items: crumbs, previews: payload.previews },
215
+ pagination: { items: nav },
216
+ full: false,
217
+ }),
218
+ // eslint-disable-next-line react-hooks/exhaustive-deps
219
+ [payload, langMap],
220
+ );
221
+ useWorkspaceSlots(slots, [slots]);
222
+
206
223
  return (
207
224
  <EntryProvider dataset={dataset} lang={lang} langMap={langMap}>
208
- <Workspace
209
- toc={toc ?? []}
210
- className="min-h-full"
211
- breadcrumb={{ items: crumbs, previews: payload.previews }}
212
- pagination={{ items: nav }}
213
- >
225
+ <div className="flex flex-col gap-4 min-h-full">
214
226
  <UntranslatedCallout copied={copied} />
215
227
  <WorkspaceTitle>
216
228
  {kind === 'entry' && payload.previewIcon ? (
@@ -272,7 +284,7 @@ export function GuidePageView({
272
284
  <CommentsSlot
273
285
  page={`guide:${dataset.id}:${payload.modId}:${payload.bookPath}:${payload.entry?.id ?? payload.categoryId ?? 'book'}#${lang}`}
274
286
  />
275
- </Workspace>
287
+ </div>
276
288
  </EntryProvider>
277
289
  );
278
290
  }
@@ -50,7 +50,7 @@ export function guideBreadcrumbItems({
50
50
  : undefined,
51
51
  });
52
52
  }
53
- items.push({ name: title });
53
+ if (kind !== 'category' || !cat) items.push({ name: title });
54
54
  return items;
55
55
  }
56
56
 
@@ -93,9 +93,7 @@ export async function loadClientGuidePayload(
93
93
  title: entry?.name ?? category?.name ?? overview.book?.name ?? bookId,
94
94
  modId: bookModId,
95
95
  bookPath,
96
- description: entry
97
- ? undefined
98
- : (category?.description ?? overview.book?.landingText),
96
+ description: entry ? undefined : category?.description,
99
97
  previewIcon:
100
98
  entry?.icon?.id ?? category?.icon?.id ?? overview.book?.cover?.id,
101
99
  previewKind: entry?.icon?.kind ?? category?.icon?.kind,
package/src/lib/format.ts CHANGED
@@ -191,6 +191,23 @@ export function formatGuide(
191
191
  return out;
192
192
  }
193
193
 
194
+ export function guidePlainText(
195
+ raw: string,
196
+ options: GuideFormatOptions = {},
197
+ ): string {
198
+ return formatGuide(raw, options)
199
+ .map((node) =>
200
+ node.type === 'break'
201
+ ? ' '
202
+ : 'value' in node
203
+ ? node.value
204
+ : '',
205
+ )
206
+ .join('')
207
+ .replace(/[ \t]+/g, ' ')
208
+ .trim();
209
+ }
210
+
194
211
  export function guideFormatOpts(props: PageRendererProps) {
195
212
  return {
196
213
  dataset: props.dataset,
@@ -3,8 +3,8 @@ import { asRecord, asString } from '@terrafirmagreg-team/dump/lib/dump/contract/
3
3
  import { splitDumpId } from '@terrafirmagreg-team/dump/lib/dump/paths';
4
4
  import { modelPublicUrl } from '@terrafirmagreg-team/dump/lib/dump/assets/urls';
5
5
  import {
6
- clientDumpJson,
7
6
  dumpModelUrl,
7
+ multiblockModelIndex,
8
8
  } from '@terrafirmagreg-team/dump/lib/dump/client/dump';
9
9
  import { loadDatasetDump } from '@terrafirmagreg-team/dump/lib/dump/mods/load';
10
10
  import { createDatasetCache } from '@terrafirmagreg-team/dump/lib/dump/reader';
@@ -29,10 +29,7 @@ const multiblockIndex = createDatasetCache(async (dataset: DatasetRef) => {
29
29
  const entries = await Promise.all(
30
30
  (dump?.mods ?? []).map(async (ns) => {
31
31
  const index = asRecord(
32
- await clientDumpJson(
33
- dataset,
34
- `data/${ns.namespace}/models/multiblock.json`,
35
- ),
32
+ await multiblockModelIndex(dataset, ns.namespace),
36
33
  );
37
34
  return Object.entries(index ?? {}).map(
38
35
  ([id, rel]) => [id, { ns: ns.namespace, rel: asString(rel) }] as const,
@@ -1,5 +1,6 @@
1
1
  import { registerPreviewResolver } from '@terrafirmagreg-team/preview/lib';
2
2
  import { loadClientGuideBook } from '@terrafirmagreg-team/guide/lib/client/guides';
3
+ import { guidePlainText } from '@terrafirmagreg-team/guide/lib/format';
3
4
  import { entryPreviewIcon } from '@terrafirmagreg-team/game/lib/preview';
4
5
  import type { PreviewResolverCtx } from '@terrafirmagreg-team/preview/lib';
5
6
  import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
@@ -8,6 +9,7 @@ export async function guideLinkPreview({
8
9
  parts,
9
10
  query,
10
11
  lang,
12
+ langMap,
11
13
  }: PreviewResolverCtx): Promise<PagePreviewData | undefined> {
12
14
  const dataset = {
13
15
  id: parts[1] ?? '',
@@ -28,7 +30,12 @@ export async function guideLinkPreview({
28
30
  }
29
31
  return {
30
32
  title: index.book?.name ?? bookId,
31
- description: index.book?.landingText,
33
+ description: index.book?.landingText
34
+ ? guidePlainText(index.book.landingText, {
35
+ macros: index.book?.macros,
36
+ langMap,
37
+ })
38
+ : undefined,
32
39
  icon: entryPreviewIcon(index.book?.cover ?? undefined, dataset),
33
40
  };
34
41
  }
package/src/lib/recipe.ts CHANGED
@@ -3,9 +3,13 @@ import type { EmbedRecipe } from '@terrafirmagreg-team/guide/lib/embeds';
3
3
  import { stackDisplayName } from '@terrafirmagreg-team/game/lib/entry/display';
4
4
 
5
5
  export function recipeIds(page: GuidePage): string[] {
6
- return (page.recipes ?? []).filter(
7
- (id): id is string => typeof id === 'string' && Boolean(id),
8
- );
6
+ return [
7
+ ...new Set(
8
+ (page.recipes ?? []).filter(
9
+ (id): id is string => typeof id === 'string' && Boolean(id),
10
+ ),
11
+ ),
12
+ ];
9
13
  }
10
14
 
11
15
  export function recipeNameScope(
@@ -0,0 +1,13 @@
1
+ import { listGuideBooks } from '@terrafirmagreg-team/guide/lib/books';
2
+ import { loadClientGuideBook } from '@terrafirmagreg-team/guide/lib/client/guides';
3
+ import { loadClientGuidePayload } from '@terrafirmagreg-team/guide/lib/client-payload';
4
+
5
+ export function getGuideSource() {
6
+ return {
7
+ books: listGuideBooks,
8
+ book: loadClientGuideBook,
9
+ payload: loadClientGuidePayload,
10
+ };
11
+ }
12
+
13
+ export type GuideSource = ReturnType<typeof getGuideSource>;