@nitrogenbuilder/connector-payload 1.1.0 → 1.2.0
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/collections/NitrogenComponentUsage.js +3 -21
- package/dist/collections/NitrogenTemplates.js +8 -0
- package/dist/components/NitrogenComponentInventoryClient.js +1 -1
- package/dist/endpoints/all.js +3 -0
- package/dist/endpoints/batch.js +9 -0
- package/dist/endpoints/collection-endpoints.js +119 -3
- package/dist/endpoints/helpers.js +2 -1
- package/dist/endpoints/menu.js +2 -0
- package/dist/endpoints/sitemap.js +47 -7
- package/dist/endpoints/templates.js +116 -0
- package/dist/endpoints/translation-status.d.ts +11 -0
- package/dist/endpoints/translation-status.js +105 -0
- package/dist/frontend/NitrogenPageClient.d.ts +6 -1
- package/dist/frontend/NitrogenPageClient.js +6 -2
- package/dist/frontend/NitrogenWrapper.d.ts +8 -1
- package/dist/frontend/NitrogenWrapper.js +25 -3
- package/dist/index.d.ts +22 -0
- package/dist/index.js +69 -2
- package/dist/inventory/indexing.d.ts +2 -5
- package/dist/inventory/indexing.js +129 -73
- package/dist/localization.d.ts +121 -0
- package/dist/localization.js +563 -0
- package/dist/types.d.ts +3 -5
- package/package.json +1 -1
|
@@ -25,10 +25,6 @@ export const NitrogenComponentUsage = {
|
|
|
25
25
|
type: 'text',
|
|
26
26
|
required: true,
|
|
27
27
|
},
|
|
28
|
-
{
|
|
29
|
-
name: 'depth',
|
|
30
|
-
type: 'number',
|
|
31
|
-
},
|
|
32
28
|
{
|
|
33
29
|
name: 'sourceCollection',
|
|
34
30
|
type: 'text',
|
|
@@ -56,26 +52,12 @@ export const NitrogenComponentUsage = {
|
|
|
56
52
|
name: 'sourceStatus',
|
|
57
53
|
type: 'text',
|
|
58
54
|
},
|
|
55
|
+
// Usage answers "which components appear in which documents". A module's
|
|
56
|
+
// position in the tree is deliberately not stored: nothing read it, and
|
|
57
|
+
// keeping it meant every reorder rewrote every row of the document.
|
|
59
58
|
{
|
|
60
59
|
name: 'moduleId',
|
|
61
60
|
type: 'text',
|
|
62
61
|
},
|
|
63
|
-
{
|
|
64
|
-
name: 'modulePath',
|
|
65
|
-
type: 'text',
|
|
66
|
-
required: true,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: 'parentComponentName',
|
|
70
|
-
type: 'text',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'parentModuleId',
|
|
74
|
-
type: 'text',
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'parentModulePath',
|
|
78
|
-
type: 'text',
|
|
79
|
-
},
|
|
80
62
|
],
|
|
81
63
|
};
|
|
@@ -93,5 +93,13 @@ export const NitrogenTemplates = {
|
|
|
93
93
|
},
|
|
94
94
|
],
|
|
95
95
|
},
|
|
96
|
+
{
|
|
97
|
+
name: 'nitrogenTranslationStatus',
|
|
98
|
+
type: 'json',
|
|
99
|
+
admin: {
|
|
100
|
+
hidden: true,
|
|
101
|
+
description: 'Per-language translation coverage, computed by Nitrogen on save',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
96
104
|
],
|
|
97
105
|
};
|
|
@@ -548,7 +548,7 @@ export default function NitrogenComponentInventoryClient({ adminRoute, component
|
|
|
548
548
|
display: 'block',
|
|
549
549
|
padding: 12,
|
|
550
550
|
textDecoration: 'none',
|
|
551
|
-
}, children: [_jsx("div", { style: { fontWeight: 600 }, children: usage.sourceTitle || usage.sourceSlug || usage.sourceDocumentId }), _jsxs("div", { style: { color: payloadTheme.mutedText, fontSize: 12, marginTop: 2 }, children: [usage.sourceCollection,
|
|
551
|
+
}, children: [_jsx("div", { style: { fontWeight: 600 }, children: usage.sourceTitle || usage.sourceSlug || usage.sourceDocumentId }), _jsxs("div", { style: { color: payloadTheme.mutedText, fontSize: 12, marginTop: 2 }, children: [usage.sourceCollection, usage.sourceSlug ? ` - ${usage.sourceSlug}` : ''] })] }, usage.usageKey))) })) : (_jsx("p", { style: { color: payloadTheme.mutedText, margin: 0 }, children: "No indexed usages yet." }))] }), _jsxs("div", { children: [_jsx("h3", { style: { color: payloadTheme.text, margin: '0 0 10px', fontSize: 15 }, children: "Props" }), definition ? (Object.values(definition.categories).map((category) => renderCategory(category))) : (_jsx("p", { style: { color: payloadTheme.mutedText, margin: 0 }, children: "No manifest definition available." }))] }), _jsxs("div", { children: [_jsx("h3", { style: { color: payloadTheme.text, margin: '0 0 10px', fontSize: 15 }, children: "Update Prop Across Site" }), editableProps.length ? (_jsxs("div", { style: {
|
|
552
552
|
border: `1px solid ${payloadTheme.border}`,
|
|
553
553
|
borderRadius: 8,
|
|
554
554
|
display: 'grid',
|
package/dist/endpoints/all.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDocumentPermalink, getDocumentRelativePermalink, getNitrogenSettings, } from './helpers.js';
|
|
2
2
|
import { resolveCollection } from '../collection-registry.js';
|
|
3
|
+
import { getRequestLanguage, resolveLocaleOptions } from '../localization.js';
|
|
3
4
|
export const allEndpoints = [
|
|
4
5
|
// GET /api/nitrogen/v1/all — List all items with filtering
|
|
5
6
|
{
|
|
@@ -17,6 +18,7 @@ export const allEndpoints = [
|
|
|
17
18
|
const paged = parseInt(url.searchParams.get('paged') || '1', 10);
|
|
18
19
|
const perPage = parseInt(url.searchParams.get('per_page') || '20', 10);
|
|
19
20
|
const statuses = statusParam.split(',').map((s) => s.trim());
|
|
21
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
20
22
|
const collection = resolveCollection(postType);
|
|
21
23
|
const where = {};
|
|
22
24
|
if (!statuses.includes('any')) {
|
|
@@ -29,6 +31,7 @@ export const allEndpoints = [
|
|
|
29
31
|
page: paged,
|
|
30
32
|
limit: perPage,
|
|
31
33
|
depth: 0,
|
|
34
|
+
...localeOptions,
|
|
32
35
|
});
|
|
33
36
|
const items = result.docs.map((doc) => {
|
|
34
37
|
const document = doc;
|
package/dist/endpoints/batch.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveCollection } from "../collection-registry.js";
|
|
2
2
|
import { getNitrogenSettings, buildDynamicData, buildPageResponse, getDocumentPermalink, getDocumentRelativePermalink, } from "./helpers.js";
|
|
3
|
+
import { getRequestLanguage, resolveLocaleOptions } from "../localization.js";
|
|
3
4
|
function toArray(value) {
|
|
4
5
|
if (Array.isArray(value)) {
|
|
5
6
|
return value.map((item) => String(item).trim()).filter(Boolean);
|
|
@@ -98,6 +99,9 @@ export const batchEndpoints = [
|
|
|
98
99
|
}
|
|
99
100
|
const settings = await getNitrogenSettings(payload);
|
|
100
101
|
const results = {};
|
|
102
|
+
// Request-level language (?lang=/?locale=) applies to every batched
|
|
103
|
+
// query; a per-request `lang`/`locale` param overrides it.
|
|
104
|
+
const requestLang = getRequestLanguage(req);
|
|
101
105
|
// Dedup: group identical (endpoint, params) requests and run each group's
|
|
102
106
|
// query once via its first ("representative") request; fan the result out
|
|
103
107
|
// to the duplicate keys afterward.
|
|
@@ -285,6 +289,10 @@ export const batchEndpoints = [
|
|
|
285
289
|
};
|
|
286
290
|
}
|
|
287
291
|
}
|
|
292
|
+
const lang = (typeof params.lang === "string" && params.lang) ||
|
|
293
|
+
(typeof params.locale === "string" && params.locale) ||
|
|
294
|
+
requestLang;
|
|
295
|
+
const localeOptions = resolveLocaleOptions(payload, lang);
|
|
288
296
|
const result = await payload.find({
|
|
289
297
|
collection: collectionSlug,
|
|
290
298
|
where,
|
|
@@ -292,6 +300,7 @@ export const batchEndpoints = [
|
|
|
292
300
|
limit,
|
|
293
301
|
sort,
|
|
294
302
|
depth,
|
|
303
|
+
...localeOptions,
|
|
295
304
|
});
|
|
296
305
|
let data;
|
|
297
306
|
if (raw) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getNitrogenSettings, buildDynamicData, buildResolvedPageResponse, buildResolvedListItemResponse, getTemplateForType, requireAuth, canServeDocument, } from './helpers.js';
|
|
2
2
|
import { resolveTemplateForDocument } from './templateConditions.js';
|
|
3
|
+
import { buildDynamicDataMeta, computeDocTranslationStatus, fetchLocaleAllDoc, getDefaultLocaleTitle, getLocalizationSettings, getPayloadLocalization, getRequestLanguage, resolveLocaleOptions, } from '../localization.js';
|
|
3
4
|
/**
|
|
4
5
|
* Creates a complete set of CRUD endpoints for a given Payload collection.
|
|
5
6
|
*
|
|
@@ -19,6 +20,7 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
19
20
|
const url = new URL(req.url || '', 'http://localhost');
|
|
20
21
|
const statusParam = url.searchParams.get('post_status') || 'published';
|
|
21
22
|
const statuses = statusParam.split(',').map((s) => s.trim());
|
|
23
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
22
24
|
const where = {};
|
|
23
25
|
if (!statuses.includes('any')) {
|
|
24
26
|
where._status = { in: statuses };
|
|
@@ -28,6 +30,7 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
28
30
|
where,
|
|
29
31
|
limit: 0,
|
|
30
32
|
depth: 1,
|
|
33
|
+
...localeOptions,
|
|
31
34
|
});
|
|
32
35
|
const items = await Promise.all(result.docs.map((doc) => buildResolvedListItemResponse(payload, doc, settings, collectionSlug)));
|
|
33
36
|
return Response.json(items);
|
|
@@ -67,9 +70,10 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
67
70
|
handler: async (req) => {
|
|
68
71
|
const { payload, routeParams } = req;
|
|
69
72
|
const id = routeParams?.id;
|
|
73
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
70
74
|
try {
|
|
71
75
|
const [doc, settings, headerTemplate, footerTemplate] = await Promise.all([
|
|
72
|
-
payload.findByID({ collection, id, depth: 1 }),
|
|
76
|
+
payload.findByID({ collection, id, depth: 1, ...localeOptions }),
|
|
73
77
|
getNitrogenSettings(payload),
|
|
74
78
|
getTemplateForType(payload, 'header'),
|
|
75
79
|
getTemplateForType(payload, 'footer'),
|
|
@@ -79,9 +83,22 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
79
83
|
}
|
|
80
84
|
const pageTemplate = await resolveTemplateForDocument(payload, doc, collectionSlug, settings);
|
|
81
85
|
const dynamicData = buildDynamicData(doc, settings);
|
|
86
|
+
// When a language was requested, the top-level `title` must stay
|
|
87
|
+
// the default-locale one — the editor edits default-language field
|
|
88
|
+
// data only and PATCHes `title` back on save. Only dynamic_data is
|
|
89
|
+
// locale-resolved. One locale:'all' fetch serves title + meta.
|
|
90
|
+
const localeAllDoc = localeOptions.locale
|
|
91
|
+
? await fetchLocaleAllDoc(payload, collectionSlug, id)
|
|
92
|
+
: undefined;
|
|
93
|
+
const dynamicDataMeta = await buildDynamicDataMeta(payload, collectionSlug, id, localeAllDoc);
|
|
94
|
+
const titleOverride = localeOptions.locale
|
|
95
|
+
? getDefaultLocaleTitle(payload, localeAllDoc ?? null)
|
|
96
|
+
: null;
|
|
82
97
|
const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData, collectionSlug);
|
|
83
98
|
return Response.json({
|
|
84
99
|
...response,
|
|
100
|
+
...(titleOverride !== null ? { title: titleOverride } : {}),
|
|
101
|
+
...(dynamicDataMeta ? { dynamic_data_meta: dynamicDataMeta } : {}),
|
|
85
102
|
template: pageTemplate,
|
|
86
103
|
headerTemplate,
|
|
87
104
|
footerTemplate,
|
|
@@ -92,6 +109,36 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
92
109
|
}
|
|
93
110
|
},
|
|
94
111
|
},
|
|
112
|
+
// GET /api/nitrogen/v1/{prefix}/:id/dynamic-data — Dynamic data (+ meta)
|
|
113
|
+
// for one item, optionally in a specific language (?lang=es). Used by the
|
|
114
|
+
// editor on language switch, without reloading the whole page.
|
|
115
|
+
{
|
|
116
|
+
path: `/nitrogen/v1/${endpointPrefix}/:id/dynamic-data`,
|
|
117
|
+
method: 'get',
|
|
118
|
+
handler: async (req) => {
|
|
119
|
+
const { payload, routeParams } = req;
|
|
120
|
+
const id = routeParams?.id;
|
|
121
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
122
|
+
try {
|
|
123
|
+
const [doc, settings] = await Promise.all([
|
|
124
|
+
payload.findByID({ collection, id, depth: 1, ...localeOptions }),
|
|
125
|
+
getNitrogenSettings(payload),
|
|
126
|
+
]);
|
|
127
|
+
if (!canServeDocument(req, doc, settings)) {
|
|
128
|
+
return Response.json({ error: 'Not found' }, { status: 404 });
|
|
129
|
+
}
|
|
130
|
+
const dynamicData = buildDynamicData(doc, settings);
|
|
131
|
+
const dynamicDataMeta = await buildDynamicDataMeta(payload, collectionSlug, id);
|
|
132
|
+
return Response.json({
|
|
133
|
+
dynamic_data: dynamicData,
|
|
134
|
+
...(dynamicDataMeta ? { dynamic_data_meta: dynamicDataMeta } : {}),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return Response.json({ error: 'Not found' }, { status: 404 });
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
},
|
|
95
142
|
// PATCH /api/nitrogen/v1/{prefix}/:id — Update an item
|
|
96
143
|
{
|
|
97
144
|
path: `/nitrogen/v1/${endpointPrefix}/:id`,
|
|
@@ -145,6 +192,34 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
145
192
|
if (data.settings !== undefined) {
|
|
146
193
|
updateData.pageSettings = data.settings;
|
|
147
194
|
}
|
|
195
|
+
// Localization enabled → recompute per-language translation status
|
|
196
|
+
// from the saved module tree. Advisory only — never blocks the save.
|
|
197
|
+
if (updateData.nitrogenData !== undefined) {
|
|
198
|
+
try {
|
|
199
|
+
const settings = await getNitrogenSettings(payload);
|
|
200
|
+
const localization = getLocalizationSettings(settings);
|
|
201
|
+
if (localization) {
|
|
202
|
+
const translationStatus = await computeDocTranslationStatus({
|
|
203
|
+
payload,
|
|
204
|
+
collectionSlug,
|
|
205
|
+
docId: id,
|
|
206
|
+
nitrogenData: updateData.nitrogenData,
|
|
207
|
+
localization,
|
|
208
|
+
});
|
|
209
|
+
if (translationStatus) {
|
|
210
|
+
updateData.nitrogenTranslationStatus = translationStatus;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Status is advisory — never block the save.
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// The editor edits default-language field data only — pin the write
|
|
219
|
+
// to the default locale. Without this, Payload's createLocalReq falls
|
|
220
|
+
// back to req.locale / req.query.locale, so a stray `?locale=` on the
|
|
221
|
+
// request would silently retarget localized fields (title).
|
|
222
|
+
const payloadLocalization = getPayloadLocalization(payload);
|
|
148
223
|
try {
|
|
149
224
|
await payload.update({
|
|
150
225
|
collection,
|
|
@@ -152,6 +227,9 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
152
227
|
data: updateData,
|
|
153
228
|
depth: 0,
|
|
154
229
|
overrideAccess: true,
|
|
230
|
+
...(payloadLocalization
|
|
231
|
+
? { locale: payloadLocalization.defaultLocale }
|
|
232
|
+
: {}),
|
|
155
233
|
req,
|
|
156
234
|
});
|
|
157
235
|
}
|
|
@@ -183,8 +261,16 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
183
261
|
if (!slug) {
|
|
184
262
|
return Response.json({ error: 'Slug is required' }, { status: 400 });
|
|
185
263
|
}
|
|
264
|
+
const lang = body?.lang || body?.data?.lang || getRequestLanguage(req);
|
|
265
|
+
const localeOptions = resolveLocaleOptions(payload, lang);
|
|
186
266
|
const [result, settings, headerTemplate, footerTemplate] = await Promise.all([
|
|
187
|
-
payload.find({
|
|
267
|
+
payload.find({
|
|
268
|
+
collection,
|
|
269
|
+
where: { slug: { equals: slug } },
|
|
270
|
+
limit: 1,
|
|
271
|
+
depth: 1,
|
|
272
|
+
...localeOptions,
|
|
273
|
+
}),
|
|
188
274
|
getNitrogenSettings(payload),
|
|
189
275
|
getTemplateForType(payload, 'header'),
|
|
190
276
|
getTemplateForType(payload, 'footer'),
|
|
@@ -198,9 +284,19 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
198
284
|
}
|
|
199
285
|
const pageTemplate = await resolveTemplateForDocument(payload, doc, collectionSlug, settings);
|
|
200
286
|
const dynamicData = buildDynamicData(doc, settings);
|
|
287
|
+
// See GET /:id — keep the round-tripped `title` at the default locale.
|
|
288
|
+
const localeAllDoc = localeOptions.locale
|
|
289
|
+
? await fetchLocaleAllDoc(payload, collectionSlug, doc.id)
|
|
290
|
+
: undefined;
|
|
291
|
+
const dynamicDataMeta = await buildDynamicDataMeta(payload, collectionSlug, doc.id, localeAllDoc);
|
|
292
|
+
const titleOverride = localeOptions.locale
|
|
293
|
+
? getDefaultLocaleTitle(payload, localeAllDoc ?? null)
|
|
294
|
+
: null;
|
|
201
295
|
const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData, collectionSlug);
|
|
202
296
|
return Response.json({
|
|
203
297
|
...response,
|
|
298
|
+
...(titleOverride !== null ? { title: titleOverride } : {}),
|
|
299
|
+
...(dynamicDataMeta ? { dynamic_data_meta: dynamicDataMeta } : {}),
|
|
204
300
|
template: pageTemplate,
|
|
205
301
|
headerTemplate,
|
|
206
302
|
footerTemplate,
|
|
@@ -214,8 +310,18 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
214
310
|
handler: async (req) => {
|
|
215
311
|
const { payload, routeParams } = req;
|
|
216
312
|
const slug = routeParams?.slug;
|
|
313
|
+
// When the host collection's slug field is localized, the locale-aware
|
|
314
|
+
// query matches the localized slug (falling back to the default
|
|
315
|
+
// locale's slug via fallbackLocale).
|
|
316
|
+
const localeOptions = resolveLocaleOptions(payload, getRequestLanguage(req));
|
|
217
317
|
const [result, settings, headerTemplate, footerTemplate] = await Promise.all([
|
|
218
|
-
payload.find({
|
|
318
|
+
payload.find({
|
|
319
|
+
collection,
|
|
320
|
+
where: { slug: { equals: slug } },
|
|
321
|
+
limit: 1,
|
|
322
|
+
depth: 1,
|
|
323
|
+
...localeOptions,
|
|
324
|
+
}),
|
|
219
325
|
getNitrogenSettings(payload),
|
|
220
326
|
getTemplateForType(payload, 'header'),
|
|
221
327
|
getTemplateForType(payload, 'footer'),
|
|
@@ -229,9 +335,19 @@ export function createCollectionEndpoints(collectionSlug, endpointPrefix) {
|
|
|
229
335
|
}
|
|
230
336
|
const pageTemplate = await resolveTemplateForDocument(payload, doc, collectionSlug, settings);
|
|
231
337
|
const dynamicData = buildDynamicData(doc, settings);
|
|
338
|
+
// See GET /:id — keep the round-tripped `title` at the default locale.
|
|
339
|
+
const localeAllDoc = localeOptions.locale
|
|
340
|
+
? await fetchLocaleAllDoc(payload, collectionSlug, doc.id)
|
|
341
|
+
: undefined;
|
|
342
|
+
const dynamicDataMeta = await buildDynamicDataMeta(payload, collectionSlug, doc.id, localeAllDoc);
|
|
343
|
+
const titleOverride = localeOptions.locale
|
|
344
|
+
? getDefaultLocaleTitle(payload, localeAllDoc ?? null)
|
|
345
|
+
: null;
|
|
232
346
|
const response = await buildResolvedPageResponse(payload, doc, settings, dynamicData, collectionSlug);
|
|
233
347
|
return Response.json({
|
|
234
348
|
...response,
|
|
349
|
+
...(titleOverride !== null ? { title: titleOverride } : {}),
|
|
350
|
+
...(dynamicDataMeta ? { dynamic_data_meta: dynamicDataMeta } : {}),
|
|
235
351
|
template: pageTemplate,
|
|
236
352
|
headerTemplate,
|
|
237
353
|
footerTemplate,
|
|
@@ -30,7 +30,8 @@ export function buildDynamicData(doc, globalSettings) {
|
|
|
30
30
|
const rawDoc = doc;
|
|
31
31
|
const docData = Object.fromEntries(Object.entries(rawDoc).filter(([key]) => key !== 'nitrogenData' &&
|
|
32
32
|
key !== 'pageSettings' &&
|
|
33
|
-
key !== 'templateSettings'
|
|
33
|
+
key !== 'templateSettings' &&
|
|
34
|
+
key !== 'nitrogenTranslationStatus'));
|
|
34
35
|
const rawContent = typeof rawDoc.content === 'string'
|
|
35
36
|
? rawDoc.content
|
|
36
37
|
: '';
|
package/dist/endpoints/menu.js
CHANGED
|
@@ -2,6 +2,8 @@ export const menuEndpoints = [
|
|
|
2
2
|
// POST /api/nitrogen/v1/menu — Get menu items (stub)
|
|
3
3
|
// WordPress nav menus are WP-specific. Returns empty array.
|
|
4
4
|
// Implement with a NavigationMenus collection if menus are needed.
|
|
5
|
+
// `?lang=` is accepted (and trivially honored) — thread it through
|
|
6
|
+
// resolveLocaleOptions if this stub gains a real menu query.
|
|
5
7
|
{
|
|
6
8
|
path: '/nitrogen/v1/menu',
|
|
7
9
|
method: 'post',
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { buildRelativePermalink, getRegisteredCollections, resolveCollection, } from '../collection-registry.js';
|
|
14
14
|
import { findAllDocs } from '../inventory/indexing.js';
|
|
15
|
+
import { getPayloadLocalization, } from '../localization.js';
|
|
15
16
|
/** Internal Nitrogen collections that must never appear in the sitemap. */
|
|
16
17
|
const EXCLUDED_COLLECTIONS = new Set([
|
|
17
18
|
'nitrogen-templates',
|
|
@@ -24,18 +25,30 @@ const EXCLUDED_COLLECTIONS = new Set([
|
|
|
24
25
|
* Payload collections may or may not have drafts enabled (the `_status`
|
|
25
26
|
* field). We first try filtering by `_status: 'published'`; if that throws
|
|
26
27
|
* (collection has no `_status`), we fall back to fetching all docs.
|
|
28
|
+
*
|
|
29
|
+
* With localization configured, docs are fetched at `locale: 'all'` so a
|
|
30
|
+
* localized slug field yields every locale's slug in one query.
|
|
27
31
|
*/
|
|
28
|
-
async function fetchPublishedSitemapDocs(payload, collection) {
|
|
32
|
+
async function fetchPublishedSitemapDocs(payload, collection, localization) {
|
|
29
33
|
const select = { slug: true, updatedAt: true };
|
|
34
|
+
const locale = localization ? 'all' : undefined;
|
|
30
35
|
try {
|
|
31
36
|
return await findAllDocs(payload, collection, {
|
|
32
37
|
where: { _status: { equals: 'published' } },
|
|
33
38
|
select,
|
|
39
|
+
locale,
|
|
34
40
|
});
|
|
35
41
|
}
|
|
36
42
|
catch {
|
|
37
|
-
return findAllDocs(payload, collection, { select });
|
|
43
|
+
return findAllDocs(payload, collection, { select, locale });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Resolve a (possibly locale-keyed) slug value for one locale. */
|
|
47
|
+
function slugForLocale(slug, locale, defaultLocale) {
|
|
48
|
+
if (slug && typeof slug === 'object' && !Array.isArray(slug)) {
|
|
49
|
+
return String(slug[locale] ?? slug[defaultLocale] ?? '');
|
|
38
50
|
}
|
|
51
|
+
return String(slug ?? '');
|
|
39
52
|
}
|
|
40
53
|
/** Coerce a doc's updatedAt into an ISO-8601 string, or null when absent. */
|
|
41
54
|
function toIsoString(value) {
|
|
@@ -89,17 +102,44 @@ export const sitemapEndpoints = [
|
|
|
89
102
|
if (EXCLUDED_COLLECTIONS.has(collection)) {
|
|
90
103
|
return Response.json({ error: 'Post type not eligible for sitemap' }, { status: 404 });
|
|
91
104
|
}
|
|
105
|
+
const localization = getPayloadLocalization(payload);
|
|
92
106
|
let docs;
|
|
93
107
|
try {
|
|
94
|
-
docs = await fetchPublishedSitemapDocs(payload, collection);
|
|
108
|
+
docs = await fetchPublishedSitemapDocs(payload, collection, localization);
|
|
95
109
|
}
|
|
96
110
|
catch {
|
|
97
111
|
return Response.json({ error: 'No URLs for this post type' }, { status: 404 });
|
|
98
112
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
// With localization configured, each doc emits its canonical
|
|
114
|
+
// default-locale URL plus one `/{lang}/`-prefixed entry per non-default
|
|
115
|
+
// locale, using the localized slug when present (falling back to the
|
|
116
|
+
// default locale's slug otherwise).
|
|
117
|
+
const urls = docs.flatMap((doc) => {
|
|
118
|
+
const lastmod = toIsoString(doc.updatedAt);
|
|
119
|
+
if (!localization) {
|
|
120
|
+
return [
|
|
121
|
+
{
|
|
122
|
+
loc: buildRelativePermalink(postType, String(doc.slug || '')),
|
|
123
|
+
lastmod,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
const { locales, defaultLocale } = localization;
|
|
128
|
+
const defaultSlug = slugForLocale(doc.slug, defaultLocale, defaultLocale);
|
|
129
|
+
const entries = [
|
|
130
|
+
{ loc: buildRelativePermalink(postType, defaultSlug), lastmod },
|
|
131
|
+
];
|
|
132
|
+
for (const locale of locales) {
|
|
133
|
+
if (locale === defaultLocale)
|
|
134
|
+
continue;
|
|
135
|
+
const localizedSlug = slugForLocale(doc.slug, locale, defaultLocale);
|
|
136
|
+
entries.push({
|
|
137
|
+
loc: `/${locale}${buildRelativePermalink(postType, localizedSlug)}`,
|
|
138
|
+
lastmod,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return entries;
|
|
142
|
+
});
|
|
103
143
|
if (urls.length === 0) {
|
|
104
144
|
return Response.json({ error: 'No URLs for this post type' }, { status: 404 });
|
|
105
145
|
}
|