Merge pull request #60 from payloadcms/fix/localized-within-blocks
Fix/localized within blocks
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -14,7 +14,7 @@
|
||||
"--runInBand"
|
||||
],
|
||||
"env": {
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.js"
|
||||
"PAYLOAD_CONFIG_PATH": "demo/payload.config.ts"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
import { PayloadCollectionConfig } from '../../src/collections/config/types';
|
||||
import { Block } from '../../src/fields/config/types';
|
||||
|
||||
const RichTextBlock: Block = {
|
||||
slug: 'richTextBlock',
|
||||
labels: {
|
||||
singular: 'Rich Text Block',
|
||||
plural: 'Rich Text Blocks',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'content',
|
||||
localized: true,
|
||||
type: 'richText',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const LocalizedPosts: PayloadCollectionConfig = {
|
||||
slug: 'localized-posts',
|
||||
@@ -48,6 +64,54 @@ const LocalizedPosts: PayloadCollectionConfig = {
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'localizedGroup',
|
||||
label: 'Localized Group',
|
||||
type: 'group',
|
||||
localized: true,
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'nonLocalizedGroup',
|
||||
label: 'Non-Localized Group',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
label: 'Non-Localized Array',
|
||||
name: 'nonLocalizedArray',
|
||||
maxRows: 3,
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'localizedEmbeddedText',
|
||||
label: 'Localized Embedded Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Blocks',
|
||||
name: 'richTextBlocks',
|
||||
type: 'blocks',
|
||||
blocks: [
|
||||
RichTextBlock,
|
||||
],
|
||||
},
|
||||
],
|
||||
timestamps: true,
|
||||
};
|
||||
|
||||
@@ -27,7 +27,8 @@ const LocalizedArrays: PayloadCollectionConfig = {
|
||||
label: 'Array Text 1',
|
||||
type: 'text',
|
||||
required: true,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: 'arrayText2',
|
||||
label: 'Array Text 2',
|
||||
type: 'text',
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"jest": "26.6.3",
|
||||
"joi": "^17.3.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"method-override": "^3.0.0",
|
||||
"micro-memoize": "^4.0.9",
|
||||
"mini-css-extract-plugin": "1.3.3",
|
||||
|
||||
@@ -4,7 +4,7 @@ import localizationPlugin from '../localization/plugin';
|
||||
import buildSchema from '../mongoose/buildSchema';
|
||||
|
||||
const buildCollectionSchema = (collection, config, schemaOptions = {}) => {
|
||||
const schema = buildSchema(collection.fields, { timestamps: collection.timestamps !== false, ...schemaOptions });
|
||||
const schema = buildSchema(config, collection.fields, { timestamps: collection.timestamps !== false, ...schemaOptions });
|
||||
|
||||
schema.plugin(paginate)
|
||||
.plugin(buildQueryPlugin);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import httpStatus from 'http-status';
|
||||
import deepmerge from 'deepmerge';
|
||||
import merge from 'lodash.merge';
|
||||
import path from 'path';
|
||||
import { UploadedFile } from 'express-fileupload';
|
||||
import { Where, Document } from '../../types';
|
||||
@@ -103,7 +104,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
if (!doc && hasWherePolicy) throw new Forbidden();
|
||||
|
||||
if (locale && doc.setLocale) {
|
||||
doc.setLocale(locale, fallbackLocale);
|
||||
doc.setLocale(locale, null);
|
||||
}
|
||||
|
||||
let originalDoc: Document = doc.toJSON({ virtuals: true });
|
||||
@@ -245,10 +246,14 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
// Update
|
||||
// /////////////////////////////////////
|
||||
|
||||
Object.assign(doc, data);
|
||||
merge(doc, data);
|
||||
|
||||
await doc.save();
|
||||
|
||||
if (locale && doc.setLocale) {
|
||||
doc.setLocale(locale, fallbackLocale);
|
||||
}
|
||||
|
||||
let result: Document = doc.toJSON({ virtuals: true });
|
||||
|
||||
result = removeInternalFields(result);
|
||||
|
||||
@@ -44,6 +44,26 @@ describe('Collections - REST', () => {
|
||||
title: 'title',
|
||||
description: englishPostDesc,
|
||||
priority: 1,
|
||||
nonLocalizedGroup: {
|
||||
text: 'english',
|
||||
},
|
||||
localizedGroup: {
|
||||
text: 'english',
|
||||
},
|
||||
nonLocalizedArray: [
|
||||
{
|
||||
localizedEmbeddedText: 'english',
|
||||
},
|
||||
],
|
||||
richTextBlocks: [
|
||||
{
|
||||
blockType: 'richTextBlock',
|
||||
blockName: 'Test Block Name',
|
||||
content: [{
|
||||
children: [{ text: 'english' }],
|
||||
}],
|
||||
},
|
||||
],
|
||||
}),
|
||||
headers,
|
||||
method: 'post',
|
||||
@@ -54,6 +74,11 @@ describe('Collections - REST', () => {
|
||||
expect(response.status).toBe(201);
|
||||
expect(data.doc.title).not.toBeNull();
|
||||
expect(data.doc.id).not.toBeNull();
|
||||
expect(data.doc.nonLocalizedGroup.text).toStrictEqual('english');
|
||||
expect(data.doc.localizedGroup.text).toStrictEqual('english');
|
||||
expect(data.doc.nonLocalizedArray[0].localizedEmbeddedText).toStrictEqual('english');
|
||||
expect(data.doc.richTextBlocks[0].content[0].children[0].text).toStrictEqual('english');
|
||||
|
||||
const timestampRegex = /^(\d{4})(?:-?W(\d+)(?:-?(\d+)D?)?|(?:-(\d+))?-(\d+))(?:[T ](\d+):(\d+)(?::(\d+)(?:\.(\d+))?)?)?(?:Z(-?\d*))?$/;
|
||||
expect(data.doc.createdAt).toStrictEqual(expect.stringMatching(timestampRegex));
|
||||
expect(data.doc.updatedAt).toStrictEqual(expect.stringMatching(timestampRegex));
|
||||
@@ -101,6 +126,26 @@ describe('Collections - REST', () => {
|
||||
title: 'title',
|
||||
description: spanishPostDesc,
|
||||
priority: 1,
|
||||
nonLocalizedGroup: {
|
||||
text: 'spanish',
|
||||
},
|
||||
localizedGroup: {
|
||||
text: 'spanish',
|
||||
},
|
||||
nonLocalizedArray: [
|
||||
{
|
||||
localizedEmbeddedText: 'spanish',
|
||||
},
|
||||
],
|
||||
richTextBlocks: [
|
||||
{
|
||||
blockType: 'richTextBlock',
|
||||
blockName: 'Test Block Name',
|
||||
content: [{
|
||||
children: [{ text: 'spanish' }],
|
||||
}],
|
||||
},
|
||||
],
|
||||
}),
|
||||
headers,
|
||||
method: 'put',
|
||||
@@ -110,6 +155,10 @@ describe('Collections - REST', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.doc.description).toBe(spanishPostDesc);
|
||||
expect(data.doc.nonLocalizedGroup.text).toStrictEqual('spanish');
|
||||
expect(data.doc.localizedGroup.text).toStrictEqual('spanish');
|
||||
expect(data.doc.nonLocalizedArray[0].localizedEmbeddedText).toStrictEqual('spanish');
|
||||
expect(data.doc.richTextBlocks[0].content[0].children[0].text).toStrictEqual('spanish');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -121,6 +170,10 @@ describe('Collections - REST', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.description).toBe(englishPostDesc);
|
||||
expect(data.nonLocalizedGroup.text).toStrictEqual('english');
|
||||
expect(data.localizedGroup.text).toStrictEqual('english');
|
||||
expect(data.nonLocalizedArray[0].localizedEmbeddedText).toStrictEqual('english');
|
||||
expect(data.richTextBlocks[0].content[0].children[0].text).toStrictEqual('english');
|
||||
});
|
||||
|
||||
it('should allow a localized post to be retrieved in specified English locale', async () => {
|
||||
@@ -129,6 +182,10 @@ describe('Collections - REST', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.description).toBe(englishPostDesc);
|
||||
expect(data.nonLocalizedGroup.text).toStrictEqual('english');
|
||||
expect(data.localizedGroup.text).toStrictEqual('english');
|
||||
expect(data.nonLocalizedArray[0].localizedEmbeddedText).toStrictEqual('english');
|
||||
expect(data.richTextBlocks[0].content[0].children[0].text).toStrictEqual('english');
|
||||
});
|
||||
|
||||
it('should allow a localized post to be retrieved in Spanish', async () => {
|
||||
@@ -137,6 +194,10 @@ describe('Collections - REST', () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.description).toBe(spanishPostDesc);
|
||||
expect(data.nonLocalizedGroup.text).toStrictEqual('spanish');
|
||||
expect(data.localizedGroup.text).toStrictEqual('spanish');
|
||||
expect(data.nonLocalizedArray[0].localizedEmbeddedText).toStrictEqual('spanish');
|
||||
expect(data.richTextBlocks[0].content[0].children[0].text).toStrictEqual('spanish');
|
||||
});
|
||||
|
||||
it('should allow a localized post to be retrieved in all locales', async () => {
|
||||
|
||||
@@ -171,7 +171,7 @@ const traverseFields = (args: Arguments): void => {
|
||||
if ((operation === 'create' || operation === 'update') && field.name) {
|
||||
const updatedData = data;
|
||||
|
||||
if (data[field.name] === undefined && originalDoc[field.name] === undefined && field.defaultValue) {
|
||||
if (data?.[field.name] === undefined && originalDoc?.[field.name] === undefined && field.defaultValue) {
|
||||
updatedData[field.name] = field.defaultValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ const validationPromise = async ({
|
||||
const hasCondition = field.admin && field.admin.condition;
|
||||
const shouldValidate = field.validate && !hasCondition;
|
||||
|
||||
let valueToValidate = newData[field.name];
|
||||
if (valueToValidate === undefined) valueToValidate = existingData[field.name];
|
||||
let valueToValidate = newData?.[field.name];
|
||||
if (valueToValidate === undefined) valueToValidate = existingData?.[field.name];
|
||||
if (valueToValidate === undefined) valueToValidate = field.defaultValue;
|
||||
|
||||
const result = shouldValidate ? await field.validate(valueToValidate, field) : true;
|
||||
|
||||
@@ -14,7 +14,7 @@ const buildModel = (config: Config): mongoose.PaginateModel<any> | null => {
|
||||
const Globals = mongoose.model('globals', globalsSchema);
|
||||
|
||||
Object.values(config.globals).forEach((globalConfig) => {
|
||||
const globalSchema = buildSchema(globalConfig.fields);
|
||||
const globalSchema = buildSchema(config, globalConfig.fields, {});
|
||||
|
||||
if (config.localization) {
|
||||
globalSchema.plugin(localizationPlugin, config.localization);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import deepmerge from 'deepmerge';
|
||||
import merge from 'lodash.merge';
|
||||
import overwriteMerge from '../../utilities/overwriteMerge';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import removeInternalFields from '../../utilities/removeInternalFields';
|
||||
import { AfterChangeHook, BeforeValidateHook } from '../../collections/config/types';
|
||||
|
||||
async function update(args) {
|
||||
const { globals: { Model } } = this;
|
||||
@@ -39,7 +39,7 @@ async function update(args) {
|
||||
}
|
||||
|
||||
if (locale && global.setLocale) {
|
||||
global.setLocale(locale, fallbackLocale);
|
||||
global.setLocale(locale, null);
|
||||
}
|
||||
|
||||
const globalJSON = global.toJSON({ virtuals: true });
|
||||
@@ -100,10 +100,14 @@ async function update(args) {
|
||||
// 7. Perform database operation
|
||||
// /////////////////////////////////////
|
||||
|
||||
Object.assign(global, data);
|
||||
merge(global, data);
|
||||
|
||||
await global.save();
|
||||
|
||||
if (locale && global.setLocale) {
|
||||
global.setLocale(locale, fallbackLocale);
|
||||
}
|
||||
|
||||
global = global.toJSON({ virtuals: true });
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const sanitizeFallbackLocale = (fallbackLocale) => {
|
||||
if (fallbackLocale === 'null' || fallbackLocale === 'none' || fallbackLocale === 'false' || fallbackLocale === false) {
|
||||
if (fallbackLocale === 'null' || fallbackLocale === 'none' || fallbackLocale === 'false' || fallbackLocale === false || fallbackLocale === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { Schema, SchemaDefinition } from 'mongoose';
|
||||
import { Config } from '../config/types';
|
||||
import { MissingFieldInputOptions } from '../errors';
|
||||
import { ArrayField, Block, BlockField, Field, GroupField, RadioField, RelationshipField, RowField, SelectField, UploadField } from '../fields/config/types';
|
||||
import localizationPlugin from '../localization/plugin';
|
||||
|
||||
type FieldSchemaGenerator = (field: Field, fields: SchemaDefinition) => SchemaDefinition;
|
||||
type FieldSchemaGenerator = (field: Field, fields: SchemaDefinition, config: Config) => SchemaDefinition;
|
||||
|
||||
const setBlockDiscriminators = (fields: Field[], schema: Schema) => {
|
||||
const setBlockDiscriminators = (fields: Field[], schema: Schema, config: Config) => {
|
||||
fields.forEach((field) => {
|
||||
const blockFieldType = field as BlockField;
|
||||
if (blockFieldType.type === 'blocks' && blockFieldType.blocks && blockFieldType.blocks.length > 0) {
|
||||
@@ -15,16 +17,21 @@ const setBlockDiscriminators = (fields: Field[], schema: Schema) => {
|
||||
blockItem.fields.forEach((blockField) => {
|
||||
const fieldSchema: FieldSchemaGenerator = fieldToSchemaMap[blockField.type];
|
||||
if (fieldSchema) {
|
||||
blockSchemaFields = fieldSchema(blockField, blockSchemaFields);
|
||||
blockSchemaFields = fieldSchema(blockField, blockSchemaFields, config);
|
||||
}
|
||||
});
|
||||
|
||||
const blockSchema = new Schema(blockSchemaFields, { _id: false });
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Possible incorrect typing in mongoose types, this works
|
||||
schema.path(field.name).discriminator(blockItem.slug, blockSchema);
|
||||
|
||||
setBlockDiscriminators(blockItem.fields, blockSchema);
|
||||
if (config.localization) {
|
||||
blockSchema.plugin(localizationPlugin, config.localization);
|
||||
}
|
||||
|
||||
setBlockDiscriminators(blockItem.fields, blockSchema, config);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -44,20 +51,20 @@ const formatBaseSchema = (field: Field) => {
|
||||
};
|
||||
};
|
||||
|
||||
const buildSchema = (configFields: Field[], options = {}): Schema => {
|
||||
const buildSchema = (config: Config, configFields: Field[], options = {}): Schema => {
|
||||
let fields = {};
|
||||
|
||||
configFields.forEach((field) => {
|
||||
const fieldSchema: FieldSchemaGenerator = fieldToSchemaMap[field.type];
|
||||
|
||||
if (fieldSchema) {
|
||||
fields = fieldSchema(field, fields);
|
||||
fields = fieldSchema(field, fields, config);
|
||||
}
|
||||
});
|
||||
|
||||
const schema = new Schema(fields, options);
|
||||
|
||||
setBlockDiscriminators(configFields, schema);
|
||||
setBlockDiscriminators(configFields, schema, config);
|
||||
|
||||
return schema;
|
||||
};
|
||||
@@ -153,22 +160,22 @@ const fieldToSchemaMap = {
|
||||
[field.name]: schema,
|
||||
};
|
||||
},
|
||||
row: (field: RowField, fields: SchemaDefinition): SchemaDefinition => {
|
||||
row: (field: RowField, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const newFields = { ...fields };
|
||||
|
||||
field.fields.forEach((rowField: Field) => {
|
||||
const fieldSchemaMap: FieldSchemaGenerator = fieldToSchemaMap[rowField.type];
|
||||
|
||||
if (fieldSchemaMap) {
|
||||
const fieldSchema = fieldSchemaMap(rowField, fields);
|
||||
const fieldSchema = fieldSchemaMap(rowField, fields, config);
|
||||
newFields[rowField.name] = fieldSchema[rowField.name];
|
||||
}
|
||||
});
|
||||
|
||||
return newFields;
|
||||
},
|
||||
array: (field: ArrayField, fields: SchemaDefinition) => {
|
||||
const schema = buildSchema(field.fields, { _id: false, id: false });
|
||||
array: (field: ArrayField, fields: SchemaDefinition, config: Config) => {
|
||||
const schema = buildSchema(config, field.fields, { _id: false, id: false });
|
||||
|
||||
return {
|
||||
...fields,
|
||||
@@ -178,8 +185,8 @@ const fieldToSchemaMap = {
|
||||
},
|
||||
};
|
||||
},
|
||||
group: (field: GroupField, fields: SchemaDefinition): SchemaDefinition => {
|
||||
const schema = buildSchema(field.fields, { _id: false, id: false });
|
||||
group: (field: GroupField, fields: SchemaDefinition, config: Config): SchemaDefinition => {
|
||||
const schema = buildSchema(config, field.fields, { _id: false, id: false });
|
||||
|
||||
return {
|
||||
...fields,
|
||||
@@ -210,12 +217,12 @@ const fieldToSchemaMap = {
|
||||
};
|
||||
},
|
||||
blocks: (field: BlockField, fields: SchemaDefinition) => {
|
||||
const flexibleSchema = new Schema({ blockName: String }, { discriminatorKey: 'blockType', _id: false, id: false });
|
||||
const blocksSchema = new Schema({ blockName: String }, { discriminatorKey: 'blockType', _id: false, id: false });
|
||||
|
||||
return {
|
||||
...fields,
|
||||
[field.name]: {
|
||||
type: [flexibleSchema],
|
||||
type: [blocksSchema],
|
||||
localized: field.localized || false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8222,6 +8222,11 @@ lodash.memoize@^4.1.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
|
||||
Reference in New Issue
Block a user