@terrafirmagreg-team/guide 0.1.0 → 1.2.1
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 +18 -19
- package/src/components/book-switcher.tsx +12 -6
- package/src/components/mdx/guide-entry.tsx +48 -0
- package/src/components/mdx/multiblock.tsx +86 -0
- package/src/{lib/tree/children.ts → components/tree/children.tsx} +13 -8
- package/src/{lib/tree/index.ts → components/tree/index.tsx} +25 -15
- package/src/components/views/browser.tsx +26 -17
- package/src/components/views/entry/pages.tsx +4 -2
- package/src/components/views/entry/view.tsx +3 -1
- package/src/components/views/kind/links.tsx +3 -2
- package/src/components/views/kind/media.tsx +6 -5
- package/src/components/views/kind/recipe.tsx +6 -51
- package/src/components/views/kind/table.tsx +4 -2
- package/src/components/views/kind/unknown.tsx +1 -0
- package/src/components/views/page/category-body.tsx +3 -1
- package/src/components/views/page/entry-body.tsx +3 -1
- package/src/components/views/page/overview-body.tsx +4 -2
- package/src/components/views/page/text.tsx +1 -0
- package/src/components/views/page/view.tsx +6 -6
- package/src/components/views/text.tsx +4 -2
- package/src/hooks/use-left-sidebar-tree.ts +15 -6
- package/src/hooks/use-tree.ts +5 -3
- package/src/lib/book/convert.ts +2 -1
- package/src/lib/book/index-rows.ts +3 -1
- package/src/lib/book/index.ts +1 -0
- package/src/lib/book/localize.ts +3 -2
- package/src/lib/book/raw.ts +5 -4
- package/src/lib/books.ts +4 -2
- package/src/lib/breadcrumb.ts +3 -1
- package/src/lib/client/guides.ts +86 -13
- package/src/lib/client-payload/extras.ts +10 -8
- package/src/lib/client-payload/index.ts +9 -8
- package/src/lib/client-payload/links.ts +10 -8
- package/src/lib/config.ts +1 -0
- package/src/lib/data/dto.ts +5 -4
- package/src/lib/data/parse.ts +4 -3
- package/src/lib/dump.ts +4 -3
- package/src/lib/embeds.ts +12 -0
- package/src/lib/format.ts +3 -2
- package/src/lib/links.ts +1 -1
- package/src/lib/markdown/book.ts +1 -0
- package/src/lib/markdown/ctx.ts +3 -1
- package/src/lib/markdown/index.ts +1 -0
- package/src/lib/markdown/page.ts +7 -8
- package/src/lib/markdown/table.ts +1 -0
- package/src/lib/markdown/text.ts +1 -0
- package/src/lib/markdown-page.ts +6 -4
- package/src/lib/messages.ts +1 -0
- package/src/lib/meta.ts +1 -0
- package/src/lib/multiblock/index.ts +1 -0
- package/src/lib/multiblock/url.ts +24 -10
- package/src/lib/names.ts +5 -3
- package/src/lib/page-kind.ts +2 -1
- package/src/lib/paths.ts +4 -2
- package/src/lib/preview.ts +8 -4
- package/src/lib/query.ts +1 -0
- package/src/lib/recipe.ts +3 -2
- package/src/lib/reference/index.ts +1 -0
- package/src/lib/reference/resolver.ts +4 -4
- package/src/lib/search/filter.ts +2 -1
- package/src/lib/search/index.ts +41 -14
- package/src/lib/sprites.ts +1 -0
- package/src/lib/table.ts +1 -0
- package/src/lib/toc.ts +3 -2
- package/src/locales/en_us.json +12 -20
- package/src/locales/ru_ru.json +12 -20
- package/src/locales.ts +1 -0
- package/src/types/index.ts +12 -10
package/package.json
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terrafirmagreg-team/guide",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"description": "Guide/books section",
|
|
6
|
+
"license": "LGPL-3.0-only",
|
|
5
7
|
"exports": {
|
|
6
8
|
"./components/*": [
|
|
7
9
|
"./src/components/*.tsx",
|
|
8
|
-
"./src/components/*.ts",
|
|
9
10
|
"./src/components/*/index.tsx",
|
|
10
11
|
"./src/components/*/index.ts"
|
|
11
12
|
],
|
|
12
13
|
"./hooks/*": [
|
|
13
14
|
"./src/hooks/*.ts",
|
|
14
|
-
"./src/hooks
|
|
15
|
-
"./src/hooks/*/index.ts",
|
|
16
|
-
"./src/hooks/*/index.tsx"
|
|
15
|
+
"./src/hooks/*/index.ts"
|
|
17
16
|
],
|
|
18
17
|
"./lib/*": [
|
|
19
18
|
"./src/lib/*.ts",
|
|
20
|
-
"./src/lib
|
|
21
|
-
"./src/lib/*/index.ts",
|
|
22
|
-
"./src/lib/*/index.tsx"
|
|
19
|
+
"./src/lib/*/index.ts"
|
|
23
20
|
],
|
|
24
21
|
"./types/*": "./src/types/*.ts",
|
|
25
22
|
"./types": "./src/types/index.ts",
|
|
26
23
|
"./locales": "./src/locales.ts"
|
|
27
24
|
},
|
|
28
25
|
"dependencies": {
|
|
26
|
+
"@terrafirmagreg-team/data": "1.2.1",
|
|
27
|
+
"@terrafirmagreg-team/discussions": "1.2.1",
|
|
28
|
+
"@terrafirmagreg-team/dump": "1.2.1",
|
|
29
|
+
"@terrafirmagreg-team/game": "1.2.1",
|
|
30
|
+
"@terrafirmagreg-team/kit": "1.2.1",
|
|
31
|
+
"@terrafirmagreg-team/mdx": "1.2.1",
|
|
32
|
+
"@terrafirmagreg-team/preview": "1.2.1",
|
|
33
|
+
"@terrafirmagreg-team/search": "1.2.1",
|
|
34
|
+
"@terrafirmagreg-team/sites": "1.2.1",
|
|
35
|
+
"@terrafirmagreg-team/translate": "1.2.1",
|
|
36
|
+
"@terrafirmagreg-team/ui": "1.2.1",
|
|
29
37
|
"fumadocs-core": "16.17.8",
|
|
30
|
-
"lucide-react": "^
|
|
31
|
-
"@terrafirmagreg-team/game": "0.1.0",
|
|
32
|
-
"@terrafirmagreg-team/quest": "0.1.0",
|
|
33
|
-
"@terrafirmagreg-team/recipe": "0.1.0",
|
|
34
|
-
"@terrafirmagreg-team/translate": "0.1.0",
|
|
35
|
-
"@terrafirmagreg-team/ui": "0.1.0",
|
|
36
|
-
"@terrafirmagreg-team/kit": "0.1.0",
|
|
37
|
-
"@terrafirmagreg-team/comments": "0.1.0"
|
|
38
|
+
"lucide-react": "^0.577.0"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"next": ">=15",
|
|
@@ -54,8 +55,6 @@
|
|
|
54
55
|
"access": "public"
|
|
55
56
|
},
|
|
56
57
|
"files": [
|
|
57
|
-
"src"
|
|
58
|
-
"styles",
|
|
59
|
-
"styles.css"
|
|
58
|
+
"src"
|
|
60
59
|
]
|
|
61
60
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { usePathname } from 'next/navigation';
|
|
4
|
+
import { useMemo } from 'react';
|
|
4
5
|
import { BookOpen } from 'lucide-react';
|
|
5
6
|
import { useAsyncValue } from '@terrafirmagreg-team/ui/hooks/use-async-value';
|
|
6
7
|
import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
|
|
7
8
|
import { useResolvedLang } from '@terrafirmagreg-team/translate/hooks/use-resolved-lang';
|
|
8
|
-
import {
|
|
9
|
+
import { useLocationSearchParams } from '@terrafirmagreg-team/ui/hooks/page/use-location-search';
|
|
9
10
|
import { guideQueryFromParams } from '@terrafirmagreg-team/guide/lib/query';
|
|
10
11
|
import { listGuideBooks } from '@terrafirmagreg-team/guide/lib/books';
|
|
11
12
|
import { guideHref } from '@terrafirmagreg-team/guide/lib/links';
|
|
12
|
-
import { parseSectionLocation } from '@terrafirmagreg-team/
|
|
13
|
+
import { parseSectionLocation } from '@terrafirmagreg-team/sites/location';
|
|
13
14
|
import { Switcher } from '@terrafirmagreg-team/ui/components/blocks/nav/switcher';
|
|
14
15
|
|
|
15
16
|
function oneLineLanding(text: string | undefined) {
|
|
@@ -20,10 +21,14 @@ function oneLineLanding(text: string | undefined) {
|
|
|
20
21
|
|
|
21
22
|
export function GuideBookSwitcher() {
|
|
22
23
|
const t = useTranslations();
|
|
23
|
-
const { dataset } = parseSectionLocation(usePathname());
|
|
24
|
+
const { dataset: datasetId } = parseSectionLocation(usePathname());
|
|
24
25
|
const lang = useResolvedLang();
|
|
25
|
-
const
|
|
26
|
-
|
|
26
|
+
const params = useLocationSearchParams();
|
|
27
|
+
const { bookId } = guideQueryFromParams(params);
|
|
28
|
+
const version = params.get('version') ?? undefined;
|
|
29
|
+
const dataset = useMemo(
|
|
30
|
+
() => (datasetId ? { id: datasetId, version } : undefined),
|
|
31
|
+
[datasetId, version],
|
|
27
32
|
);
|
|
28
33
|
const books = useAsyncValue(
|
|
29
34
|
dataset ? () => listGuideBooks(dataset, lang) : undefined,
|
|
@@ -40,7 +45,8 @@ export function GuideBookSwitcher() {
|
|
|
40
45
|
url: guideHref(dataset, lang, book.bookId),
|
|
41
46
|
}))}
|
|
42
47
|
selected={bookId}
|
|
43
|
-
placeholder={t('ui.select_book')}
|
|
48
|
+
placeholder={t('Select book', { note: 'ui.select_book' })}
|
|
44
49
|
/>
|
|
45
50
|
);
|
|
46
51
|
}
|
|
52
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { usePathname } from 'next/navigation';
|
|
4
|
+
import { useContext } from 'react';
|
|
5
|
+
import { parseSectionLocation } from '@terrafirmagreg-team/sites/location';
|
|
6
|
+
import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
|
|
7
|
+
import { useEntryContext } from '@terrafirmagreg-team/game/hooks/use-entry-context';
|
|
8
|
+
import { ResolvedLangContext } from '@terrafirmagreg-team/translate/hooks/use-resolved-lang';
|
|
9
|
+
import { useAsyncValue } from '@terrafirmagreg-team/ui/hooks/use-async-value';
|
|
10
|
+
import { findClientGuideEntry } from '@terrafirmagreg-team/guide/lib/client/guides';
|
|
11
|
+
import type { GuideDirectiveProps } from '@terrafirmagreg-team/game/types/entry';
|
|
12
|
+
|
|
13
|
+
export function GuideEntry({
|
|
14
|
+
id,
|
|
15
|
+
dataset: datasetProp,
|
|
16
|
+
name,
|
|
17
|
+
modId,
|
|
18
|
+
bookPath,
|
|
19
|
+
entry: entryOpt,
|
|
20
|
+
...rest
|
|
21
|
+
}: GuideDirectiveProps) {
|
|
22
|
+
const ctx = useEntryContext();
|
|
23
|
+
const pathname = usePathname();
|
|
24
|
+
const locId = parseSectionLocation(pathname).dataset;
|
|
25
|
+
const dataset =
|
|
26
|
+
datasetProp ?? ctx?.dataset ?? (locId ? { id: locId } : undefined);
|
|
27
|
+
const lang = useContext(ResolvedLangContext)?.lang;
|
|
28
|
+
const hit = useAsyncValue(
|
|
29
|
+
dataset && lang && id
|
|
30
|
+
? () => findClientGuideEntry(dataset, lang, id)
|
|
31
|
+
: false,
|
|
32
|
+
[dataset, lang, id],
|
|
33
|
+
);
|
|
34
|
+
return (
|
|
35
|
+
<Entry
|
|
36
|
+
kind="guide"
|
|
37
|
+
id={hit?.entry.id ?? id}
|
|
38
|
+
dataset={dataset}
|
|
39
|
+
name={name ?? hit?.entry.name}
|
|
40
|
+
icon={hit?.entry.icon}
|
|
41
|
+
modId={modId ?? hit?.modId}
|
|
42
|
+
bookPath={bookPath ?? hit?.bookPath}
|
|
43
|
+
entry={entryOpt ?? hit?.entry.id}
|
|
44
|
+
{...rest}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useContext, useMemo } from 'react';
|
|
4
|
+
import { usePathname } from 'next/navigation';
|
|
5
|
+
import {
|
|
6
|
+
type DatasetRef,
|
|
7
|
+
parseSectionLocation,
|
|
8
|
+
} from '@terrafirmagreg-team/sites/location';
|
|
9
|
+
import { useAsyncValue } from '@terrafirmagreg-team/ui/hooks/use-async-value';
|
|
10
|
+
import { useDumpLocation } from '@terrafirmagreg-team/game/hooks/use-dump-location';
|
|
11
|
+
import { ResolvedLangContext } from '@terrafirmagreg-team/translate/hooks/use-resolved-lang';
|
|
12
|
+
import { createDumpAssetResolver } from '@terrafirmagreg-team/dump/lib/dump/assets/urls';
|
|
13
|
+
import { asMultiblock } from '@terrafirmagreg-team/guide/lib/meta';
|
|
14
|
+
import { glbFor } from '@terrafirmagreg-team/guide/lib/multiblock/url';
|
|
15
|
+
import { loadMultiblockNameIndex } from '@terrafirmagreg-team/guide/lib/client/guides';
|
|
16
|
+
import { multiblockKey } from '@terrafirmagreg-team/guide/lib/multiblock';
|
|
17
|
+
import { MultiblockVisual } from '@terrafirmagreg-team/guide/components/views/kind/media';
|
|
18
|
+
import type { GuideMultiblock } from '@terrafirmagreg-team/guide/types';
|
|
19
|
+
|
|
20
|
+
export type MdxMultiblockProps = {
|
|
21
|
+
id?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
spec?: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
dataset?: DatasetRef;
|
|
26
|
+
title?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function MdxMultiblock({
|
|
30
|
+
id,
|
|
31
|
+
name,
|
|
32
|
+
spec,
|
|
33
|
+
label,
|
|
34
|
+
dataset: datasetProp,
|
|
35
|
+
title,
|
|
36
|
+
}: MdxMultiblockProps) {
|
|
37
|
+
const pathname = usePathname();
|
|
38
|
+
const locId = parseSectionLocation(pathname).dataset;
|
|
39
|
+
const dataset = useMemo<DatasetRef>(
|
|
40
|
+
() => datasetProp ?? { id: locId ?? '' },
|
|
41
|
+
[datasetProp, locId],
|
|
42
|
+
);
|
|
43
|
+
const lang = useContext(ResolvedLangContext)?.lang;
|
|
44
|
+
const dumpLoc = useDumpLocation(dataset);
|
|
45
|
+
const resolveResource = useMemo(
|
|
46
|
+
() => createDumpAssetResolver(dataset),
|
|
47
|
+
[dataset],
|
|
48
|
+
);
|
|
49
|
+
const namedSpec = useAsyncValue(
|
|
50
|
+
dataset && lang && name
|
|
51
|
+
? async () =>
|
|
52
|
+
(await loadMultiblockNameIndex(dataset, lang))[
|
|
53
|
+
name.trim().toLowerCase()
|
|
54
|
+
]
|
|
55
|
+
: false,
|
|
56
|
+
[dataset, lang, name],
|
|
57
|
+
);
|
|
58
|
+
const value = useMemo<string | GuideMultiblock | undefined>(() => {
|
|
59
|
+
if (spec) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(spec) as GuideMultiblock;
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (name) return namedSpec;
|
|
67
|
+
return id;
|
|
68
|
+
}, [id, name, spec, namedSpec]);
|
|
69
|
+
const key = multiblockKey(asMultiblock(value));
|
|
70
|
+
const src = useAsyncValue(
|
|
71
|
+
dataset && key
|
|
72
|
+
? () => glbFor(dataset, '', asMultiblock(value), lang)
|
|
73
|
+
: false,
|
|
74
|
+
[dataset, key, lang, dumpLoc],
|
|
75
|
+
);
|
|
76
|
+
const glbs = src && key ? { [key]: src } : undefined;
|
|
77
|
+
return (
|
|
78
|
+
<MultiblockVisual
|
|
79
|
+
value={value}
|
|
80
|
+
title={title ?? label ?? id ?? name}
|
|
81
|
+
glbs={glbs}
|
|
82
|
+
resolveResource={resolveResource}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
@@ -2,13 +2,17 @@ import { createElement } from 'react';
|
|
|
2
2
|
import type * as PageTree from 'fumadocs-core/page-tree';
|
|
3
3
|
import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
|
|
4
4
|
import { encodeQueryValue } from '@terrafirmagreg-team/translate/lib/url';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type DatasetRef,
|
|
7
|
+
sectionUrl,
|
|
8
|
+
versionSuffix,
|
|
9
|
+
} from '@terrafirmagreg-team/sites/location';
|
|
6
10
|
import { bookOptionId, guideCategoryHref } from '@terrafirmagreg-team/guide/lib/paths';
|
|
7
11
|
import type { GuideIndex } from '@terrafirmagreg-team/guide/types';
|
|
8
|
-
import type { TaggedReference } from '@terrafirmagreg-team/
|
|
12
|
+
import type { TaggedReference } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
|
|
9
13
|
|
|
10
14
|
function nodeIcon(
|
|
11
|
-
dataset:
|
|
15
|
+
dataset: DatasetRef,
|
|
12
16
|
icon: TaggedReference | undefined,
|
|
13
17
|
name: string,
|
|
14
18
|
): React.ReactNode {
|
|
@@ -34,7 +38,7 @@ function nodeIcon(
|
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export function guideTreeChildren(
|
|
37
|
-
dataset:
|
|
41
|
+
dataset: DatasetRef,
|
|
38
42
|
index: GuideIndex,
|
|
39
43
|
): PageTree.Node[] {
|
|
40
44
|
const bookId =
|
|
@@ -56,15 +60,16 @@ export function guideTreeChildren(
|
|
|
56
60
|
index.bookPath ?? '',
|
|
57
61
|
category,
|
|
58
62
|
),
|
|
59
|
-
$id: `${dataset}/${index.modId}/${index.bookPath}/${category.id}#index`,
|
|
63
|
+
$id: `${dataset.id}/${dataset.version ?? ''}/${index.modId}/${index.bookPath}/${category.id}#index`,
|
|
60
64
|
},
|
|
61
|
-
$id: `${dataset}/${index.modId}/${index.bookPath}/${category.id}`,
|
|
65
|
+
$id: `${dataset.id}/${dataset.version ?? ''}/${index.modId}/${index.bookPath}/${category.id}`,
|
|
62
66
|
children: category.entries.map((entry): PageTree.Item => ({
|
|
63
67
|
type: 'page',
|
|
64
68
|
name: entry.name,
|
|
65
|
-
url: `${sectionUrl('guide', dataset)}?book=${encodeQueryValue(bookId ?? '')}&entry=${encodeQueryValue(entry.id)}`,
|
|
69
|
+
url: `${sectionUrl('guide', dataset.id)}?book=${encodeQueryValue(bookId ?? '')}&entry=${encodeQueryValue(entry.id)}${versionSuffix(dataset)}`,
|
|
66
70
|
icon: nodeIcon(dataset, entry.icon, entry.name),
|
|
67
|
-
$id: `${dataset}/${index.modId}/${index.bookPath}/${category.id}/${entry.id}`,
|
|
71
|
+
$id: `${dataset.id}/${dataset.version ?? ''}/${index.modId}/${index.bookPath}/${category.id}/${entry.id}`,
|
|
68
72
|
})),
|
|
69
73
|
}));
|
|
70
74
|
}
|
|
75
|
+
|
|
@@ -2,14 +2,20 @@ import 'server-only';
|
|
|
2
2
|
import { createElement } from 'react';
|
|
3
3
|
import type * as PageTree from 'fumadocs-core/page-tree';
|
|
4
4
|
import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
|
|
5
|
-
import { loadMergedLangMap } from '@terrafirmagreg-team/
|
|
6
|
-
import { asRecord, asString } from '@terrafirmagreg-team/
|
|
7
|
-
import { loadDatasetDump } from '@terrafirmagreg-team/
|
|
8
|
-
import {
|
|
5
|
+
import { loadMergedLangMap } from '@terrafirmagreg-team/dump/lib/dump/assets/lang/load';
|
|
6
|
+
import { asRecord, asString } from '@terrafirmagreg-team/dump/lib/dump/contract/read';
|
|
7
|
+
import { loadDatasetDump } from '@terrafirmagreg-team/dump/lib/dump/mods/load';
|
|
8
|
+
import { DUMP_POINTER_TTL } from '@terrafirmagreg-team/dump/lib/dump/json-reader';
|
|
9
|
+
import { createAsyncCache } from '@terrafirmagreg-team/data/lib/async-cache';
|
|
10
|
+
import { clientDumpJson } from '@terrafirmagreg-team/dump/lib/dump/client/dump';
|
|
9
11
|
import { keyTreeNode } from '@terrafirmagreg-team/translate/server';
|
|
10
12
|
import { loadLocaleRegistry } from '@terrafirmagreg-team/translate/server';
|
|
11
13
|
import { encodeQueryValue } from '@terrafirmagreg-team/translate/lib/url';
|
|
12
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type DatasetRef,
|
|
16
|
+
sectionUrl,
|
|
17
|
+
versionSuffix,
|
|
18
|
+
} from '@terrafirmagreg-team/sites/location';
|
|
13
19
|
import { bookOptionId } from '@terrafirmagreg-team/guide/lib/paths';
|
|
14
20
|
import { bookSlug, toBook } from '@terrafirmagreg-team/guide/lib/book';
|
|
15
21
|
import { sectionDatasets } from '@terrafirmagreg-team/kit/lib/datasets';
|
|
@@ -17,7 +23,7 @@ import type { GuideBookJson, GuideBookRef } from '@terrafirmagreg-team/guide/lib
|
|
|
17
23
|
import type { SectionTrees } from '@terrafirmagreg-team/kit/types';
|
|
18
24
|
|
|
19
25
|
function bookIcon(
|
|
20
|
-
dataset:
|
|
26
|
+
dataset: DatasetRef,
|
|
21
27
|
raw: GuideBookJson | undefined,
|
|
22
28
|
): React.ReactNode {
|
|
23
29
|
const cover = asRecord(raw?.cover);
|
|
@@ -43,7 +49,7 @@ function bookIcon(
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
function bookLeaf(
|
|
46
|
-
dataset:
|
|
52
|
+
dataset: DatasetRef,
|
|
47
53
|
ref: GuideBookRef,
|
|
48
54
|
slug: string,
|
|
49
55
|
raw: GuideBookJson | undefined,
|
|
@@ -54,14 +60,14 @@ function bookLeaf(
|
|
|
54
60
|
return keyTreeNode({
|
|
55
61
|
type: 'page',
|
|
56
62
|
name: book.name,
|
|
57
|
-
url: `${sectionUrl('guide', dataset)}?book=${encodeQueryValue(bookId)}`,
|
|
63
|
+
url: `${sectionUrl('guide', dataset.id)}?book=${encodeQueryValue(bookId)}${versionSuffix(dataset)}`,
|
|
58
64
|
icon: bookIcon(dataset, raw),
|
|
59
|
-
$id: `${dataset}/book/${bookId}`,
|
|
65
|
+
$id: `${dataset.id}/${dataset.version ?? ''}/book/${bookId}`,
|
|
60
66
|
}) as PageTree.Item;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
async function datasetBookLeaves(
|
|
64
|
-
dataset:
|
|
70
|
+
dataset: DatasetRef,
|
|
65
71
|
langMap: Record<string, string>,
|
|
66
72
|
): Promise<PageTree.Node[]> {
|
|
67
73
|
const dump = await loadDatasetDump(dataset);
|
|
@@ -92,11 +98,14 @@ async function datasetBookLeaves(
|
|
|
92
98
|
return leaves;
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
|
|
101
|
+
const guideTreesCache = createAsyncCache(
|
|
102
|
+
async (): Promise<SectionTrees> => buildGuideTrees(),
|
|
103
|
+
() => 'guide-trees',
|
|
104
|
+
DUMP_POINTER_TTL,
|
|
105
|
+
);
|
|
96
106
|
|
|
97
107
|
export function guideTrees(): Promise<SectionTrees> {
|
|
98
|
-
|
|
99
|
-
return cached;
|
|
108
|
+
return guideTreesCache();
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
async function buildGuideTrees(): Promise<SectionTrees> {
|
|
@@ -105,13 +114,14 @@ async function buildGuideTrees(): Promise<SectionTrees> {
|
|
|
105
114
|
const trees: SectionTrees = {};
|
|
106
115
|
for (const { id, title: datasetTitle, languages } of datasets) {
|
|
107
116
|
for (const lang of languages ?? [defaultLanguage]) {
|
|
108
|
-
const map = await loadMergedLangMap(id, lang);
|
|
117
|
+
const map = await loadMergedLangMap({ id }, lang);
|
|
109
118
|
(trees[lang] ??= {})[id] = {
|
|
110
119
|
name: datasetTitle ?? id,
|
|
111
|
-
children: await datasetBookLeaves(id, map),
|
|
120
|
+
children: await datasetBookLeaves({ id }, map),
|
|
112
121
|
$id: `guide/${id}/${lang}`,
|
|
113
122
|
} satisfies PageTree.Root;
|
|
114
123
|
}
|
|
115
124
|
}
|
|
116
125
|
return trees;
|
|
117
126
|
}
|
|
127
|
+
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
2
|
import { useEffect, useMemo } from 'react';
|
|
4
|
-
import { useRouter
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import { useLocationSearchParams } from '@terrafirmagreg-team/ui/hooks/page/use-location-search';
|
|
5
5
|
import { useAsyncValue } from '@terrafirmagreg-team/ui/hooks/use-async-value';
|
|
6
|
+
import { useVersionedDataset } from '@terrafirmagreg-team/kit/hooks/use-versioned-dataset';
|
|
6
7
|
import { useLangMap } from '@terrafirmagreg-team/game/hooks/use-lang-map';
|
|
7
8
|
import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
|
|
8
9
|
import { Skeleton } from '@terrafirmagreg-team/ui/components/ui/skeleton';
|
|
@@ -23,30 +24,21 @@ import { guideConfig } from '@terrafirmagreg-team/guide/lib/config';
|
|
|
23
24
|
import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
|
|
24
25
|
import type { GuideMessages } from '@terrafirmagreg-team/guide/types';
|
|
25
26
|
|
|
26
|
-
const MESSAGE_KEYS: readonly string[] = [
|
|
27
|
-
'guide.related',
|
|
28
|
-
'guide.objective',
|
|
29
|
-
'guide.link',
|
|
30
|
-
'guide.quest',
|
|
31
|
-
'guide.recipe_empty',
|
|
32
|
-
'guide.entity_unavailable',
|
|
33
|
-
'guide.entry_missing',
|
|
34
|
-
'guide.entry_count',
|
|
35
|
-
'guide.page_count',
|
|
36
|
-
];
|
|
37
|
-
|
|
38
27
|
export function GuideBrowser({
|
|
39
|
-
dataset,
|
|
28
|
+
dataset: id,
|
|
40
29
|
datasetTitle,
|
|
41
30
|
lang,
|
|
31
|
+
versions,
|
|
42
32
|
}: {
|
|
43
33
|
dataset: string;
|
|
44
34
|
datasetTitle: string;
|
|
45
35
|
lang: Lang;
|
|
36
|
+
versions?: readonly string[];
|
|
46
37
|
}) {
|
|
47
38
|
const t = useTranslations();
|
|
48
39
|
const router = useRouter();
|
|
49
|
-
const params =
|
|
40
|
+
const params = useLocationSearchParams();
|
|
41
|
+
const dataset = useVersionedDataset(id, { versions }) ?? { id };
|
|
50
42
|
const { bookId, entryId, categoryId } = useMemo(
|
|
51
43
|
() => guideQueryFromParams(params),
|
|
52
44
|
[params],
|
|
@@ -57,7 +49,23 @@ export function GuideBrowser({
|
|
|
57
49
|
);
|
|
58
50
|
const langMap = useLangMap(dataset, lang);
|
|
59
51
|
const messages = useMemo<GuideMessages>(
|
|
60
|
-
() =>
|
|
52
|
+
() => ({
|
|
53
|
+
'guide.related': t('Related', { note: 'guide.related' }),
|
|
54
|
+
'guide.objective': t('Objective', { note: 'guide.objective' }),
|
|
55
|
+
'guide.link': t('Open', { note: 'guide.link' }),
|
|
56
|
+
'guide.quest': t('Quest', { note: 'guide.quest' }),
|
|
57
|
+
'guide.recipe_empty': t('No recipes', { note: 'guide.recipe_empty' }),
|
|
58
|
+
'guide.entity_unavailable': t('Entity preview unavailable', {
|
|
59
|
+
note: 'guide.entity_unavailable',
|
|
60
|
+
}),
|
|
61
|
+
'guide.entry_missing': t('Entry not found', {
|
|
62
|
+
note: 'guide.entry_missing',
|
|
63
|
+
}),
|
|
64
|
+
'guide.entry_count': t('{count} entries', {
|
|
65
|
+
note: 'guide.entry_count',
|
|
66
|
+
}),
|
|
67
|
+
'guide.page_count': t('{count} pages', { note: 'guide.page_count' }),
|
|
68
|
+
}),
|
|
61
69
|
[t],
|
|
62
70
|
);
|
|
63
71
|
|
|
@@ -122,3 +130,4 @@ export function GuideBrowser({
|
|
|
122
130
|
}
|
|
123
131
|
return <GuidePageView payload={payload} langMap={langMap} />;
|
|
124
132
|
}
|
|
133
|
+
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
3
|
import { PageText } from '@terrafirmagreg-team/guide/components/views/page/text';
|
|
3
4
|
import {
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
import { TablePage } from '@terrafirmagreg-team/guide/components/views/kind/table';
|
|
20
21
|
import { UnknownPage } from '@terrafirmagreg-team/guide/components/views/kind/unknown';
|
|
21
22
|
import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
|
|
22
|
-
import type { AtlasBundle } from '@terrafirmagreg-team/
|
|
23
|
+
import type { AtlasBundle } from '@terrafirmagreg-team/dump/lib/dump/assets/atlas/dto';
|
|
23
24
|
import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
|
|
24
25
|
import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
|
|
25
26
|
import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
|
|
@@ -79,7 +80,7 @@ export function EntryPages({
|
|
|
79
80
|
previews,
|
|
80
81
|
messages,
|
|
81
82
|
}: {
|
|
82
|
-
dataset:
|
|
83
|
+
dataset: DatasetRef;
|
|
83
84
|
lang: Lang;
|
|
84
85
|
bookPath: string;
|
|
85
86
|
pages: GuidePage[];
|
|
@@ -184,3 +185,4 @@ export function EntryPages({
|
|
|
184
185
|
</div>
|
|
185
186
|
);
|
|
186
187
|
}
|
|
188
|
+
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
|
|
1
2
|
import { EntryPages } from '@terrafirmagreg-team/guide/components/views/entry/pages';
|
|
2
3
|
import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
|
|
3
4
|
import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
|
|
@@ -24,7 +25,7 @@ export function EntryView({
|
|
|
24
25
|
previews,
|
|
25
26
|
messages,
|
|
26
27
|
}: {
|
|
27
|
-
dataset:
|
|
28
|
+
dataset: DatasetRef;
|
|
28
29
|
lang: Lang;
|
|
29
30
|
bookPath: string;
|
|
30
31
|
entry: GuideEntry;
|
|
@@ -58,3 +59,4 @@ export function EntryView({
|
|
|
58
59
|
/>
|
|
59
60
|
);
|
|
60
61
|
}
|
|
62
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LinkPreview } from '@terrafirmagreg-team/ui/components/blocks/link/link-preview';
|
|
2
2
|
import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
|
|
3
|
-
import {
|
|
3
|
+
import { guideEmbeds } from '@terrafirmagreg-team/guide/lib/embeds';
|
|
4
4
|
import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
|
|
5
5
|
import {
|
|
6
6
|
entryLabel,
|
|
@@ -92,7 +92,7 @@ export function QuestPage({
|
|
|
92
92
|
const id = page.trigger || page.quest || page.link;
|
|
93
93
|
const hit = id ? questLinks?.[parseGuideReference(id).id] : undefined;
|
|
94
94
|
const href = hit
|
|
95
|
-
?
|
|
95
|
+
? guideEmbeds().quest?.href(dataset, lang, { category: hit.filename, id: hit.questId })
|
|
96
96
|
: undefined;
|
|
97
97
|
const name = id ? spriteName(dataset, langMap, atlases, id) : '—';
|
|
98
98
|
const label = id && name !== id ? name : <code>{name}</code>;
|
|
@@ -109,3 +109,4 @@ export function QuestPage({
|
|
|
109
109
|
</p>
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
|
+
|
|
@@ -5,11 +5,11 @@ import { asMultiblock } from '@terrafirmagreg-team/guide/lib/meta';
|
|
|
5
5
|
import {
|
|
6
6
|
createDumpAssetResolver,
|
|
7
7
|
texturePublicUrl,
|
|
8
|
-
} from '@terrafirmagreg-team/
|
|
9
|
-
import { dumpRelPublicUrl } from '@terrafirmagreg-team/
|
|
10
|
-
import { parseStackId } from '@terrafirmagreg-team/
|
|
11
|
-
import { langString } from '@terrafirmagreg-team/
|
|
12
|
-
import { matchVariant } from '@terrafirmagreg-team/
|
|
8
|
+
} from '@terrafirmagreg-team/dump/lib/dump/assets/urls';
|
|
9
|
+
import { dumpRelPublicUrl } from '@terrafirmagreg-team/dump/lib/dump/paths';
|
|
10
|
+
import { parseStackId } from '@terrafirmagreg-team/dump/lib/dump/contract/ref';
|
|
11
|
+
import { langString } from '@terrafirmagreg-team/dump/lib/dump/assets/lang/names';
|
|
12
|
+
import { matchVariant } from '@terrafirmagreg-team/dump/lib/dump/data/registry/parse';
|
|
13
13
|
import { GlbViewer } from '@terrafirmagreg-team/ui/components/blocks/media/glb/viewer';
|
|
14
14
|
import { multiblockKey } from '@terrafirmagreg-team/guide/lib/multiblock';
|
|
15
15
|
import { guideFormatOpts } from '@terrafirmagreg-team/guide/lib/format';
|
|
@@ -165,3 +165,4 @@ export function MultiMultiblockPage({
|
|
|
165
165
|
<MissingVisual title={page.name} />
|
|
166
166
|
);
|
|
167
167
|
}
|
|
168
|
+
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import Link from 'next/link';
|
|
5
2
|
import { Card } from '@terrafirmagreg-team/ui/components/ui/card';
|
|
6
3
|
import { EmptyState } from '@terrafirmagreg-team/ui/components/blocks/page/empty-state';
|
|
7
|
-
import {
|
|
8
|
-
import { FitCard } from '@terrafirmagreg-team/recipe/components/cards/fit-card';
|
|
4
|
+
import { guideEmbeds } from '@terrafirmagreg-team/guide/lib/embeds';
|
|
9
5
|
import { Entry } from '@terrafirmagreg-team/game/components/entry/entry';
|
|
10
6
|
import { EntryList } from '@terrafirmagreg-team/game/components/entry/list';
|
|
11
7
|
import { guideMessage } from '@terrafirmagreg-team/guide/lib/messages';
|
|
@@ -13,51 +9,8 @@ import { collectItemIds } from '@terrafirmagreg-team/guide/lib/data/parse';
|
|
|
13
9
|
import { pageTitle } from '@terrafirmagreg-team/guide/lib/toc';
|
|
14
10
|
import { spriteName } from '@terrafirmagreg-team/guide/lib/names';
|
|
15
11
|
import { recipeIds, recipeNameScope } from '@terrafirmagreg-team/guide/lib/recipe';
|
|
16
|
-
import { recipeHref } from '@terrafirmagreg-team/recipe/lib/links';
|
|
17
|
-
import { recipeCardImageUrl } from '@terrafirmagreg-team/recipe/lib/links';
|
|
18
|
-
import { recipeImageUrl } from '@terrafirmagreg-team/game/lib/dump/assets/urls';
|
|
19
|
-
import { recipeTitle } from '@terrafirmagreg-team/recipe/lib/data/parse';
|
|
20
|
-
import type { Recipe } from '@terrafirmagreg-team/recipe/lib/data/dto';
|
|
21
|
-
import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
id,
|
|
25
|
-
recipe,
|
|
26
|
-
dataset,
|
|
27
|
-
lang,
|
|
28
|
-
langMap,
|
|
29
|
-
}: {
|
|
30
|
-
id: string;
|
|
31
|
-
recipe: Recipe;
|
|
32
|
-
dataset: string;
|
|
33
|
-
lang: string;
|
|
34
|
-
langMap: Record<string, string>;
|
|
35
|
-
}) {
|
|
36
|
-
const [failed, setFailed] = useState(false);
|
|
37
|
-
if (failed) return null;
|
|
38
|
-
const src =
|
|
39
|
-
recipeImageUrl(dataset, recipe) ?? recipeCardImageUrl(dataset, id);
|
|
40
|
-
if (!src) return null;
|
|
41
|
-
const title = recipeTitle(recipe, langMap);
|
|
42
|
-
return (
|
|
43
|
-
<Link
|
|
44
|
-
href={recipeHref(dataset, lang, { id })}
|
|
45
|
-
className="not-prose block w-fit max-w-full"
|
|
46
|
-
>
|
|
47
|
-
<FitCard w={recipe.w ?? 0}>
|
|
48
|
-
<PagedCard
|
|
49
|
-
recipe={recipe}
|
|
50
|
-
dataset={dataset}
|
|
51
|
-
lang={lang}
|
|
52
|
-
langMap={langMap}
|
|
53
|
-
title={title}
|
|
54
|
-
src={src}
|
|
55
|
-
onFail={() => setFailed(true)}
|
|
56
|
-
/>
|
|
57
|
-
</FitCard>
|
|
58
|
-
</Link>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
13
|
+
import type { PageRendererProps } from '@terrafirmagreg-team/guide/types';
|
|
61
14
|
|
|
62
15
|
export function RecipePage({
|
|
63
16
|
dataset,
|
|
@@ -76,13 +29,14 @@ export function RecipePage({
|
|
|
76
29
|
</EmptyState>
|
|
77
30
|
);
|
|
78
31
|
}
|
|
32
|
+
const RecipeCard = guideEmbeds().recipe?.Card;
|
|
79
33
|
return (
|
|
80
34
|
<div className="space-y-4">
|
|
81
35
|
{ids.map((id) => {
|
|
82
36
|
const recipe = extras?.recipes?.[id];
|
|
83
|
-
if (recipe) {
|
|
37
|
+
if (recipe && RecipeCard) {
|
|
84
38
|
return (
|
|
85
|
-
<
|
|
39
|
+
<RecipeCard
|
|
86
40
|
key={id}
|
|
87
41
|
id={id}
|
|
88
42
|
recipe={recipe}
|
|
@@ -132,3 +86,4 @@ export function SpotlightPage({
|
|
|
132
86
|
/>
|
|
133
87
|
);
|
|
134
88
|
}
|
|
89
|
+
|