@byline/host-tanstack-start 2.7.0 → 3.0.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/dist/admin-shell/chrome/route-error.d.ts +9 -1
- package/dist/admin-shell/chrome/route-error.js +37 -5
- package/dist/admin-shell/collections/create.js +5 -1
- package/dist/admin-shell/collections/edit.js +5 -1
- package/dist/server-fns/collections/create.d.ts +2 -0
- package/dist/server-fns/collections/create.js +3 -2
- package/dist/server-fns/collections/get.d.ts +2 -0
- package/dist/server-fns/collections/get.js +9 -0
- package/dist/server-fns/collections/history.d.ts +1 -0
- package/dist/server-fns/collections/list.d.ts +1 -0
- package/dist/server-fns/collections/list.js +2 -1
- package/dist/server-fns/collections/update.d.ts +2 -0
- package/dist/server-fns/collections/update.js +3 -2
- package/package.json +8 -8
- package/src/admin-shell/chrome/route-error.tsx +75 -4
- package/src/admin-shell/collections/create.tsx +4 -0
- package/src/admin-shell/collections/edit.tsx +4 -0
- package/src/server-fns/collections/create.ts +15 -2
- package/src/server-fns/collections/get.ts +21 -0
- package/src/server-fns/collections/list.ts +5 -0
- package/src/server-fns/collections/update.ts +11 -1
|
@@ -10,9 +10,17 @@
|
|
|
10
10
|
* These are designed to be used as `errorComponent` and `notFoundComponent`
|
|
11
11
|
* on layout routes (root, admin, public) so that errors render inside the
|
|
12
12
|
* appropriate shell rather than blowing away the entire page.
|
|
13
|
+
*
|
|
14
|
+
* `RouteError` / `RouteNotFound` self-mount their own `<I18nProvider>`. A
|
|
15
|
+
* route's `errorComponent` renders *in place of that route's `component`*, so
|
|
16
|
+
* the admin layout's own `<I18nProvider>` (mounted inside that component) is
|
|
17
|
+
* gone by the time the error screen renders — calling `useTranslation` against
|
|
18
|
+
* the absent provider previously threw a *second* error that masked the real
|
|
19
|
+
* one. Self-mounting (mirroring `sign-in-page.tsx`) makes the screen localised
|
|
20
|
+
* and provider-independent wherever it renders.
|
|
13
21
|
*/
|
|
14
22
|
import type { ErrorComponentProps, NotFoundRouteProps } from '@tanstack/react-router';
|
|
15
|
-
export declare function RouteError(
|
|
23
|
+
export declare function RouteError(props: ErrorComponentProps): import("react").JSX.Element;
|
|
16
24
|
export declare function RouteNotFound(_props: NotFoundRouteProps): import("react").JSX.Element;
|
|
17
25
|
/**
|
|
18
26
|
* Minimal fallback for when providers may be broken — used at the root
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useContext } from "react";
|
|
2
3
|
import { useRouter } from "@tanstack/react-router";
|
|
3
|
-
import { BylineError, ErrorCodes } from "@byline/core";
|
|
4
|
-
import { useTranslation } from "@byline/i18n/react";
|
|
4
|
+
import { BylineError, ErrorCodes, getClientConfig } from "@byline/core";
|
|
5
|
+
import { I18nContext, I18nProvider, useTranslation } from "@byline/i18n/react";
|
|
5
6
|
import { Alert, Button, Container, Section } from "@byline/ui/react";
|
|
6
7
|
import classnames from "classnames";
|
|
7
8
|
import route_error_module from "./route-error.module.js";
|
|
@@ -24,7 +25,26 @@ function getErrorMessage(error, t) {
|
|
|
24
25
|
if (error instanceof Error) return error.message;
|
|
25
26
|
return t('routeError.defaultMessage');
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
+
function ErrorScreenI18nProvider({ children }) {
|
|
29
|
+
if (null != useContext(I18nContext)) return /*#__PURE__*/ jsx(Fragment, {
|
|
30
|
+
children: children
|
|
31
|
+
});
|
|
32
|
+
let bundle = {};
|
|
33
|
+
let locale = 'en';
|
|
34
|
+
try {
|
|
35
|
+
const { i18n } = getClientConfig();
|
|
36
|
+
bundle = i18n.translations ?? {};
|
|
37
|
+
locale = i18n.interface.defaultLocale;
|
|
38
|
+
} catch {}
|
|
39
|
+
return /*#__PURE__*/ jsx(I18nProvider, {
|
|
40
|
+
bundle: bundle,
|
|
41
|
+
activeLocale: locale,
|
|
42
|
+
defaultLocale: locale,
|
|
43
|
+
localeDefinitions: [],
|
|
44
|
+
children: children
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function RouteErrorContent({ error, reset }) {
|
|
28
48
|
const router = useRouter();
|
|
29
49
|
const { t } = useTranslation('byline-admin');
|
|
30
50
|
return /*#__PURE__*/ jsx(Section, {
|
|
@@ -66,7 +86,14 @@ function RouteError({ error, reset }) {
|
|
|
66
86
|
})
|
|
67
87
|
});
|
|
68
88
|
}
|
|
69
|
-
function
|
|
89
|
+
function RouteError(props) {
|
|
90
|
+
return /*#__PURE__*/ jsx(ErrorScreenI18nProvider, {
|
|
91
|
+
children: /*#__PURE__*/ jsx(RouteErrorContent, {
|
|
92
|
+
...props
|
|
93
|
+
})
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function RouteNotFoundContent() {
|
|
70
97
|
const { t } = useTranslation('byline-admin');
|
|
71
98
|
return /*#__PURE__*/ jsx(Section, {
|
|
72
99
|
className: classnames('byline-route-error', route_error_module.section),
|
|
@@ -95,6 +122,11 @@ function RouteNotFound(_props) {
|
|
|
95
122
|
})
|
|
96
123
|
});
|
|
97
124
|
}
|
|
125
|
+
function RouteNotFound(_props) {
|
|
126
|
+
return /*#__PURE__*/ jsx(ErrorScreenI18nProvider, {
|
|
127
|
+
children: /*#__PURE__*/ jsx(RouteNotFoundContent, {})
|
|
128
|
+
});
|
|
129
|
+
}
|
|
98
130
|
function RootError({ error, reset }) {
|
|
99
131
|
function rootMessage(err) {
|
|
100
132
|
if (err instanceof Error) return err.message;
|
|
@@ -15,7 +15,7 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData })=>{
|
|
|
15
15
|
});
|
|
16
16
|
const navigate = useNavigate();
|
|
17
17
|
const { labels, path, fields } = collectionDefinition;
|
|
18
|
-
const handleSubmit = async ({ data, systemPath })=>{
|
|
18
|
+
const handleSubmit = async ({ data, systemPath, systemAvailableLocales })=>{
|
|
19
19
|
try {
|
|
20
20
|
await createCollectionDocument({
|
|
21
21
|
data: {
|
|
@@ -23,6 +23,9 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData })=>{
|
|
|
23
23
|
data,
|
|
24
24
|
...systemPath ? {
|
|
25
25
|
path: systemPath
|
|
26
|
+
} : {},
|
|
27
|
+
...systemAvailableLocales ? {
|
|
28
|
+
availableLocales: systemAvailableLocales
|
|
26
29
|
} : {}
|
|
27
30
|
}
|
|
28
31
|
});
|
|
@@ -68,6 +71,7 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData })=>{
|
|
|
68
71
|
adminConfig: adminConfig,
|
|
69
72
|
useAsTitle: collectionDefinition.useAsTitle,
|
|
70
73
|
useAsPath: collectionDefinition.useAsPath,
|
|
74
|
+
advertiseLocales: collectionDefinition.advertiseLocales,
|
|
71
75
|
headingLabel: labels.singular,
|
|
72
76
|
useNavigationGuard: useTanStackNavigationGuard,
|
|
73
77
|
onCancel: ()=>navigate({
|
|
@@ -346,7 +346,7 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
346
346
|
});
|
|
347
347
|
}
|
|
348
348
|
};
|
|
349
|
-
const handleSubmit = async ({ data: _data, patches, systemPath })=>{
|
|
349
|
+
const handleSubmit = async ({ data: _data, patches, systemPath, systemAvailableLocales })=>{
|
|
350
350
|
try {
|
|
351
351
|
await updateCollectionDocumentWithPatches({
|
|
352
352
|
data: {
|
|
@@ -357,6 +357,9 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
357
357
|
locale,
|
|
358
358
|
...systemPath ? {
|
|
359
359
|
path: systemPath
|
|
360
|
+
} : {},
|
|
361
|
+
...systemAvailableLocales ? {
|
|
362
|
+
availableLocales: systemAvailableLocales
|
|
360
363
|
} : {}
|
|
361
364
|
}
|
|
362
365
|
});
|
|
@@ -422,6 +425,7 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
422
425
|
adminConfig: adminConfig,
|
|
423
426
|
useAsTitle: collectionDefinition.useAsTitle,
|
|
424
427
|
useAsPath: collectionDefinition.useAsPath,
|
|
428
|
+
advertiseLocales: collectionDefinition.advertiseLocales,
|
|
425
429
|
headingLabel: labels.singular,
|
|
426
430
|
initialLocale: locale,
|
|
427
431
|
onLocaleChange: handleLocaleChange,
|
|
@@ -10,11 +10,13 @@ export declare const createCollectionDocument: import("@tanstack/react-start").R
|
|
|
10
10
|
data: any;
|
|
11
11
|
locale?: string;
|
|
12
12
|
path?: string;
|
|
13
|
+
availableLocales?: string[];
|
|
13
14
|
}) => {
|
|
14
15
|
collection: string;
|
|
15
16
|
data: any;
|
|
16
17
|
locale?: string;
|
|
17
18
|
path?: string;
|
|
19
|
+
availableLocales?: string[];
|
|
18
20
|
}, Promise<{
|
|
19
21
|
status: "ok";
|
|
20
22
|
}>>;
|
|
@@ -6,7 +6,7 @@ import { ensureCollection } from "../../integrations/api-utils.js";
|
|
|
6
6
|
const createCollectionDocument = createServerFn({
|
|
7
7
|
method: 'POST'
|
|
8
8
|
}).inputValidator((input)=>input).handler(async ({ data: input })=>{
|
|
9
|
-
const { collection: path, data: documentData, locale, path: explicitPath } = input;
|
|
9
|
+
const { collection: path, data: documentData, locale, path: explicitPath, availableLocales } = input;
|
|
10
10
|
const logger = getLogger();
|
|
11
11
|
const config = await ensureCollection(path);
|
|
12
12
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -31,7 +31,8 @@ const createCollectionDocument = createServerFn({
|
|
|
31
31
|
data: structuredClone(documentData),
|
|
32
32
|
status: documentData.status,
|
|
33
33
|
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
34
|
-
path: explicitPath
|
|
34
|
+
path: explicitPath,
|
|
35
|
+
availableLocales
|
|
35
36
|
});
|
|
36
37
|
return {
|
|
37
38
|
status: 'ok'
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* `{target_document_id, target_collection_id}` refs.
|
|
17
17
|
*/
|
|
18
18
|
export declare function getCollectionDocument(collection: string, id: string, locale?: string, depth?: number, populateRelations?: boolean): Promise<{
|
|
19
|
+
_availableVersionLocales?: string[] | undefined;
|
|
20
|
+
availableLocales?: string[] | undefined;
|
|
19
21
|
_restoreWarnings?: string[] | undefined;
|
|
20
22
|
_publishedVersion: Record<string, any> | null;
|
|
21
23
|
} | null>;
|
|
@@ -39,6 +39,7 @@ const getDocumentFn = createServerFn({
|
|
|
39
39
|
populate,
|
|
40
40
|
depth: resolvedDepth,
|
|
41
41
|
status: 'any',
|
|
42
|
+
onMissingLocale: 'empty',
|
|
42
43
|
lenient: true
|
|
43
44
|
});
|
|
44
45
|
if (!document) throw ERR_NOT_FOUND({
|
|
@@ -69,11 +70,19 @@ const getDocumentFn = createServerFn({
|
|
|
69
70
|
}) : null;
|
|
70
71
|
}
|
|
71
72
|
const restoreWarnings = serialised._restoreWarnings;
|
|
73
|
+
const availableLocales = serialised.availableLocales;
|
|
74
|
+
const availableVersionLocales = serialised._availableVersionLocales;
|
|
72
75
|
return {
|
|
73
76
|
...parsed,
|
|
74
77
|
_publishedVersion: publishedVersion,
|
|
75
78
|
...restoreWarnings && restoreWarnings.length > 0 ? {
|
|
76
79
|
_restoreWarnings: restoreWarnings
|
|
80
|
+
} : {},
|
|
81
|
+
...Array.isArray(availableLocales) ? {
|
|
82
|
+
availableLocales
|
|
83
|
+
} : {},
|
|
84
|
+
...Array.isArray(availableVersionLocales) ? {
|
|
85
|
+
_availableVersionLocales: availableVersionLocales
|
|
77
86
|
} : {}
|
|
78
87
|
};
|
|
79
88
|
});
|
|
@@ -29,6 +29,7 @@ export declare const getCollectionDocumentHistory: import("@tanstack/react-start
|
|
|
29
29
|
updatedAt: string;
|
|
30
30
|
versionId?: string | undefined;
|
|
31
31
|
path?: string | undefined;
|
|
32
|
+
sourceLocale?: string | undefined;
|
|
32
33
|
hasPublishedVersion?: boolean | undefined;
|
|
33
34
|
}[];
|
|
34
35
|
meta: {
|
|
@@ -30,6 +30,7 @@ export declare const getCollectionDocuments: import("@tanstack/react-start").Req
|
|
|
30
30
|
updatedAt: string;
|
|
31
31
|
versionId?: string | undefined;
|
|
32
32
|
path?: string | undefined;
|
|
33
|
+
sourceLocale?: string | undefined;
|
|
33
34
|
hasPublishedVersion?: boolean | undefined;
|
|
34
35
|
}[];
|
|
35
36
|
meta: {
|
|
@@ -33,7 +33,8 @@ const getCollectionDocuments = createServerFn({
|
|
|
33
33
|
page: params.page,
|
|
34
34
|
pageSize,
|
|
35
35
|
select: params.fields,
|
|
36
|
-
status: 'any'
|
|
36
|
+
status: 'any',
|
|
37
|
+
onMissingLocale: 'empty'
|
|
37
38
|
});
|
|
38
39
|
const documentIds = result.docs.map((d)=>d.id);
|
|
39
40
|
const publishedSet = documentIds.length > 0 ? await getServerConfig().db.queries.documents.getPublishedDocumentIds({
|
|
@@ -13,6 +13,7 @@ export declare const updateCollectionDocumentWithPatches: import("@tanstack/reac
|
|
|
13
13
|
versionId?: string;
|
|
14
14
|
locale?: string;
|
|
15
15
|
path?: string;
|
|
16
|
+
availableLocales?: string[];
|
|
16
17
|
}) => {
|
|
17
18
|
collection: string;
|
|
18
19
|
id: string;
|
|
@@ -20,6 +21,7 @@ export declare const updateCollectionDocumentWithPatches: import("@tanstack/reac
|
|
|
20
21
|
versionId?: string;
|
|
21
22
|
locale?: string;
|
|
22
23
|
path?: string;
|
|
24
|
+
availableLocales?: string[];
|
|
23
25
|
}, Promise<{
|
|
24
26
|
status: "ok";
|
|
25
27
|
}>>;
|
|
@@ -6,7 +6,7 @@ import { ensureCollection } from "../../integrations/api-utils.js";
|
|
|
6
6
|
const updateCollectionDocumentWithPatches = createServerFn({
|
|
7
7
|
method: 'POST'
|
|
8
8
|
}).inputValidator((input)=>input).handler(async ({ data: input })=>{
|
|
9
|
-
const { collection: path, id, patches, versionId, locale, path: explicitPath } = input;
|
|
9
|
+
const { collection: path, id, patches, versionId, locale, path: explicitPath, availableLocales } = input;
|
|
10
10
|
const logger = getLogger();
|
|
11
11
|
const config = await ensureCollection(path);
|
|
12
12
|
if (!config) throw ERR_NOT_FOUND({
|
|
@@ -32,7 +32,8 @@ const updateCollectionDocumentWithPatches = createServerFn({
|
|
|
32
32
|
patches,
|
|
33
33
|
documentVersionId: versionId,
|
|
34
34
|
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
35
|
-
path: explicitPath
|
|
35
|
+
path: explicitPath,
|
|
36
|
+
availableLocales
|
|
36
37
|
});
|
|
37
38
|
return {
|
|
38
39
|
status: 'ok'
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "3.0.1",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -115,13 +115,13 @@
|
|
|
115
115
|
"react-swipeable": "^7.0.2",
|
|
116
116
|
"uuid": "^14.0.0",
|
|
117
117
|
"zod": "^4.4.3",
|
|
118
|
-
"@byline/
|
|
119
|
-
"@byline/
|
|
120
|
-
"@byline/
|
|
121
|
-
"@byline/
|
|
122
|
-
"@byline/
|
|
123
|
-
"@byline/
|
|
124
|
-
"@byline/
|
|
118
|
+
"@byline/admin": "3.0.1",
|
|
119
|
+
"@byline/auth": "3.0.1",
|
|
120
|
+
"@byline/core": "3.0.1",
|
|
121
|
+
"@byline/ui": "3.0.1",
|
|
122
|
+
"@byline/i18n": "3.0.1",
|
|
123
|
+
"@byline/client": "3.0.1",
|
|
124
|
+
"@byline/ai": "3.0.1"
|
|
125
125
|
},
|
|
126
126
|
"peerDependencies": {
|
|
127
127
|
"@tanstack/react-router": "^1.167.0",
|
|
@@ -10,14 +10,24 @@
|
|
|
10
10
|
* These are designed to be used as `errorComponent` and `notFoundComponent`
|
|
11
11
|
* on layout routes (root, admin, public) so that errors render inside the
|
|
12
12
|
* appropriate shell rather than blowing away the entire page.
|
|
13
|
+
*
|
|
14
|
+
* `RouteError` / `RouteNotFound` self-mount their own `<I18nProvider>`. A
|
|
15
|
+
* route's `errorComponent` renders *in place of that route's `component`*, so
|
|
16
|
+
* the admin layout's own `<I18nProvider>` (mounted inside that component) is
|
|
17
|
+
* gone by the time the error screen renders — calling `useTranslation` against
|
|
18
|
+
* the absent provider previously threw a *second* error that masked the real
|
|
19
|
+
* one. Self-mounting (mirroring `sign-in-page.tsx`) makes the screen localised
|
|
20
|
+
* and provider-independent wherever it renders.
|
|
13
21
|
*/
|
|
14
22
|
|
|
23
|
+
import { type ReactNode, useContext } from 'react'
|
|
15
24
|
import type { ErrorComponentProps, NotFoundRouteProps } from '@tanstack/react-router'
|
|
16
25
|
import { useRouter } from '@tanstack/react-router'
|
|
17
26
|
|
|
18
|
-
import { BylineError, ErrorCodes } from '@byline/core'
|
|
27
|
+
import { BylineError, ErrorCodes, getClientConfig } from '@byline/core'
|
|
28
|
+
import type { TranslationBundle } from '@byline/i18n'
|
|
19
29
|
import type { UseTranslationReturn } from '@byline/i18n/react'
|
|
20
|
-
import { useTranslation } from '@byline/i18n/react'
|
|
30
|
+
import { I18nContext, I18nProvider, useTranslation } from '@byline/i18n/react'
|
|
21
31
|
import { Alert, Button, Container, Section } from '@byline/ui/react'
|
|
22
32
|
import cx from 'classnames'
|
|
23
33
|
|
|
@@ -56,7 +66,52 @@ function getErrorMessage(error: unknown, t: Translate): string {
|
|
|
56
66
|
return t('routeError.defaultMessage')
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Self-mounts the byline-admin `<I18nProvider>` for the error/not-found
|
|
71
|
+
* screens (see the file header for why the layout-level provider is absent
|
|
72
|
+
* here).
|
|
73
|
+
*
|
|
74
|
+
* When a provider is already in scope (e.g. a not-found that renders inside
|
|
75
|
+
* the admin layout) it is reused as-is, preserving the user's active locale.
|
|
76
|
+
* Only when none is present (the error-boundary case) does it self-mount.
|
|
77
|
+
*
|
|
78
|
+
* Resilient by design (an error component that throws masks the real error):
|
|
79
|
+
* if the client config can't be read, it falls back to an English-default
|
|
80
|
+
* provider with an empty bundle. With a provider always present,
|
|
81
|
+
* `useTranslation` never throws; a missing key then renders as the raw key
|
|
82
|
+
* rather than crashing (see `I18nProvider`'s `onMissing`).
|
|
83
|
+
*/
|
|
84
|
+
function ErrorScreenI18nProvider({ children }: { children: ReactNode }) {
|
|
85
|
+
// Already inside a provider — reuse it (keeps the active locale) and skip the
|
|
86
|
+
// self-mount entirely.
|
|
87
|
+
if (useContext(I18nContext) != null) {
|
|
88
|
+
return <>{children}</>
|
|
89
|
+
}
|
|
90
|
+
let bundle: TranslationBundle = {}
|
|
91
|
+
let locale = 'en'
|
|
92
|
+
try {
|
|
93
|
+
const { i18n } = getClientConfig()
|
|
94
|
+
bundle = i18n.translations ?? {}
|
|
95
|
+
// No active provider, so render in the default locale — recovering the
|
|
96
|
+
// per-user active locale isn't worth another lookup that could itself throw.
|
|
97
|
+
locale = i18n.interface.defaultLocale
|
|
98
|
+
} catch {
|
|
99
|
+
// Config unavailable (very early boot / broken provider chain) — fall
|
|
100
|
+
// through with the English defaults above.
|
|
101
|
+
}
|
|
102
|
+
return (
|
|
103
|
+
<I18nProvider
|
|
104
|
+
bundle={bundle}
|
|
105
|
+
activeLocale={locale}
|
|
106
|
+
defaultLocale={locale}
|
|
107
|
+
localeDefinitions={[]}
|
|
108
|
+
>
|
|
109
|
+
{children}
|
|
110
|
+
</I18nProvider>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function RouteErrorContent({ error, reset }: ErrorComponentProps) {
|
|
60
115
|
const router = useRouter()
|
|
61
116
|
const { t } = useTranslation('byline-admin')
|
|
62
117
|
|
|
@@ -89,7 +144,15 @@ export function RouteError({ error, reset }: ErrorComponentProps) {
|
|
|
89
144
|
)
|
|
90
145
|
}
|
|
91
146
|
|
|
92
|
-
export function
|
|
147
|
+
export function RouteError(props: ErrorComponentProps) {
|
|
148
|
+
return (
|
|
149
|
+
<ErrorScreenI18nProvider>
|
|
150
|
+
<RouteErrorContent {...props} />
|
|
151
|
+
</ErrorScreenI18nProvider>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function RouteNotFoundContent() {
|
|
93
156
|
const { t } = useTranslation('byline-admin')
|
|
94
157
|
return (
|
|
95
158
|
<Section className={cx('byline-route-error', styles.section)}>
|
|
@@ -109,6 +172,14 @@ export function RouteNotFound(_props: NotFoundRouteProps) {
|
|
|
109
172
|
)
|
|
110
173
|
}
|
|
111
174
|
|
|
175
|
+
export function RouteNotFound(_props: NotFoundRouteProps) {
|
|
176
|
+
return (
|
|
177
|
+
<ErrorScreenI18nProvider>
|
|
178
|
+
<RouteNotFoundContent />
|
|
179
|
+
</ErrorScreenI18nProvider>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
112
183
|
/**
|
|
113
184
|
* Minimal fallback for when providers may be broken — used at the root
|
|
114
185
|
* route where the i18n provider itself may not be mounted. Stays in
|
|
@@ -44,10 +44,12 @@ export const CreateView = ({
|
|
|
44
44
|
const handleSubmit = async ({
|
|
45
45
|
data,
|
|
46
46
|
systemPath,
|
|
47
|
+
systemAvailableLocales,
|
|
47
48
|
}: {
|
|
48
49
|
// biome-ignore lint/suspicious/noExplicitAny: data is collection-specific
|
|
49
50
|
data: any
|
|
50
51
|
systemPath?: string | null
|
|
52
|
+
systemAvailableLocales?: string[]
|
|
51
53
|
}) => {
|
|
52
54
|
try {
|
|
53
55
|
await createCollectionDocument({
|
|
@@ -55,6 +57,7 @@ export const CreateView = ({
|
|
|
55
57
|
collection: path,
|
|
56
58
|
data,
|
|
57
59
|
...(systemPath ? { path: systemPath } : {}),
|
|
60
|
+
...(systemAvailableLocales ? { availableLocales: systemAvailableLocales } : {}),
|
|
58
61
|
},
|
|
59
62
|
})
|
|
60
63
|
navigate({
|
|
@@ -97,6 +100,7 @@ export const CreateView = ({
|
|
|
97
100
|
adminConfig={adminConfig}
|
|
98
101
|
useAsTitle={collectionDefinition.useAsTitle}
|
|
99
102
|
useAsPath={collectionDefinition.useAsPath}
|
|
103
|
+
advertiseLocales={collectionDefinition.advertiseLocales}
|
|
100
104
|
headingLabel={labels.singular}
|
|
101
105
|
useNavigationGuard={useTanStackNavigationGuard}
|
|
102
106
|
onCancel={() =>
|
|
@@ -334,12 +334,14 @@ export const EditView = ({
|
|
|
334
334
|
data: _data,
|
|
335
335
|
patches,
|
|
336
336
|
systemPath,
|
|
337
|
+
systemAvailableLocales,
|
|
337
338
|
}: {
|
|
338
339
|
// biome-ignore lint/suspicious/noExplicitAny: data is collection-specific
|
|
339
340
|
data: any
|
|
340
341
|
// biome-ignore lint/suspicious/noExplicitAny: patches list shape
|
|
341
342
|
patches: any[]
|
|
342
343
|
systemPath?: string | null
|
|
344
|
+
systemAvailableLocales?: string[]
|
|
343
345
|
}) => {
|
|
344
346
|
try {
|
|
345
347
|
await updateCollectionDocumentWithPatches({
|
|
@@ -350,6 +352,7 @@ export const EditView = ({
|
|
|
350
352
|
versionId: initialData.versionId as string | undefined,
|
|
351
353
|
locale,
|
|
352
354
|
...(systemPath ? { path: systemPath } : {}),
|
|
355
|
+
...(systemAvailableLocales ? { availableLocales: systemAvailableLocales } : {}),
|
|
353
356
|
},
|
|
354
357
|
})
|
|
355
358
|
|
|
@@ -404,6 +407,7 @@ export const EditView = ({
|
|
|
404
407
|
adminConfig={adminConfig}
|
|
405
408
|
useAsTitle={collectionDefinition.useAsTitle}
|
|
406
409
|
useAsPath={collectionDefinition.useAsPath}
|
|
410
|
+
advertiseLocales={collectionDefinition.advertiseLocales}
|
|
407
411
|
headingLabel={labels.singular}
|
|
408
412
|
initialLocale={locale}
|
|
409
413
|
onLocaleChange={handleLocaleChange}
|
|
@@ -21,10 +21,22 @@ import { ensureCollection } from '../../integrations/api-utils.js'
|
|
|
21
21
|
|
|
22
22
|
export const createCollectionDocument = createServerFn({ method: 'POST' })
|
|
23
23
|
.inputValidator(
|
|
24
|
-
(input: {
|
|
24
|
+
(input: {
|
|
25
|
+
collection: string
|
|
26
|
+
data: any
|
|
27
|
+
locale?: string
|
|
28
|
+
path?: string
|
|
29
|
+
availableLocales?: string[]
|
|
30
|
+
}) => input
|
|
25
31
|
)
|
|
26
32
|
.handler(async ({ data: input }) => {
|
|
27
|
-
const {
|
|
33
|
+
const {
|
|
34
|
+
collection: path,
|
|
35
|
+
data: documentData,
|
|
36
|
+
locale,
|
|
37
|
+
path: explicitPath,
|
|
38
|
+
availableLocales,
|
|
39
|
+
} = input
|
|
28
40
|
const logger = getLogger()
|
|
29
41
|
const config = await ensureCollection(path)
|
|
30
42
|
if (!config) {
|
|
@@ -52,6 +64,7 @@ export const createCollectionDocument = createServerFn({ method: 'POST' })
|
|
|
52
64
|
status: documentData.status,
|
|
53
65
|
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
54
66
|
path: explicitPath,
|
|
67
|
+
availableLocales,
|
|
55
68
|
})
|
|
56
69
|
|
|
57
70
|
return { status: 'ok' as const }
|
|
@@ -88,6 +88,10 @@ const getDocumentFn = createServerFn({ method: 'GET' })
|
|
|
88
88
|
populate,
|
|
89
89
|
depth: resolvedDepth,
|
|
90
90
|
status: 'any',
|
|
91
|
+
// Admin edit path: show the RAW per-locale values — untranslated localized
|
|
92
|
+
// fields stay empty (the signal to use "Copy to Locale"), never falling
|
|
93
|
+
// back to the default locale. Overrides the client's `'fallback'` default.
|
|
94
|
+
onMissingLocale: 'empty',
|
|
91
95
|
// Admin edit path: tolerate schema-mismatch warnings rather than
|
|
92
96
|
// hard-failing the load. Warnings (if any) come back on the document
|
|
93
97
|
// as `_restoreWarnings` and the edit form surfaces them via an Alert.
|
|
@@ -148,12 +152,29 @@ const getDocumentFn = createServerFn({ method: 'GET' })
|
|
|
148
152
|
| string[]
|
|
149
153
|
| undefined
|
|
150
154
|
|
|
155
|
+
// Same preservation for the available-locales metadata — both are stripped
|
|
156
|
+
// by the Zod parse but the sidebar widget needs them at edit time:
|
|
157
|
+
// `availableLocales` — the stored editorial set → initialises the
|
|
158
|
+
// form-context slot / the checked state.
|
|
159
|
+
// `_availableVersionLocales` — the ledger fact → drives each row's intent.
|
|
160
|
+
// See docs/I18N.md.
|
|
161
|
+
const availableLocales = (serialised as Record<string, any>).availableLocales as
|
|
162
|
+
| string[]
|
|
163
|
+
| undefined
|
|
164
|
+
const availableVersionLocales = (serialised as Record<string, any>)._availableVersionLocales as
|
|
165
|
+
| string[]
|
|
166
|
+
| undefined
|
|
167
|
+
|
|
151
168
|
return {
|
|
152
169
|
...(parsed as Record<string, any>),
|
|
153
170
|
_publishedVersion: publishedVersion,
|
|
154
171
|
...(restoreWarnings && restoreWarnings.length > 0
|
|
155
172
|
? { _restoreWarnings: restoreWarnings }
|
|
156
173
|
: {}),
|
|
174
|
+
...(Array.isArray(availableLocales) ? { availableLocales } : {}),
|
|
175
|
+
...(Array.isArray(availableVersionLocales)
|
|
176
|
+
? { _availableVersionLocales: availableVersionLocales }
|
|
177
|
+
: {}),
|
|
157
178
|
}
|
|
158
179
|
})
|
|
159
180
|
|
|
@@ -82,6 +82,11 @@ export const getCollectionDocuments = createServerFn({ method: 'GET' })
|
|
|
82
82
|
pageSize,
|
|
83
83
|
select: params.fields,
|
|
84
84
|
status: 'any',
|
|
85
|
+
// Admin list: show the raw per-locale state (untranslated docs render
|
|
86
|
+
// empty in the active locale's columns) rather than falling back to the
|
|
87
|
+
// default locale. Consistent with the edit view; overrides the client's
|
|
88
|
+
// `'fallback'` default.
|
|
89
|
+
onMissingLocale: 'empty',
|
|
85
90
|
})
|
|
86
91
|
|
|
87
92
|
// Decorate each doc with `hasPublishedVersion` so the list UI can show a
|
|
@@ -29,10 +29,19 @@ export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POS
|
|
|
29
29
|
versionId?: string
|
|
30
30
|
locale?: string
|
|
31
31
|
path?: string
|
|
32
|
+
availableLocales?: string[]
|
|
32
33
|
}) => input
|
|
33
34
|
)
|
|
34
35
|
.handler(async ({ data: input }) => {
|
|
35
|
-
const {
|
|
36
|
+
const {
|
|
37
|
+
collection: path,
|
|
38
|
+
id,
|
|
39
|
+
patches,
|
|
40
|
+
versionId,
|
|
41
|
+
locale,
|
|
42
|
+
path: explicitPath,
|
|
43
|
+
availableLocales,
|
|
44
|
+
} = input
|
|
36
45
|
const logger = getLogger()
|
|
37
46
|
const config = await ensureCollection(path)
|
|
38
47
|
if (!config) {
|
|
@@ -61,6 +70,7 @@ export const updateCollectionDocumentWithPatches = createServerFn({ method: 'POS
|
|
|
61
70
|
documentVersionId: versionId,
|
|
62
71
|
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
63
72
|
path: explicitPath,
|
|
73
|
+
availableLocales,
|
|
64
74
|
})
|
|
65
75
|
|
|
66
76
|
return { status: 'ok' as const }
|