nextjs-cms 0.8.9 → 0.9.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/LICENSE +21 -21
- package/README.md +290 -290
- package/dist/api/index.d.ts +92 -9
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/lib/serverActions.d.ts +64 -9
- package/dist/api/lib/serverActions.d.ts.map +1 -1
- package/dist/api/lib/serverActions.js +463 -90
- package/dist/api/root.d.ts +184 -18
- package/dist/api/root.d.ts.map +1 -1
- package/dist/api/routers/accountSettings.d.ts +2 -2
- package/dist/api/routers/accountSettings.js +10 -10
- package/dist/api/routers/admins.js +11 -11
- package/dist/api/routers/auth.d.ts +1 -1
- package/dist/api/routers/config.d.ts +13 -0
- package/dist/api/routers/config.d.ts.map +1 -1
- package/dist/api/routers/config.js +4 -0
- package/dist/api/routers/cpanel.js +7 -7
- package/dist/api/routers/fields.d.ts +1 -0
- package/dist/api/routers/fields.d.ts.map +1 -1
- package/dist/api/routers/fields.js +39 -6
- package/dist/api/routers/gallery.js +1 -1
- package/dist/api/routers/hasItemsSection.d.ts +41 -2
- package/dist/api/routers/hasItemsSection.d.ts.map +1 -1
- package/dist/api/routers/hasItemsSection.js +43 -2
- package/dist/api/routers/logs.js +1 -1
- package/dist/api/routers/navigation.d.ts +3 -3
- package/dist/api/routers/simpleSection.d.ts +31 -1
- package/dist/api/routers/simpleSection.d.ts.map +1 -1
- package/dist/api/routers/simpleSection.js +44 -2
- package/dist/api/trpc.js +2 -2
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/lib/actions.d.ts +3 -3
- package/dist/auth/lib/actions.d.ts.map +1 -1
- package/dist/auth/lib/actions.js +14 -14
- package/dist/auth/react.d.ts +2 -2
- package/dist/auth/react.d.ts.map +1 -1
- package/dist/auth/react.js +7 -7
- package/dist/cli/lib/db-config.js +10 -10
- package/dist/cli/lib/update-sections.d.ts.map +1 -1
- package/dist/cli/lib/update-sections.js +145 -9
- package/dist/cli/utils/schema-generator.d.ts +20 -0
- package/dist/cli/utils/schema-generator.d.ts.map +1 -1
- package/dist/cli/utils/schema-generator.js +40 -0
- package/dist/core/config/config-loader.d.ts +49 -5
- package/dist/core/config/config-loader.d.ts.map +1 -1
- package/dist/core/config/config-loader.js +100 -21
- package/dist/core/config/index.d.ts +2 -2
- package/dist/core/config/index.d.ts.map +1 -1
- package/dist/core/config/index.js +1 -1
- package/dist/core/db/table-checker/MysqlTable.js +8 -8
- package/dist/core/factories/FieldFactory.d.ts +5 -3
- package/dist/core/factories/FieldFactory.d.ts.map +1 -1
- package/dist/core/factories/FieldFactory.js +74 -16
- package/dist/core/factories/section-factory-with-esbuild.d.ts.map +1 -1
- package/dist/core/factories/section-factory-with-esbuild.js +15 -9
- package/dist/core/factories/section-factory-with-jiti.d.ts.map +1 -1
- package/dist/core/factories/section-factory-with-jiti.js +15 -9
- package/dist/core/fields/checkbox.d.ts +4 -1
- package/dist/core/fields/checkbox.d.ts.map +1 -1
- package/dist/core/fields/color.d.ts +4 -1
- package/dist/core/fields/color.d.ts.map +1 -1
- package/dist/core/fields/color.js +2 -2
- package/dist/core/fields/date.d.ts +4 -1
- package/dist/core/fields/date.d.ts.map +1 -1
- package/dist/core/fields/date.js +2 -2
- package/dist/core/fields/document.d.ts +4 -1
- package/dist/core/fields/document.d.ts.map +1 -1
- package/dist/core/fields/document.js +27 -18
- package/dist/core/fields/field-group.d.ts +3 -3
- package/dist/core/fields/field-group.d.ts.map +1 -1
- package/dist/core/fields/field.d.ts +11 -8
- package/dist/core/fields/field.d.ts.map +1 -1
- package/dist/core/fields/field.js +15 -11
- package/dist/core/fields/map.d.ts +4 -1
- package/dist/core/fields/map.d.ts.map +1 -1
- package/dist/core/fields/map.js +2 -2
- package/dist/core/fields/number.d.ts +26 -1
- package/dist/core/fields/number.d.ts.map +1 -1
- package/dist/core/fields/number.js +16 -7
- package/dist/core/fields/password.d.ts +4 -1
- package/dist/core/fields/password.d.ts.map +1 -1
- package/dist/core/fields/password.js +3 -3
- package/dist/core/fields/photo.d.ts +4 -1
- package/dist/core/fields/photo.d.ts.map +1 -1
- package/dist/core/fields/photo.js +17 -17
- package/dist/core/fields/richText.d.ts +17 -3
- package/dist/core/fields/richText.d.ts.map +1 -1
- package/dist/core/fields/richText.js +20 -8
- package/dist/core/fields/select.d.ts +10 -3
- package/dist/core/fields/select.d.ts.map +1 -1
- package/dist/core/fields/select.js +27 -34
- package/dist/core/fields/selectMultiple.d.ts +8 -4
- package/dist/core/fields/selectMultiple.d.ts.map +1 -1
- package/dist/core/fields/selectMultiple.js +32 -24
- package/dist/core/fields/slug.d.ts +16 -1
- package/dist/core/fields/slug.d.ts.map +1 -1
- package/dist/core/fields/slug.js +3 -3
- package/dist/core/fields/tags.d.ts +6 -3
- package/dist/core/fields/tags.d.ts.map +1 -1
- package/dist/core/fields/tags.js +26 -19
- package/dist/core/fields/text.d.ts +24 -1
- package/dist/core/fields/text.d.ts.map +1 -1
- package/dist/core/fields/text.js +12 -3
- package/dist/core/fields/textArea.d.ts +24 -1
- package/dist/core/fields/textArea.d.ts.map +1 -1
- package/dist/core/fields/textArea.js +9 -0
- package/dist/core/fields/video.d.ts +4 -1
- package/dist/core/fields/video.d.ts.map +1 -1
- package/dist/core/fields/video.js +14 -12
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/localization/index.d.ts +3 -0
- package/dist/core/localization/index.d.ts.map +1 -0
- package/dist/core/localization/index.js +1 -0
- package/dist/core/localization/resolve-locale.d.ts +29 -0
- package/dist/core/localization/resolve-locale.d.ts.map +1 -0
- package/dist/core/localization/resolve-locale.js +43 -0
- package/dist/core/sections/category.d.ts +56 -44
- package/dist/core/sections/category.d.ts.map +1 -1
- package/dist/core/sections/category.js +3 -3
- package/dist/core/sections/hasItems.d.ts +80 -44
- package/dist/core/sections/hasItems.d.ts.map +1 -1
- package/dist/core/sections/section.d.ts +55 -28
- package/dist/core/sections/section.d.ts.map +1 -1
- package/dist/core/sections/section.js +22 -0
- package/dist/core/sections/simple.d.ts +8 -8
- package/dist/core/sections/simple.d.ts.map +1 -1
- package/dist/core/submit/ItemEditSubmit.d.ts +24 -16
- package/dist/core/submit/ItemEditSubmit.d.ts.map +1 -1
- package/dist/core/submit/ItemEditSubmit.js +62 -38
- package/dist/core/submit/LocaleSubmit.d.ts +97 -0
- package/dist/core/submit/LocaleSubmit.d.ts.map +1 -0
- package/dist/core/submit/LocaleSubmit.js +435 -0
- package/dist/core/submit/NewItemSubmit.d.ts +0 -8
- package/dist/core/submit/NewItemSubmit.d.ts.map +1 -1
- package/dist/core/submit/NewItemSubmit.js +6 -12
- package/dist/core/submit/index.d.ts +1 -0
- package/dist/core/submit/index.d.ts.map +1 -1
- package/dist/core/submit/index.js +1 -0
- package/dist/core/submit/submit.d.ts +35 -12
- package/dist/core/submit/submit.d.ts.map +1 -1
- package/dist/core/submit/submit.js +88 -69
- package/dist/db/schema.d.ts +17 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +1 -0
- package/dist/logging/log.d.ts +1 -1
- package/dist/logging/log.d.ts.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/loader.d.ts +3 -3
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/translations/base/en.d.ts +24 -2
- package/dist/translations/base/en.d.ts.map +1 -1
- package/dist/translations/base/en.js +24 -2
- package/dist/translations/client.d.ts +292 -28
- package/dist/translations/client.d.ts.map +1 -1
- package/dist/translations/client.js +2 -2
- package/dist/translations/dict-store.d.ts +2 -2
- package/dist/translations/dict-store.d.ts.map +1 -1
- package/dist/translations/dict-store.js +9 -9
- package/dist/translations/index.d.ts +5 -5
- package/dist/translations/index.d.ts.map +1 -1
- package/dist/translations/index.js +6 -6
- package/dist/translations/language-cookie.d.ts +24 -0
- package/dist/translations/language-cookie.d.ts.map +1 -0
- package/dist/translations/language-cookie.js +44 -0
- package/dist/translations/language-utils.d.ts +42 -0
- package/dist/translations/language-utils.d.ts.map +1 -0
- package/dist/translations/language-utils.js +52 -0
- package/dist/translations/server.d.ts +293 -29
- package/dist/translations/server.d.ts.map +1 -1
- package/dist/translations/server.js +5 -5
- package/dist/validators/number.d.ts +1 -1
- package/dist/validators/number.d.ts.map +1 -1
- package/dist/validators/number.js +1 -1
- package/dist/validators/select-multiple.d.ts +2 -2
- package/dist/validators/select-multiple.d.ts.map +1 -1
- package/dist/validators/select-multiple.js +1 -1
- package/package.json +7 -3
- package/dist/translations/dictionaries/ar.d.ts +0 -433
- package/dist/translations/dictionaries/ar.d.ts.map +0 -1
- package/dist/translations/dictionaries/ar.js +0 -444
- package/dist/translations/dictionaries/en.d.ts +0 -433
- package/dist/translations/dictionaries/en.d.ts.map +0 -1
- package/dist/translations/dictionaries/en.js +0 -444
|
@@ -14,12 +14,13 @@ import sharp from 'sharp';
|
|
|
14
14
|
import { readChunk } from 'read-chunk';
|
|
15
15
|
import { fileTypeFromBuffer } from 'file-type';
|
|
16
16
|
import { getCMSConfig } from '../../core/config/index.js';
|
|
17
|
+
import { resolveLocale } from '../../core/localization/index.js';
|
|
17
18
|
import { getPluginRoutes } from '../../plugins/loader.js';
|
|
18
19
|
import through2 from 'through2';
|
|
19
20
|
import { recordLog } from '../../logging/index.js';
|
|
20
21
|
import getString from '../../translations/index.js';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
22
|
+
import { resolveMultilingualString } from '../../translations/language-utils.js';
|
|
23
|
+
import { resolveLanguage } from '../../translations/language-utils.js';
|
|
23
24
|
export const isAccessAllowed = async ({ sectionName, role, userId, }) => {
|
|
24
25
|
/**
|
|
25
26
|
* Check admin privileges
|
|
@@ -57,14 +58,14 @@ export const getDocument = async (session, input) => {
|
|
|
57
58
|
if (!section?.name) {
|
|
58
59
|
throw new TRPCError({
|
|
59
60
|
code: 'BAD_REQUEST',
|
|
60
|
-
message: getString('invalidFilePath', session.user.
|
|
61
|
+
message: getString('invalidFilePath', session.user.language),
|
|
61
62
|
});
|
|
62
63
|
}
|
|
63
64
|
const fieldConfig = section.fields.find((field) => field.name === fieldName);
|
|
64
65
|
if (!fieldConfig || typeof fieldConfig.build !== 'function') {
|
|
65
66
|
throw new TRPCError({
|
|
66
67
|
code: 'BAD_REQUEST',
|
|
67
|
-
message: getString('invalidRequest', session.user.
|
|
68
|
+
message: getString('invalidRequest', session.user.language),
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
71
|
const field = fieldConfig.build();
|
|
@@ -74,7 +75,7 @@ export const getDocument = async (session, input) => {
|
|
|
74
75
|
if (!field || !field.name || !field.extensions || field.extensions.length === 0) {
|
|
75
76
|
throw new TRPCError({
|
|
76
77
|
code: 'BAD_REQUEST',
|
|
77
|
-
message: getString('invalidRequest', session.user.
|
|
78
|
+
message: getString('invalidRequest', session.user.language),
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
@@ -90,7 +91,7 @@ export const getDocument = async (session, input) => {
|
|
|
90
91
|
if (!fs.existsSync(pathToFile)) {
|
|
91
92
|
throw new TRPCError({
|
|
92
93
|
code: 'BAD_REQUEST',
|
|
93
|
-
message: getString('fileNotFound', session.user.
|
|
94
|
+
message: getString('fileNotFound', session.user.language),
|
|
94
95
|
});
|
|
95
96
|
}
|
|
96
97
|
/**
|
|
@@ -107,7 +108,7 @@ export const getDocument = async (session, input) => {
|
|
|
107
108
|
if (!fileType) {
|
|
108
109
|
throw new TRPCError({
|
|
109
110
|
code: 'BAD_REQUEST',
|
|
110
|
-
message: getString('invalidFileType', session.user.
|
|
111
|
+
message: getString('invalidFileType', session.user.language),
|
|
111
112
|
});
|
|
112
113
|
}
|
|
113
114
|
/**
|
|
@@ -116,7 +117,7 @@ export const getDocument = async (session, input) => {
|
|
|
116
117
|
if (!documentAllowedExtensions.includes(fileType.ext)) {
|
|
117
118
|
throw new TRPCError({
|
|
118
119
|
code: 'BAD_REQUEST',
|
|
119
|
-
message: getString('invalidFileType', session.user.
|
|
120
|
+
message: getString('invalidFileType', session.user.language),
|
|
120
121
|
});
|
|
121
122
|
}
|
|
122
123
|
/**
|
|
@@ -252,8 +253,8 @@ export async function streamPhoto(input) {
|
|
|
252
253
|
const nodeStream = processedImage.pipe(through2());
|
|
253
254
|
return nodeStreamToWebStream(nodeStream, processedImage);
|
|
254
255
|
}
|
|
255
|
-
export const getVideo = async (input) => {
|
|
256
|
-
throw new Error(getString('useVideoApiRoute',
|
|
256
|
+
export const getVideo = async (session, input) => {
|
|
257
|
+
throw new Error(getString('useVideoApiRoute', session.user.language));
|
|
257
258
|
};
|
|
258
259
|
export const getAdminPrivileges = async (adminId) => {
|
|
259
260
|
const rows = await db
|
|
@@ -266,17 +267,17 @@ export const getAllPrivileges = async (session) => {
|
|
|
266
267
|
const [sections, pluginRoutes] = await Promise.all([SectionFactory.getSections(), getPluginRoutes()]);
|
|
267
268
|
// Get the locale for resolving localized titles
|
|
268
269
|
const cmsConfig = await getCMSConfig();
|
|
269
|
-
const
|
|
270
|
+
const language = resolveLanguage(session.user.language, cmsConfig.i18n.supportedLanguages, cmsConfig.i18n.fallbackLanguage);
|
|
270
271
|
// First, let's assign the static privileges
|
|
271
272
|
const privilegesList = [];
|
|
272
273
|
// Now, let's add the rest of the privileges to the list
|
|
273
274
|
sections.forEach((section, index) => {
|
|
274
275
|
let title;
|
|
275
276
|
if (typeof section.title === 'string') {
|
|
276
|
-
title =
|
|
277
|
+
title = resolveMultilingualString(section.title, language, cmsConfig.i18n.fallbackLanguage);
|
|
277
278
|
}
|
|
278
279
|
else {
|
|
279
|
-
title =
|
|
280
|
+
title = resolveMultilingualString(section.title.section, language, cmsConfig.i18n.fallbackLanguage);
|
|
280
281
|
}
|
|
281
282
|
privilegesList.push({
|
|
282
283
|
title,
|
|
@@ -290,7 +291,7 @@ export const getAllPrivileges = async (session) => {
|
|
|
290
291
|
if (pluginPrivileges.has(route.pluginName))
|
|
291
292
|
return;
|
|
292
293
|
pluginPrivileges.set(route.pluginName, {
|
|
293
|
-
title:
|
|
294
|
+
title: resolveMultilingualString(route.title, language, cmsConfig.i18n.fallbackLanguage),
|
|
294
295
|
});
|
|
295
296
|
});
|
|
296
297
|
Array.from(pluginPrivileges.entries()).forEach(([pluginName, meta], index) => {
|
|
@@ -301,7 +302,7 @@ export const getAllPrivileges = async (session) => {
|
|
|
301
302
|
sectionType: 'plugin',
|
|
302
303
|
});
|
|
303
304
|
});
|
|
304
|
-
privilegesList.push({ title: '
|
|
305
|
+
privilegesList.push({ title: getString('admins', language), order: 0, sectionName: 'admins', sectionType: 'static' }, { title: getString('log', language), order: 2, sectionName: 'log', sectionType: 'static' });
|
|
305
306
|
return privilegesList;
|
|
306
307
|
};
|
|
307
308
|
export const getAdminsList = async () => {
|
|
@@ -326,8 +327,30 @@ export const getAdminsList = async () => {
|
|
|
326
327
|
});
|
|
327
328
|
return adminsWithRoles;
|
|
328
329
|
};
|
|
329
|
-
export const createSimpleSectionPage = async (session, sectionName) => {
|
|
330
|
+
export const createSimpleSectionPage = async (session, sectionName, locale) => {
|
|
330
331
|
try {
|
|
332
|
+
const cmsConfig = await getCMSConfig();
|
|
333
|
+
const localeResult = resolveLocale({
|
|
334
|
+
localization: cmsConfig.localization,
|
|
335
|
+
locale,
|
|
336
|
+
});
|
|
337
|
+
if (locale && !localeResult.resolvedLocale) {
|
|
338
|
+
if (localeResult.localizationEnabled === false) {
|
|
339
|
+
return {
|
|
340
|
+
error: {
|
|
341
|
+
message: getString('localizationNotEnabledForSection', session.user.language),
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
error: {
|
|
347
|
+
message: getString('invalidLocale', session.user.language, {
|
|
348
|
+
locale,
|
|
349
|
+
locales: localeResult.availableLocales.map((l) => l.code).join(', '),
|
|
350
|
+
}),
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
331
354
|
/**
|
|
332
355
|
* Let's fetch the section information
|
|
333
356
|
*/
|
|
@@ -346,41 +369,90 @@ export const createSimpleSectionPage = async (session, sectionName) => {
|
|
|
346
369
|
};
|
|
347
370
|
}
|
|
348
371
|
await fieldsFactory.generateFields();
|
|
349
|
-
/**
|
|
350
|
-
* Let's check for variants
|
|
351
|
-
*/
|
|
352
|
-
/*const variants = section[0].variants
|
|
353
|
-
const sectionVariants: { name: string }[] = []
|
|
354
|
-
|
|
355
|
-
if (variants && variants.trim() !== '') {
|
|
356
|
-
/!**
|
|
357
|
-
* Convert to JSON
|
|
358
|
-
*!/
|
|
359
|
-
const variantsJson = JSON.parse(variants)
|
|
360
|
-
|
|
361
|
-
/!**
|
|
362
|
-
* Loop through the variants
|
|
363
|
-
*!/
|
|
364
|
-
|
|
365
|
-
variantsJson.forEach((variant: any) => {
|
|
366
|
-
const variantName = variant.name
|
|
367
|
-
const variantInfo = variant.info
|
|
368
|
-
})
|
|
369
|
-
}*/
|
|
370
372
|
const sectionInfo = fieldsFactory.sectionInfo;
|
|
371
373
|
const gallery = await sectionInfo.getGallery();
|
|
372
374
|
// Get the locale for resolving localized titles
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
|
|
375
|
+
const uiLanguage = resolveLanguage(session.user.language, cmsConfig.i18n.supportedLanguages, cmsConfig.i18n.fallbackLanguage);
|
|
376
|
+
const resolvedTitle = resolveMultilingualString(sectionInfo.title, uiLanguage, cmsConfig.i18n.fallbackLanguage);
|
|
377
|
+
/**
|
|
378
|
+
* Fetch localization metadata for sections with localized fields
|
|
379
|
+
*/
|
|
380
|
+
let localizationData = null;
|
|
381
|
+
if (localeResult.localizationEnabled) {
|
|
382
|
+
let existingTranslations = [];
|
|
383
|
+
if (sectionInfo.hasLocalizedFields) {
|
|
384
|
+
const localesTableName = sectionInfo.localesTableName;
|
|
385
|
+
const localeColumns = await MysqlTableChecker.getColumns(localesTableName);
|
|
386
|
+
const localeTableExists = localeColumns.length > 0;
|
|
387
|
+
if (localeTableExists) {
|
|
388
|
+
// Simple sections always have itemId = 1
|
|
389
|
+
const sectionItemId = 1;
|
|
390
|
+
const [rows] = await db.execute(sql `SELECT locale FROM \`${sql.raw(localesTableName)}\` WHERE parent_id = ${sectionItemId}`);
|
|
391
|
+
existingTranslations = rows.map((r) => r.locale);
|
|
392
|
+
// Override localized field values with locale-specific data
|
|
393
|
+
if (locale && localeResult.resolvedLocale && localeResult.isDefault === false) {
|
|
394
|
+
const [localeRows] = await db.execute(sql `SELECT * FROM \`${sql.raw(localesTableName)}\` WHERE parent_id = ${sectionItemId} AND locale = ${localeResult.resolvedLocale.code} LIMIT 1`);
|
|
395
|
+
const localeRow = localeRows[0] ?? null;
|
|
396
|
+
for (const field of sectionInfo.fields) {
|
|
397
|
+
if (!field.localized)
|
|
398
|
+
continue;
|
|
399
|
+
// For select/tags with destinationDb, fetch locale-scoped junction values
|
|
400
|
+
const f = field;
|
|
401
|
+
if (f.destinationDb && (field.type === 'select' || field.type === 'select_multiple')) {
|
|
402
|
+
if (f.db) {
|
|
403
|
+
const [_rows] = await db.execute(sql `SELECT * FROM \`${sql.raw(f.destinationDb.table)}\` a JOIN \`${sql.raw(f.db.table)}\` b ON a.\`${sql.raw(f.destinationDb.selectIdentifier)}\` = b.\`${sql.raw(f.db.identifier)}\` WHERE a.\`${sql.raw(f.destinationDb.itemIdentifier)}\` = ${sectionItemId} AND a.\`locale\` = ${localeResult.resolvedLocale.code}`);
|
|
404
|
+
const values = Array.isArray(_rows)
|
|
405
|
+
? _rows.map((row) => ({
|
|
406
|
+
value: row[f.destinationDb.selectIdentifier],
|
|
407
|
+
label: row[f.db.label],
|
|
408
|
+
}))
|
|
409
|
+
: [];
|
|
410
|
+
field.setValue(values);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
const [_rows] = await db.execute(sql `SELECT * FROM \`${sql.raw(f.destinationDb.table)}\` WHERE \`${sql.raw(f.destinationDb.itemIdentifier)}\` = ${sectionItemId} AND \`locale\` = ${localeResult.resolvedLocale.code}`);
|
|
414
|
+
const values = Array.isArray(_rows)
|
|
415
|
+
? _rows.map((row) => ({
|
|
416
|
+
value: row[f.destinationDb.selectIdentifier],
|
|
417
|
+
label: f.options?.find((opt) => opt.value?.toString() ===
|
|
418
|
+
row[f.destinationDb.selectIdentifier]?.toString())?.label ?? '',
|
|
419
|
+
}))
|
|
420
|
+
: [];
|
|
421
|
+
field.setValue(values);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else if (f.destinationDb && field.type === 'tags') {
|
|
425
|
+
const [_rows] = await db.execute(sql `SELECT \`${sql.raw(f.destinationDb.selectIdentifier)}\` FROM \`${sql.raw(f.destinationDb.table)}\` WHERE \`${sql.raw(f.destinationDb.itemIdentifier)}\` = ${sectionItemId} AND \`locale\` = ${localeResult.resolvedLocale.code}`);
|
|
426
|
+
const tags = Array.isArray(_rows)
|
|
427
|
+
? _rows.map((row) => row[f.destinationDb.selectIdentifier])
|
|
428
|
+
: [];
|
|
429
|
+
field.setValue(tags.join(','));
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// Simple field: override from locale row
|
|
433
|
+
field.setValue(localeRow ? (localeRow[field.name] ?? null) : null);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
localizationData = {
|
|
440
|
+
defaultLocale: localeResult.defaultLocale,
|
|
441
|
+
currentLocale: localeResult.resolvedLocale ?? localeResult.defaultLocale,
|
|
442
|
+
existingTranslations,
|
|
443
|
+
locales: localeResult.availableLocales,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
376
446
|
return {
|
|
377
447
|
section: {
|
|
378
448
|
name: sectionInfo.name,
|
|
379
449
|
title: resolvedTitle,
|
|
380
450
|
gallery: gallery,
|
|
381
451
|
variants: sectionInfo.variants,
|
|
452
|
+
configFile: sectionInfo.configFile,
|
|
382
453
|
},
|
|
383
454
|
inputGroups: await fieldsFactory.getGroupedFields(),
|
|
455
|
+
localization: localizationData,
|
|
384
456
|
};
|
|
385
457
|
}
|
|
386
458
|
catch (err) {
|
|
@@ -410,7 +482,7 @@ export const deleteSectionItem = async (session, sectionName, sectionItemId, rec
|
|
|
410
482
|
if (!section) {
|
|
411
483
|
return {
|
|
412
484
|
error: {
|
|
413
|
-
message: getString('sectionNotFound', session.user.
|
|
485
|
+
message: getString('sectionNotFound', session.user.language),
|
|
414
486
|
},
|
|
415
487
|
};
|
|
416
488
|
}
|
|
@@ -420,7 +492,7 @@ export const deleteSectionItem = async (session, sectionName, sectionItemId, rec
|
|
|
420
492
|
if (!columns.includes(identifierFieldName)) {
|
|
421
493
|
return {
|
|
422
494
|
error: {
|
|
423
|
-
message: getString('sectionTableIdentifierNotFound', session.user.
|
|
495
|
+
message: getString('sectionTableIdentifierNotFound', session.user.language),
|
|
424
496
|
},
|
|
425
497
|
};
|
|
426
498
|
}
|
|
@@ -435,21 +507,38 @@ export const deleteSectionItem = async (session, sectionName, sectionItemId, rec
|
|
|
435
507
|
* Run beforeDelete hook before the actual deletion
|
|
436
508
|
*/
|
|
437
509
|
if (section.hooks?.beforeDelete) {
|
|
438
|
-
|
|
510
|
+
const hook = section.hooks.beforeDelete;
|
|
511
|
+
const handler = typeof hook === 'function' ? hook : hook.handler;
|
|
512
|
+
await handler({
|
|
439
513
|
itemId: sectionItemId,
|
|
440
514
|
values: sectionItemRow ?? {},
|
|
441
515
|
section: section,
|
|
442
516
|
});
|
|
443
517
|
}
|
|
444
518
|
/**
|
|
445
|
-
*
|
|
519
|
+
* Cascade delete translations from the locales table.
|
|
520
|
+
* Use deleteLocaleTranslation per locale to ensure localized files are cleaned up from disk.
|
|
446
521
|
*/
|
|
447
|
-
|
|
522
|
+
const cmsConfig = await getCMSConfig();
|
|
523
|
+
if (cmsConfig.localization?.enabled && section.hasLocalizedFields) {
|
|
524
|
+
const localesTableName = section.localesTableName;
|
|
525
|
+
const localeColumns = await MysqlTableChecker.getColumns(localesTableName);
|
|
526
|
+
if (localeColumns.length > 0) {
|
|
527
|
+
const [localeRows] = await db.execute(sql `SELECT locale FROM \`${sql.raw(localesTableName)}\` WHERE parent_id = ${sectionItemId}`);
|
|
528
|
+
const locales = localeRows.map((r) => r.locale);
|
|
529
|
+
for (const locale of locales) {
|
|
530
|
+
const result = await deleteLocaleTranslation(session, sectionName, sectionItemId, locale);
|
|
531
|
+
if (result && typeof result === 'object' && 'error' in result) {
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
448
537
|
/**
|
|
449
538
|
* Grab the file fields to delete the files
|
|
450
539
|
*/
|
|
451
540
|
const fileFieldConfigs = section.fieldConfigs.filter((fieldConfig) => fieldConfig.type === 'document' || fieldConfig.type === 'photo' || fieldConfig.type === 'video');
|
|
452
|
-
const uploadsFolder =
|
|
541
|
+
const uploadsFolder = cmsConfig.media.upload.path;
|
|
453
542
|
if (sectionItemRow) {
|
|
454
543
|
for (const field of fileFieldConfigs) {
|
|
455
544
|
// @ts-ignore
|
|
@@ -464,6 +553,15 @@ export const deleteSectionItem = async (session, sectionName, sectionItemId, rec
|
|
|
464
553
|
console.error('Error deleting document', error);
|
|
465
554
|
}
|
|
466
555
|
}
|
|
556
|
+
else if (field.type === 'video') {
|
|
557
|
+
try {
|
|
558
|
+
await fs.promises.unlink(path.join(uploadsFolder, '.videos', section.name, value));
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
// Log but continue - file may not exist or already deleted
|
|
562
|
+
console.error('Error deleting video', error);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
467
565
|
else {
|
|
468
566
|
try {
|
|
469
567
|
await fs.promises.unlink(path.join(uploadsFolder, '.photos', section.name, value));
|
|
@@ -554,12 +652,18 @@ export const deleteSectionItem = async (session, sectionName, sectionItemId, rec
|
|
|
554
652
|
.delete(EditorPhotosTable)
|
|
555
653
|
.where(and(eq(EditorPhotosTable.section, sectionName), eq(EditorPhotosTable.itemId, sectionItemId)));
|
|
556
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* Delete the row from the table
|
|
657
|
+
*/
|
|
658
|
+
await db.execute(sql `delete from \`${sql.raw(tableName)}\` where \`${sql.raw(identifierFieldName)}\` = ${sectionItemId}`);
|
|
557
659
|
/**
|
|
558
660
|
* Run afterDelete hook after the deletion
|
|
559
661
|
*/
|
|
560
662
|
if (section.hooks?.afterDelete) {
|
|
663
|
+
const hook = section.hooks.afterDelete;
|
|
664
|
+
const handler = typeof hook === 'function' ? hook : hook.handler;
|
|
561
665
|
try {
|
|
562
|
-
await
|
|
666
|
+
await handler({
|
|
563
667
|
itemId: sectionItemId,
|
|
564
668
|
values: sectionItemRow ?? {},
|
|
565
669
|
section: section,
|
|
@@ -600,7 +704,170 @@ export const deleteSectionItem = async (session, sectionName, sectionItemId, rec
|
|
|
600
704
|
};
|
|
601
705
|
}
|
|
602
706
|
};
|
|
603
|
-
export const
|
|
707
|
+
export const deleteLocaleTranslation = async (session, sectionName, sectionItemId, locale) => {
|
|
708
|
+
sectionItemId = sectionItemId.toString();
|
|
709
|
+
try {
|
|
710
|
+
const _s = (await SectionFactory.getSectionForAdmin({
|
|
711
|
+
name: sectionName,
|
|
712
|
+
admin: {
|
|
713
|
+
id: session.user.id,
|
|
714
|
+
requiredRole: 'D',
|
|
715
|
+
},
|
|
716
|
+
}));
|
|
717
|
+
const section = _s?.build();
|
|
718
|
+
if (!section) {
|
|
719
|
+
return {
|
|
720
|
+
error: {
|
|
721
|
+
message: getString('sectionNotFound', session.user.language),
|
|
722
|
+
},
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
const cmsConfig = await getCMSConfig();
|
|
726
|
+
if (!cmsConfig.localization?.enabled || !section.hasLocalizedFields) {
|
|
727
|
+
return {
|
|
728
|
+
error: {
|
|
729
|
+
message: getString('localizationNotEnabledForSection', session.user.language),
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
if (locale === cmsConfig.localization.defaultLocale) {
|
|
734
|
+
return {
|
|
735
|
+
error: {
|
|
736
|
+
message: getString('cannotDeleteBaseLocaleTranslation', session.user.language),
|
|
737
|
+
},
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const localesTableName = section.localesTableName;
|
|
741
|
+
const localeColumns = await MysqlTableChecker.getColumns(localesTableName);
|
|
742
|
+
if (localeColumns.length === 0) {
|
|
743
|
+
return {
|
|
744
|
+
error: {
|
|
745
|
+
message: getString('localesTableDoesNotExist', session.user.language),
|
|
746
|
+
},
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
// Fetch the locale row before deleting so we can clean up files
|
|
750
|
+
const [localeRows] = await db.execute(sql `SELECT * FROM \`${sql.raw(localesTableName)}\` WHERE parent_id = ${sectionItemId} AND locale = ${locale} LIMIT 1`);
|
|
751
|
+
const localeRow = localeRows[0] ?? null;
|
|
752
|
+
const beforeDeleteHook = section.hooks?.beforeDelete;
|
|
753
|
+
if (beforeDeleteHook && typeof beforeDeleteHook === 'object' && beforeDeleteHook.runForLocales) {
|
|
754
|
+
await beforeDeleteHook.handler({
|
|
755
|
+
itemId: sectionItemId,
|
|
756
|
+
values: localeRow ?? {},
|
|
757
|
+
section: section,
|
|
758
|
+
locale,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
// Delete files for localized photo/document/video fields
|
|
762
|
+
if (localeRow) {
|
|
763
|
+
const uploadsFolder = cmsConfig.media.upload.path;
|
|
764
|
+
const localizedFileFields = section.fieldConfigs.filter((f) => f.localized === true && (f.type === 'photo' || f.type === 'document' || f.type === 'video'));
|
|
765
|
+
for (const field of localizedFileFields) {
|
|
766
|
+
const value = localeRow[field.name];
|
|
767
|
+
if (!value)
|
|
768
|
+
continue;
|
|
769
|
+
if (field.type === 'document') {
|
|
770
|
+
try {
|
|
771
|
+
await fs.promises.unlink(path.join(uploadsFolder, '.documents', section.name, value));
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
console.error('Error deleting document', error);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
else if (field.type === 'video') {
|
|
778
|
+
try {
|
|
779
|
+
await fs.promises.unlink(path.join(uploadsFolder, '.videos', section.name, value));
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
console.error('Error deleting video', error);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
// photo
|
|
787
|
+
try {
|
|
788
|
+
await fs.promises.unlink(path.join(uploadsFolder, '.photos', section.name, value));
|
|
789
|
+
await fs.promises.unlink(path.join(uploadsFolder, '.thumbs', section.name, value));
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
console.error('Error deleting photo', error);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// Delete locale-scoped editor photos for localized rich_text fields
|
|
798
|
+
const editorPhotos = await db
|
|
799
|
+
.select()
|
|
800
|
+
.from(EditorPhotosTable)
|
|
801
|
+
.where(and(eq(EditorPhotosTable.section, sectionName), eq(EditorPhotosTable.itemId, sectionItemId.toString()), eq(EditorPhotosTable.locale, locale)));
|
|
802
|
+
if (editorPhotos.length > 0) {
|
|
803
|
+
const uploadsFolder = cmsConfig.media.upload.path;
|
|
804
|
+
for (const photo of editorPhotos) {
|
|
805
|
+
try {
|
|
806
|
+
await fs.promises.unlink(path.join(uploadsFolder, '.photos', section.name, photo.name));
|
|
807
|
+
}
|
|
808
|
+
catch (error) {
|
|
809
|
+
console.error('Error deleting editor photo', error);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
await db
|
|
813
|
+
.delete(EditorPhotosTable)
|
|
814
|
+
.where(and(eq(EditorPhotosTable.section, sectionName), eq(EditorPhotosTable.itemId, sectionItemId.toString()), eq(EditorPhotosTable.locale, locale)));
|
|
815
|
+
}
|
|
816
|
+
// Delete locale-scoped junction table rows for localized select/tags fields
|
|
817
|
+
const localizedJunctionFields = section.fieldConfigs.filter((f) => f.localized === true &&
|
|
818
|
+
f.destinationDb &&
|
|
819
|
+
(f.type === 'select' || f.type === 'select_multiple' || f.type === 'tags'));
|
|
820
|
+
for (const field of localizedJunctionFields) {
|
|
821
|
+
const destDb = field.destinationDb;
|
|
822
|
+
if (!destDb)
|
|
823
|
+
continue;
|
|
824
|
+
try {
|
|
825
|
+
await db.execute(sql `DELETE FROM \`${sql.raw(destDb.table)}\` WHERE \`${sql.raw(destDb.itemIdentifier)}\` = ${sectionItemId} AND \`locale\` = ${locale}`);
|
|
826
|
+
}
|
|
827
|
+
catch (error) {
|
|
828
|
+
console.error(`Error deleting junction table rows for ${field.name}`, error);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
await db.execute(sql `DELETE FROM \`${sql.raw(localesTableName)}\` WHERE parent_id = ${sectionItemId} AND locale = ${locale}`);
|
|
832
|
+
const afterDeleteHook = section.hooks?.afterDelete;
|
|
833
|
+
if (afterDeleteHook && typeof afterDeleteHook === 'object' && afterDeleteHook.runForLocales) {
|
|
834
|
+
try {
|
|
835
|
+
await afterDeleteHook.handler({
|
|
836
|
+
itemId: sectionItemId,
|
|
837
|
+
values: localeRow ?? {},
|
|
838
|
+
section: section,
|
|
839
|
+
locale,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
catch (e) {
|
|
843
|
+
console.error('afterDelete hook failed:', e);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
await recordLog({
|
|
847
|
+
eventType: 'section.item.locale.delete',
|
|
848
|
+
actorId: session.user.id,
|
|
849
|
+
actorUsername: session.user.name,
|
|
850
|
+
entityType: 'section_item_locale',
|
|
851
|
+
entityId: sectionItemId,
|
|
852
|
+
entityLabel: locale,
|
|
853
|
+
sectionName: section.name,
|
|
854
|
+
metadata: {
|
|
855
|
+
locale,
|
|
856
|
+
},
|
|
857
|
+
});
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
catch (err) {
|
|
861
|
+
const errorMessage = err instanceof Error ? err.message : getString('unknownErrorOccurred', session.user.language);
|
|
862
|
+
console.error('Error deleting locale translation', err);
|
|
863
|
+
return {
|
|
864
|
+
error: {
|
|
865
|
+
message: getString('deleteLocaleTranslationFailed', session.user.language, { detail: errorMessage }),
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
export const createEditPage = async (session, sectionName, sectionItemId, locale) => {
|
|
604
871
|
/**
|
|
605
872
|
* Generate the fields for the edit page
|
|
606
873
|
*/
|
|
@@ -611,6 +878,28 @@ export const createEditPage = async (session, sectionName, sectionItemId) => {
|
|
|
611
878
|
itemId: sectionItemId,
|
|
612
879
|
});
|
|
613
880
|
try {
|
|
881
|
+
const cmsConfig = await getCMSConfig();
|
|
882
|
+
const localeResult = resolveLocale({
|
|
883
|
+
localization: cmsConfig.localization,
|
|
884
|
+
locale,
|
|
885
|
+
});
|
|
886
|
+
if (locale && !localeResult.resolvedLocale) {
|
|
887
|
+
if (localeResult.localizationEnabled === false) {
|
|
888
|
+
return {
|
|
889
|
+
error: {
|
|
890
|
+
message: getString('localizationNotEnabledForSection', session.user.language),
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
error: {
|
|
896
|
+
message: getString('invalidLocale', session.user.language, {
|
|
897
|
+
locale,
|
|
898
|
+
locales: localeResult.availableLocales.map((l) => l.code).join(', '),
|
|
899
|
+
}),
|
|
900
|
+
},
|
|
901
|
+
};
|
|
902
|
+
}
|
|
614
903
|
await fieldsFactory.initialize();
|
|
615
904
|
await fieldsFactory.generateFields();
|
|
616
905
|
if (fieldsFactory.error) {
|
|
@@ -665,23 +954,91 @@ export const createEditPage = async (session, sectionName, sectionItemId) => {
|
|
|
665
954
|
}
|
|
666
955
|
}
|
|
667
956
|
const sectionInfo = fieldsFactory.sectionInfo;
|
|
668
|
-
//
|
|
669
|
-
const
|
|
670
|
-
const locale = resolveLocale(session.user.locale, cmsConfig.i18n.supportedLanguages, cmsConfig.i18n.fallbackLanguage);
|
|
957
|
+
// Resolve localized titles using the user's language
|
|
958
|
+
const uiLanguage = resolveLanguage(session.user.language, cmsConfig.i18n.supportedLanguages, cmsConfig.i18n.fallbackLanguage);
|
|
671
959
|
const resolvedTitle = {
|
|
672
|
-
section:
|
|
673
|
-
singular:
|
|
674
|
-
plural:
|
|
960
|
+
section: resolveMultilingualString(sectionInfo.title.section, uiLanguage, cmsConfig.i18n.fallbackLanguage),
|
|
961
|
+
singular: resolveMultilingualString(sectionInfo.title.singular, uiLanguage, cmsConfig.i18n.fallbackLanguage),
|
|
962
|
+
plural: resolveMultilingualString(sectionInfo.title.plural, uiLanguage, cmsConfig.i18n.fallbackLanguage),
|
|
675
963
|
};
|
|
964
|
+
/**
|
|
965
|
+
* Fetch localization metadata for sections with localized fields
|
|
966
|
+
*/
|
|
967
|
+
let localizationData = null;
|
|
968
|
+
if (localeResult.localizationEnabled) {
|
|
969
|
+
let existingTranslations = [];
|
|
970
|
+
if (sectionInfo.hasLocalizedFields) {
|
|
971
|
+
const localesTableName = sectionInfo.localesTableName;
|
|
972
|
+
const localeColumns = await MysqlTableChecker.getColumns(localesTableName);
|
|
973
|
+
const localeTableExists = localeColumns.length > 0;
|
|
974
|
+
if (localeTableExists) {
|
|
975
|
+
const [rows] = await db.execute(sql `SELECT locale FROM \`${sql.raw(localesTableName)}\` WHERE parent_id = ${sectionItemId}`);
|
|
976
|
+
existingTranslations = rows.map((r) => r.locale);
|
|
977
|
+
// Override localized field values with locale-specific data
|
|
978
|
+
if (locale && localeResult.resolvedLocale && localeResult.isDefault === false) {
|
|
979
|
+
const [localeRows] = await db.execute(sql `SELECT * FROM \`${sql.raw(localesTableName)}\` WHERE parent_id = ${sectionItemId} AND locale = ${localeResult.resolvedLocale.code} LIMIT 1`);
|
|
980
|
+
const localeRow = localeRows[0] ?? null;
|
|
981
|
+
for (const field of sectionInfo.fields) {
|
|
982
|
+
if (!field.localized)
|
|
983
|
+
continue;
|
|
984
|
+
// For select/tags with destinationDb, fetch locale-scoped junction values
|
|
985
|
+
const f = field;
|
|
986
|
+
if (f.destinationDb && (field.type === 'select' || field.type === 'select_multiple')) {
|
|
987
|
+
if (f.db) {
|
|
988
|
+
const [_rows] = await db.execute(sql `SELECT * FROM \`${sql.raw(f.destinationDb.table)}\` a JOIN \`${sql.raw(f.db.table)}\` b ON a.\`${sql.raw(f.destinationDb.selectIdentifier)}\` = b.\`${sql.raw(f.db.identifier)}\` WHERE a.\`${sql.raw(f.destinationDb.itemIdentifier)}\` = ${sectionItemId} AND a.\`locale\` = ${localeResult.resolvedLocale.code}`);
|
|
989
|
+
const values = Array.isArray(_rows)
|
|
990
|
+
? _rows.map((row) => ({
|
|
991
|
+
value: row[f.destinationDb.selectIdentifier],
|
|
992
|
+
label: row[f.db.label],
|
|
993
|
+
}))
|
|
994
|
+
: [];
|
|
995
|
+
field.setValue(values);
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
const [_rows] = await db.execute(sql `SELECT * FROM \`${sql.raw(f.destinationDb.table)}\` WHERE \`${sql.raw(f.destinationDb.itemIdentifier)}\` = ${sectionItemId} AND \`locale\` = ${localeResult.resolvedLocale.code}`);
|
|
999
|
+
const values = Array.isArray(_rows)
|
|
1000
|
+
? _rows.map((row) => ({
|
|
1001
|
+
value: row[f.destinationDb.selectIdentifier],
|
|
1002
|
+
label: f.options?.find((opt) => opt.value?.toString() ===
|
|
1003
|
+
row[f.destinationDb.selectIdentifier]?.toString())?.label ?? '',
|
|
1004
|
+
}))
|
|
1005
|
+
: [];
|
|
1006
|
+
field.setValue(values);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
else if (f.destinationDb && field.type === 'tags') {
|
|
1010
|
+
const [_rows] = await db.execute(sql `SELECT \`${sql.raw(f.destinationDb.selectIdentifier)}\` FROM \`${sql.raw(f.destinationDb.table)}\` WHERE \`${sql.raw(f.destinationDb.itemIdentifier)}\` = ${sectionItemId} AND \`locale\` = ${localeResult.resolvedLocale.code}`);
|
|
1011
|
+
const tags = Array.isArray(_rows)
|
|
1012
|
+
? _rows.map((row) => row[f.destinationDb.selectIdentifier])
|
|
1013
|
+
: [];
|
|
1014
|
+
field.setValue(tags.join(','));
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
// Simple field: override from locale row
|
|
1018
|
+
field.setValue(localeRow ? (localeRow[field.name] ?? null) : null);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
localizationData = {
|
|
1025
|
+
defaultLocale: localeResult.defaultLocale,
|
|
1026
|
+
currentLocale: localeResult.resolvedLocale ?? localeResult.defaultLocale,
|
|
1027
|
+
existingTranslations,
|
|
1028
|
+
locales: localeResult.availableLocales,
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
676
1031
|
return {
|
|
677
1032
|
section: {
|
|
678
1033
|
name: sectionInfo.name,
|
|
679
1034
|
title: resolvedTitle,
|
|
680
1035
|
gallery: gallery,
|
|
681
1036
|
variants: sectionInfo.variants,
|
|
1037
|
+
configFile: sectionInfo.configFile,
|
|
682
1038
|
},
|
|
683
1039
|
inputGroups: await fieldsFactory.getGroupedFields(),
|
|
684
1040
|
gallery: galleryItems,
|
|
1041
|
+
localization: localizationData,
|
|
685
1042
|
};
|
|
686
1043
|
}
|
|
687
1044
|
catch (error) {
|
|
@@ -745,20 +1102,25 @@ export const createNewPage = async (session, sectionName) => {
|
|
|
745
1102
|
const gallery = await sectionInfo.getGallery();
|
|
746
1103
|
// Get the locale for resolving localized titles
|
|
747
1104
|
const cmsConfig = await getCMSConfig();
|
|
748
|
-
const
|
|
1105
|
+
const language = resolveLanguage(session.user.language, cmsConfig.i18n.supportedLanguages, cmsConfig.i18n.fallbackLanguage);
|
|
749
1106
|
const resolvedTitle = {
|
|
750
|
-
section:
|
|
751
|
-
singular:
|
|
752
|
-
plural:
|
|
1107
|
+
section: resolveMultilingualString(sectionInfo.title.section, language, cmsConfig.i18n.fallbackLanguage),
|
|
1108
|
+
singular: resolveMultilingualString(sectionInfo.title.singular, language, cmsConfig.i18n.fallbackLanguage),
|
|
1109
|
+
plural: resolveMultilingualString(sectionInfo.title.plural, language, cmsConfig.i18n.fallbackLanguage),
|
|
753
1110
|
};
|
|
1111
|
+
const defaultLocale = cmsConfig.localization?.enabled
|
|
1112
|
+
? (cmsConfig.localization.locales.find((l) => l.code === cmsConfig.localization.defaultLocale) ?? null)
|
|
1113
|
+
: null;
|
|
754
1114
|
return {
|
|
755
1115
|
section: {
|
|
756
1116
|
name: sectionInfo.name,
|
|
757
1117
|
title: resolvedTitle,
|
|
758
1118
|
gallery: gallery,
|
|
759
1119
|
variants: sectionInfo.variants,
|
|
1120
|
+
configFile: sectionInfo.configFile,
|
|
760
1121
|
},
|
|
761
1122
|
inputGroups: await fieldsFactory.getGroupedFields(),
|
|
1123
|
+
defaultLocale,
|
|
762
1124
|
};
|
|
763
1125
|
}
|
|
764
1126
|
catch (err) {
|
|
@@ -787,7 +1149,7 @@ export const getCategorySectionChildren = async ({ session, id, sectionName, lev
|
|
|
787
1149
|
if (!section) {
|
|
788
1150
|
return {
|
|
789
1151
|
error: {
|
|
790
|
-
message: getString('sectionNotFound', session.user.
|
|
1152
|
+
message: getString('sectionNotFound', session.user.language),
|
|
791
1153
|
},
|
|
792
1154
|
};
|
|
793
1155
|
}
|
|
@@ -835,7 +1197,7 @@ export const getCategorySection = async (session, sectionName) => {
|
|
|
835
1197
|
if (!section) {
|
|
836
1198
|
return {
|
|
837
1199
|
error: {
|
|
838
|
-
message: getString('sectionNotFound', session.user.
|
|
1200
|
+
message: getString('sectionNotFound', session.user.language),
|
|
839
1201
|
},
|
|
840
1202
|
};
|
|
841
1203
|
}
|
|
@@ -862,11 +1224,11 @@ export const getCategorySection = async (session, sectionName) => {
|
|
|
862
1224
|
s.setValue(undefined);
|
|
863
1225
|
// Get the locale for resolving localized titles (label must be a string for React)
|
|
864
1226
|
const cmsConfig = await getCMSConfig();
|
|
865
|
-
const
|
|
1227
|
+
const language = resolveLanguage(session.user.language, cmsConfig.i18n.supportedLanguages, cmsConfig.i18n.fallbackLanguage);
|
|
866
1228
|
const resolvedTitle = {
|
|
867
|
-
section:
|
|
868
|
-
singular:
|
|
869
|
-
plural:
|
|
1229
|
+
section: resolveMultilingualString(section.title.section, language, cmsConfig.i18n.fallbackLanguage),
|
|
1230
|
+
singular: resolveMultilingualString(section.title.singular, language, cmsConfig.i18n.fallbackLanguage),
|
|
1231
|
+
plural: resolveMultilingualString(section.title.plural, language, cmsConfig.i18n.fallbackLanguage),
|
|
870
1232
|
};
|
|
871
1233
|
const categorySectionSelect = {
|
|
872
1234
|
options: s.options,
|
|
@@ -902,7 +1264,7 @@ export const getBrowsePage = async (session, sectionName, page = 1, q) => {
|
|
|
902
1264
|
if (!section) {
|
|
903
1265
|
return {
|
|
904
1266
|
error: {
|
|
905
|
-
message: getString('sectionNotFound', session.user.
|
|
1267
|
+
message: getString('sectionNotFound', session.user.language),
|
|
906
1268
|
},
|
|
907
1269
|
};
|
|
908
1270
|
}
|
|
@@ -913,26 +1275,38 @@ export const getBrowsePage = async (session, sectionName, page = 1, q) => {
|
|
|
913
1275
|
const sqlChunks = [];
|
|
914
1276
|
const totalRowsSqlChunks = [];
|
|
915
1277
|
const sectionSearch = section.search?.searchFields ? section.search?.searchFields.length > 0 : false;
|
|
916
|
-
|
|
917
|
-
|
|
1278
|
+
// Check if we need to JOIN _locales for search
|
|
1279
|
+
const cmsConfig = await getCMSConfig();
|
|
1280
|
+
const hasLocalization = !!cmsConfig.localization?.enabled;
|
|
1281
|
+
const hasLocalizedSearchFields = hasLocalization && q && q.trim().length > 0 && section.search?.searchFields?.some((f) => f.localized);
|
|
1282
|
+
const localesTable = hasLocalizedSearchFields ? section.localesTableName : null;
|
|
1283
|
+
if (localesTable) {
|
|
1284
|
+
// Use DISTINCT to avoid duplicate rows from the LEFT JOIN
|
|
1285
|
+
sqlChunks.push(sql `select distinct ${sql.raw(`\`${tableName}\``)}.* from ${sql.raw(`\`${tableName}\``)} left join ${sql.raw(`\`${localesTable}\``)} on ${sql.raw(`\`${localesTable}\``)}.parent_id = ${sql.raw(`\`${tableName}\``)}.${sql.raw(`\`${section.db.identifier.name}\``)}`);
|
|
1286
|
+
totalRowsSqlChunks.push(sql `select count(distinct ${sql.raw(`\`${tableName}\``)}.${sql.raw(`\`${section.db.identifier.name}\``)} ) as total from ${sql.raw(`\`${tableName}\``)} left join ${sql.raw(`\`${localesTable}\``)} on ${sql.raw(`\`${localesTable}\``)}.parent_id = ${sql.raw(`\`${tableName}\``)}.${sql.raw(`\`${section.db.identifier.name}\``)}`);
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
sqlChunks.push(sql `select * from ${sql.raw(`\`${tableName}\``)}`);
|
|
1290
|
+
totalRowsSqlChunks.push(sql `select count(*) as total from ${sql.raw(`\`${tableName}\``)}`);
|
|
1291
|
+
}
|
|
918
1292
|
if (q && q.trim().length > 0) {
|
|
919
1293
|
if (section.search?.searchFields.length) {
|
|
920
|
-
|
|
1294
|
+
const whereParts = [];
|
|
1295
|
+
for (const field of section.search.searchFields) {
|
|
1296
|
+
// Search in main table
|
|
1297
|
+
whereParts.push(sql `${sql.raw(`\`${tableName}\``)}.${sql.raw(`\`${field.name}\``)} LIKE CONCAT('%',${sql `${q.trim()}`},'%')`);
|
|
1298
|
+
// Also search in _locales table for localized fields
|
|
1299
|
+
if (localesTable && field.localized) {
|
|
1300
|
+
whereParts.push(sql `${sql.raw(`\`${localesTable}\``)}.${sql.raw(`\`${field.name}\``)} LIKE CONCAT('%',${sql `${q.trim()}`},'%')`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
921
1303
|
sqlChunks.push(sql `where`);
|
|
1304
|
+
sqlChunks.push(sql.join(whereParts, sql ` or `));
|
|
922
1305
|
totalRowsSqlChunks.push(sql `where`);
|
|
923
|
-
|
|
924
|
-
// sqlChunks.push(sql`${sql.raw(field.name)} like '%${q}%'`)
|
|
925
|
-
sqlChunks.push(sql `${sql.raw(field.name)} LIKE CONCAT('%',${sql `${q.trim()}`},'%')`);
|
|
926
|
-
totalRowsSqlChunks.push(sql `${sql.raw(field.name)} LIKE CONCAT('%',${sql `${q.trim()}`},'%')`);
|
|
927
|
-
if (i === section.search?.searchFields.length)
|
|
928
|
-
continue;
|
|
929
|
-
sqlChunks.push(sql `or`);
|
|
930
|
-
totalRowsSqlChunks.push(sql `or`);
|
|
931
|
-
i++;
|
|
932
|
-
}
|
|
1306
|
+
totalRowsSqlChunks.push(sql.join(whereParts, sql ` or `));
|
|
933
1307
|
}
|
|
934
1308
|
}
|
|
935
|
-
sqlChunks.push(sql `ORDER BY
|
|
1309
|
+
sqlChunks.push(sql `ORDER BY ${sql.raw(`\`${tableName}\``)}.${sql.raw(`\`${orderByFieldName}\``)} DESC LIMIT ${limit} OFFSET ${offset}`);
|
|
936
1310
|
const finalSql = sql.join(sqlChunks, sql.raw(' '));
|
|
937
1311
|
const totalRowsSql = sql.join(totalRowsSqlChunks, sql.raw(' '));
|
|
938
1312
|
// Now, let's get the section items from the table
|
|
@@ -940,10 +1314,9 @@ export const getBrowsePage = async (session, sectionName, page = 1, q) => {
|
|
|
940
1314
|
const totalCountResult = await db.execute(totalRowsSql);
|
|
941
1315
|
const totalCountRows = totalCountResult[0];
|
|
942
1316
|
const rows = sectionItems[0];
|
|
943
|
-
//
|
|
944
|
-
const
|
|
945
|
-
const
|
|
946
|
-
const resolvedTitle = resolveLocalizedString(section.title.section, locale, cmsConfig.i18n.fallbackLanguage);
|
|
1317
|
+
// Resolve localized section title for the browse page header
|
|
1318
|
+
const language = resolveLanguage(session.user.language, cmsConfig.i18n.supportedLanguages, cmsConfig.i18n.fallbackLanguage);
|
|
1319
|
+
const resolvedTitle = resolveMultilingualString(section.title.section, language, cmsConfig.i18n.fallbackLanguage);
|
|
947
1320
|
return {
|
|
948
1321
|
section: {
|
|
949
1322
|
// tableName: tableName,
|
|
@@ -1001,25 +1374,25 @@ export const getSidebar = async (session) => {
|
|
|
1001
1374
|
// Include dashboard override plugin even without explicit privilege
|
|
1002
1375
|
const allowedPluginRoutes = pluginRoutes.filter(({ pluginName, path }) => privilegeSet.has(pluginName) || path === dashboardOverridePath);
|
|
1003
1376
|
// Get the locale for resolving localized titles
|
|
1004
|
-
const
|
|
1377
|
+
const language = resolveLanguage(session.user.language, cmsConfig.i18n.supportedLanguages, cmsConfig.i18n.fallbackLanguage);
|
|
1005
1378
|
// Let's loop through the sections and add path, icon and title to each section item
|
|
1006
1379
|
const simpleSectionItems = simple.map((section) => {
|
|
1007
1380
|
return {
|
|
1008
|
-
title:
|
|
1381
|
+
title: resolveMultilingualString(section.title, language, cmsConfig.i18n.fallbackLanguage),
|
|
1009
1382
|
path: `/section/${section.name}`,
|
|
1010
1383
|
icon: section.icon,
|
|
1011
1384
|
};
|
|
1012
1385
|
});
|
|
1013
1386
|
const hasItemsSectionItems = has_items.map((section) => {
|
|
1014
1387
|
return {
|
|
1015
|
-
title:
|
|
1388
|
+
title: resolveMultilingualString(section.title.section, language, cmsConfig.i18n.fallbackLanguage),
|
|
1016
1389
|
path: `/${section.name}`,
|
|
1017
1390
|
icon: section.icon,
|
|
1018
1391
|
};
|
|
1019
1392
|
});
|
|
1020
1393
|
const categorySectionsItems = category.map((section) => {
|
|
1021
1394
|
return {
|
|
1022
|
-
title:
|
|
1395
|
+
title: resolveMultilingualString(section.title.section, language, cmsConfig.i18n.fallbackLanguage),
|
|
1023
1396
|
path: `/categorized/${section.name}`,
|
|
1024
1397
|
icon: section.icon,
|
|
1025
1398
|
};
|
|
@@ -1028,7 +1401,7 @@ export const getSidebar = async (session) => {
|
|
|
1028
1401
|
const pluginSections = allowedPluginRoutes
|
|
1029
1402
|
.filter((route) => route.path !== dashboardOverridePath)
|
|
1030
1403
|
.map((route) => ({
|
|
1031
|
-
title:
|
|
1404
|
+
title: resolveMultilingualString(route.title, language, cmsConfig.i18n.fallbackLanguage),
|
|
1032
1405
|
path: route.path,
|
|
1033
1406
|
icon: route.icon ?? '',
|
|
1034
1407
|
}));
|
|
@@ -1037,7 +1410,7 @@ export const getSidebar = async (session) => {
|
|
|
1037
1410
|
* Add the dashboard section (points to override if configured)
|
|
1038
1411
|
*/
|
|
1039
1412
|
{
|
|
1040
|
-
title: getString('dashboard', session.user.
|
|
1413
|
+
title: getString('dashboard', session.user.language),
|
|
1041
1414
|
path: dashboardOverridePath ?? '/dashboard',
|
|
1042
1415
|
icon: 'home',
|
|
1043
1416
|
},
|
|
@@ -1046,7 +1419,7 @@ export const getSidebar = async (session) => {
|
|
|
1046
1419
|
*/
|
|
1047
1420
|
...pluginSections,
|
|
1048
1421
|
...fixed.map((section) => ({
|
|
1049
|
-
title: getString(section.name, session.user.
|
|
1422
|
+
title: getString(section.name, session.user.language),
|
|
1050
1423
|
path: `/${section.name}`,
|
|
1051
1424
|
icon: section.icon,
|
|
1052
1425
|
})),
|