feat: confine conditions inside list to the same local list item (#863)
This commit is contained in:
parent
82a8e11ab3
commit
5602812774
@ -569,6 +569,64 @@ collections:
|
|||||||
- label: File
|
- label: File
|
||||||
name: file
|
name: file
|
||||||
widget: file
|
widget: file
|
||||||
|
- name: typed_list_with_condition
|
||||||
|
label: Typed List With Condition
|
||||||
|
widget: list
|
||||||
|
types:
|
||||||
|
- label: Type 1 Object
|
||||||
|
name: type_1_object
|
||||||
|
widget: object
|
||||||
|
fields:
|
||||||
|
- name: template
|
||||||
|
label: template
|
||||||
|
widget: select
|
||||||
|
options:
|
||||||
|
- column
|
||||||
|
- row
|
||||||
|
- banner
|
||||||
|
default: column
|
||||||
|
- label: String
|
||||||
|
name: string
|
||||||
|
widget: string
|
||||||
|
- label: Boolean
|
||||||
|
name: boolean
|
||||||
|
widget: boolean
|
||||||
|
condition:
|
||||||
|
field: template
|
||||||
|
value: banner
|
||||||
|
- label: Text
|
||||||
|
name: text
|
||||||
|
widget: text
|
||||||
|
- label: Type 2 Object
|
||||||
|
name: type_2_object
|
||||||
|
widget: object
|
||||||
|
fields:
|
||||||
|
- label: Number
|
||||||
|
name: number
|
||||||
|
widget: number
|
||||||
|
- label: Select
|
||||||
|
name: select
|
||||||
|
widget: select
|
||||||
|
options:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
|
- c
|
||||||
|
- label: Datetime
|
||||||
|
name: datetime
|
||||||
|
widget: datetime
|
||||||
|
- label: Markdown
|
||||||
|
name: markdown
|
||||||
|
widget: markdown
|
||||||
|
- label: Type 3 Object
|
||||||
|
name: type_3_object
|
||||||
|
widget: object
|
||||||
|
fields:
|
||||||
|
- label: Image
|
||||||
|
name: image
|
||||||
|
widget: image
|
||||||
|
- label: File
|
||||||
|
name: file
|
||||||
|
widget: file
|
||||||
- name: map
|
- name: map
|
||||||
label: Map
|
label: Map
|
||||||
file: _widgets/map.json
|
file: _widgets/map.json
|
||||||
|
@ -1045,7 +1045,7 @@ export class Backend<EF extends BaseField = UnknownField, BC extends BackendClas
|
|||||||
}
|
}
|
||||||
|
|
||||||
filterEntries(collection: { entries: Entry[] }, filterRule: FilterRule | FilterRule[]) {
|
filterEntries(collection: { entries: Entry[] }, filterRule: FilterRule | FilterRule[]) {
|
||||||
return filterEntries(collection.entries, filterRule);
|
return filterEntries(collection.entries, filterRule, undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ const EditorControl = ({
|
|||||||
t,
|
t,
|
||||||
value,
|
value,
|
||||||
forList = false,
|
forList = false,
|
||||||
|
listItemPath,
|
||||||
forSingleList = false,
|
forSingleList = false,
|
||||||
changeDraftField,
|
changeDraftField,
|
||||||
i18n,
|
i18n,
|
||||||
@ -94,7 +95,7 @@ const EditorControl = ({
|
|||||||
() => isFieldHidden(field, locale, i18n?.defaultLocale),
|
() => isFieldHidden(field, locale, i18n?.defaultLocale),
|
||||||
[field, i18n?.defaultLocale, locale],
|
[field, i18n?.defaultLocale, locale],
|
||||||
);
|
);
|
||||||
const hidden = useHidden(field, entry);
|
const hidden = useHidden(field, entry, listItemPath);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
@ -202,6 +203,7 @@ const EditorControl = ({
|
|||||||
t,
|
t,
|
||||||
value: finalValue,
|
value: finalValue,
|
||||||
forList,
|
forList,
|
||||||
|
listItemPath,
|
||||||
forSingleList,
|
forSingleList,
|
||||||
i18n,
|
i18n,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
@ -231,6 +233,7 @@ const EditorControl = ({
|
|||||||
query,
|
query,
|
||||||
finalValue,
|
finalValue,
|
||||||
forList,
|
forList,
|
||||||
|
listItemPath,
|
||||||
forSingleList,
|
forSingleList,
|
||||||
i18n,
|
i18n,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
@ -248,6 +251,7 @@ interface EditorControlOwnProps {
|
|||||||
parentPath: string;
|
parentPath: string;
|
||||||
value: ValueOrNestedValue;
|
value: ValueOrNestedValue;
|
||||||
forList?: boolean;
|
forList?: boolean;
|
||||||
|
listItemPath?: string;
|
||||||
forSingleList?: boolean;
|
forSingleList?: boolean;
|
||||||
i18n: I18nSettings | undefined;
|
i18n: I18nSettings | undefined;
|
||||||
fieldName?: string;
|
fieldName?: string;
|
||||||
|
@ -32,6 +32,7 @@ export interface EditorControlPaneProps {
|
|||||||
onLocaleChange?: (locale: string) => void;
|
onLocaleChange?: (locale: string) => void;
|
||||||
allowDefaultLocale?: boolean;
|
allowDefaultLocale?: boolean;
|
||||||
context?: 'default' | 'i18nSplit';
|
context?: 'default' | 'i18nSplit';
|
||||||
|
listItemPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditorControlPane = ({
|
const EditorControlPane = ({
|
||||||
@ -47,6 +48,7 @@ const EditorControlPane = ({
|
|||||||
onLocaleChange,
|
onLocaleChange,
|
||||||
allowDefaultLocale = false,
|
allowDefaultLocale = false,
|
||||||
context = 'default',
|
context = 'default',
|
||||||
|
listItemPath,
|
||||||
t,
|
t,
|
||||||
}: TranslatedProps<EditorControlPaneProps>) => {
|
}: TranslatedProps<EditorControlPaneProps>) => {
|
||||||
const pathField = useMemo(
|
const pathField = useMemo(
|
||||||
@ -138,6 +140,7 @@ const EditorControlPane = ({
|
|||||||
locale={locale}
|
locale={locale}
|
||||||
parentPath=""
|
parentPath=""
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
listItemPath={listItemPath}
|
||||||
controlled
|
controlled
|
||||||
isMeta
|
isMeta
|
||||||
/>
|
/>
|
||||||
@ -156,6 +159,7 @@ const EditorControlPane = ({
|
|||||||
locale={locale}
|
locale={locale}
|
||||||
parentPath=""
|
parentPath=""
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
listItemPath={listItemPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -311,6 +311,7 @@ export interface WidgetControlProps<T, F extends BaseField = UnknownField, EV =
|
|||||||
fieldsErrors: FieldsErrors;
|
fieldsErrors: FieldsErrors;
|
||||||
submitted: boolean;
|
submitted: boolean;
|
||||||
forList: boolean;
|
forList: boolean;
|
||||||
|
listItemPath: string | undefined;
|
||||||
forSingleList: boolean;
|
forSingleList: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
duplicate: boolean;
|
duplicate: boolean;
|
||||||
|
@ -71,30 +71,86 @@ describe('filterEntries', () => {
|
|||||||
|
|
||||||
describe('isHidden', () => {
|
describe('isHidden', () => {
|
||||||
it('should show field by default', () => {
|
it('should show field by default', () => {
|
||||||
expect(isHidden(mockTitleField, mockExternalEntry)).toBeFalsy();
|
expect(isHidden(mockTitleField, mockExternalEntry, undefined)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should hide field if single condition is not met', () => {
|
it('should hide field if single condition is not met', () => {
|
||||||
expect(isHidden(mockUrlField, mockInternalEntry)).toBeTruthy();
|
expect(isHidden(mockUrlField, mockInternalEntry, undefined)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show field if single condition is met', () => {
|
it('should show field if single condition is met', () => {
|
||||||
expect(isHidden(mockUrlField, mockExternalEntry)).toBeFalsy();
|
expect(isHidden(mockUrlField, mockExternalEntry, undefined)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should hide field if all multiple conditions are not met', () => {
|
it('should hide field if all multiple conditions are not met', () => {
|
||||||
expect(isHidden(mockBodyField, mockExternalEntry)).toBeTruthy();
|
expect(isHidden(mockBodyField, mockExternalEntry, undefined)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show field if single condition is met', () => {
|
it('should show field if single condition is met', () => {
|
||||||
expect(isHidden(mockBodyField, mockHasSummaryEntry)).toBeFalsy();
|
expect(isHidden(mockBodyField, mockHasSummaryEntry, undefined)).toBeFalsy();
|
||||||
expect(isHidden(mockBodyField, mockInternalEntry)).toBeFalsy();
|
expect(isHidden(mockBodyField, mockInternalEntry, undefined)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show field if entry is undefined', () => {
|
it('should show field if entry is undefined', () => {
|
||||||
expect(isHidden(mockTitleField, undefined)).toBeFalsy();
|
expect(isHidden(mockTitleField, undefined, undefined)).toBeFalsy();
|
||||||
expect(isHidden(mockUrlField, undefined)).toBeFalsy();
|
expect(isHidden(mockUrlField, undefined, undefined)).toBeFalsy();
|
||||||
expect(isHidden(mockBodyField, undefined)).toBeFalsy();
|
expect(isHidden(mockBodyField, undefined, undefined)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('inside list', () => {
|
||||||
|
const mockInsideListEntry = createMockEntry({
|
||||||
|
path: 'path/to/file-1.md',
|
||||||
|
data: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
title: 'I am a title',
|
||||||
|
type: 'external',
|
||||||
|
url: 'http://example.com',
|
||||||
|
hasSummary: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'I am a title',
|
||||||
|
type: 'internal',
|
||||||
|
body: 'I am the body of your post',
|
||||||
|
hasSummary: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'I am a title',
|
||||||
|
type: 'external',
|
||||||
|
url: 'http://example.com',
|
||||||
|
body: 'I am the body of your post',
|
||||||
|
hasSummary: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show field by default', () => {
|
||||||
|
expect(isHidden(mockTitleField, mockInsideListEntry, 'list.0')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide field if single condition is not met', () => {
|
||||||
|
expect(isHidden(mockUrlField, mockInsideListEntry, 'list.1')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show field if single condition is met', () => {
|
||||||
|
expect(isHidden(mockUrlField, mockInsideListEntry, 'list.0')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide field if all multiple conditions are not met', () => {
|
||||||
|
expect(isHidden(mockBodyField, mockInsideListEntry, 'list.0')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show field if single condition is met', () => {
|
||||||
|
expect(isHidden(mockBodyField, mockInsideListEntry, 'list.2')).toBeFalsy();
|
||||||
|
expect(isHidden(mockBodyField, mockInsideListEntry, 'list.1')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show field if entry is undefined', () => {
|
||||||
|
expect(isHidden(mockTitleField, undefined, 'list.0')).toBeFalsy();
|
||||||
|
expect(isHidden(mockUrlField, undefined, 'list.0')).toBeFalsy();
|
||||||
|
expect(isHidden(mockBodyField, undefined, 'list.0')).toBeFalsy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -87,35 +87,36 @@ describe('filterEntries', () => {
|
|||||||
|
|
||||||
describe('field rules', () => {
|
describe('field rules', () => {
|
||||||
it('should filter fields', () => {
|
it('should filter fields', () => {
|
||||||
expect(filterEntries(entries, { field: 'language', value: 'en' })).toEqual([
|
expect(filterEntries(entries, { field: 'language', value: 'en' }, undefined)).toEqual([
|
||||||
mockEnglishEntry,
|
mockEnglishEntry,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(filterEntries(entries, { field: 'language', value: 'fr' })).toEqual([mockFrenchEntry]);
|
expect(filterEntries(entries, { field: 'language', value: 'fr' }, undefined)).toEqual([
|
||||||
|
mockFrenchEntry,
|
||||||
|
]);
|
||||||
|
|
||||||
expect(filterEntries(entries, { field: 'language', value: 'gr' })).toEqual([
|
expect(filterEntries(entries, { field: 'language', value: 'gr' }, undefined)).toEqual([
|
||||||
mockIndexEntry,
|
mockIndexEntry,
|
||||||
mockUnderscoreIndexEntry,
|
mockUnderscoreIndexEntry,
|
||||||
mockRandomFileNameEntry,
|
mockRandomFileNameEntry,
|
||||||
mockTags1and4Entry,
|
mockTags1and4Entry,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(filterEntries(entries, { field: 'draft', value: true })).toEqual([
|
expect(filterEntries(entries, { field: 'draft', value: true }, undefined)).toEqual([
|
||||||
mockEnglishEntry,
|
mockEnglishEntry,
|
||||||
mockTags1and4Entry,
|
mockTags1and4Entry,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter fields if multiple filter values are provided (must match only one)', () => {
|
it('should filter fields if multiple filter values are provided (must match only one)', () => {
|
||||||
expect(filterEntries(entries, { field: 'language', value: ['en', 'fr'] })).toEqual([
|
expect(filterEntries(entries, { field: 'language', value: ['en', 'fr'] }, undefined)).toEqual(
|
||||||
mockEnglishEntry,
|
[mockEnglishEntry, mockFrenchEntry],
|
||||||
mockFrenchEntry,
|
);
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter fields based on pattern', () => {
|
it('should filter fields based on pattern', () => {
|
||||||
// Languages with an r in their name
|
// Languages with an r in their name
|
||||||
expect(filterEntries(entries, { field: 'language', pattern: 'r' })).toEqual([
|
expect(filterEntries(entries, { field: 'language', pattern: 'r' }, undefined)).toEqual([
|
||||||
mockFrenchEntry,
|
mockFrenchEntry,
|
||||||
mockIndexEntry,
|
mockIndexEntry,
|
||||||
mockUnderscoreIndexEntry,
|
mockUnderscoreIndexEntry,
|
||||||
@ -125,20 +126,22 @@ describe('filterEntries', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should filter fields if field value is an array (must include value)', () => {
|
it('should filter fields if field value is an array (must include value)', () => {
|
||||||
expect(filterEntries(entries, { field: 'tags', value: 'tag-4' })).toEqual([
|
expect(filterEntries(entries, { field: 'tags', value: 'tag-4' }, undefined)).toEqual([
|
||||||
mockFrenchEntry,
|
mockFrenchEntry,
|
||||||
mockIndexEntry,
|
mockIndexEntry,
|
||||||
mockTags1and4Entry,
|
mockTags1and4Entry,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(filterEntries(entries, { field: 'numbers', value: 8 })).toEqual([
|
expect(filterEntries(entries, { field: 'numbers', value: 8 }, undefined)).toEqual([
|
||||||
mockRandomFileNameEntry,
|
mockRandomFileNameEntry,
|
||||||
mockTags1and4Entry,
|
mockTags1and4Entry,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter fields if field value is an array and multiple filter values are provided (must include only one)', () => {
|
it('should filter fields if field value is an array and multiple filter values are provided (must include only one)', () => {
|
||||||
expect(filterEntries(entries, { field: 'tags', value: ['tag-3', 'tag-4'] })).toEqual([
|
expect(
|
||||||
|
filterEntries(entries, { field: 'tags', value: ['tag-3', 'tag-4'] }, undefined),
|
||||||
|
).toEqual([
|
||||||
mockFrenchEntry,
|
mockFrenchEntry,
|
||||||
mockIndexEntry,
|
mockIndexEntry,
|
||||||
mockUnderscoreIndexEntry,
|
mockUnderscoreIndexEntry,
|
||||||
@ -146,7 +149,7 @@ describe('filterEntries', () => {
|
|||||||
mockTags1and4Entry,
|
mockTags1and4Entry,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(filterEntries(entries, { field: 'numbers', value: ['5', '6'] })).toEqual([
|
expect(filterEntries(entries, { field: 'numbers', value: ['5', '6'] }, undefined)).toEqual([
|
||||||
mockEnglishEntry,
|
mockEnglishEntry,
|
||||||
mockFrenchEntry,
|
mockFrenchEntry,
|
||||||
mockIndexEntry,
|
mockIndexEntry,
|
||||||
@ -155,17 +158,21 @@ describe('filterEntries', () => {
|
|||||||
|
|
||||||
it('should match all values if matchAll is one (value is an array, multiple filter values are provided)', () => {
|
it('should match all values if matchAll is one (value is an array, multiple filter values are provided)', () => {
|
||||||
expect(
|
expect(
|
||||||
filterEntries(entries, { field: 'tags', value: ['tag-1', 'tag-4'], matchAll: true }),
|
filterEntries(
|
||||||
|
entries,
|
||||||
|
{ field: 'tags', value: ['tag-1', 'tag-4'], matchAll: true },
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
).toEqual([mockFrenchEntry, mockIndexEntry, mockTags1and4Entry]);
|
).toEqual([mockFrenchEntry, mockIndexEntry, mockTags1and4Entry]);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
filterEntries(entries, { field: 'numbers', value: ['5', '6'], matchAll: true }),
|
filterEntries(entries, { field: 'numbers', value: ['5', '6'], matchAll: true }, undefined),
|
||||||
).toEqual([mockFrenchEntry, mockIndexEntry]);
|
).toEqual([mockFrenchEntry, mockIndexEntry]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter fields based on pattern when value is an array', () => {
|
it('should filter fields based on pattern when value is an array', () => {
|
||||||
// Tags containing the word "fish"
|
// Tags containing the word "fish"
|
||||||
expect(filterEntries(entries, { field: 'tags', pattern: 'fish' })).toEqual([
|
expect(filterEntries(entries, { field: 'tags', pattern: 'fish' }, undefined)).toEqual([
|
||||||
mockEnglishEntry,
|
mockEnglishEntry,
|
||||||
mockTags1and4Entry,
|
mockTags1and4Entry,
|
||||||
]);
|
]);
|
||||||
@ -173,10 +180,14 @@ describe('filterEntries', () => {
|
|||||||
|
|
||||||
it('should filter based on multiple rules (must match all rules)', () => {
|
it('should filter based on multiple rules (must match all rules)', () => {
|
||||||
expect(
|
expect(
|
||||||
filterEntries(entries, [
|
filterEntries(
|
||||||
{ field: 'tags', value: ['tag-3', 'tag-4'] },
|
entries,
|
||||||
{ field: 'language', value: 'gr' },
|
[
|
||||||
]),
|
{ field: 'tags', value: ['tag-3', 'tag-4'] },
|
||||||
|
{ field: 'language', value: 'gr' },
|
||||||
|
],
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
).toEqual([
|
).toEqual([
|
||||||
mockIndexEntry,
|
mockIndexEntry,
|
||||||
mockUnderscoreIndexEntry,
|
mockUnderscoreIndexEntry,
|
||||||
@ -187,29 +198,35 @@ describe('filterEntries', () => {
|
|||||||
|
|
||||||
it('should filter based on multiple rules (must match all rules) (matchAll on)', () => {
|
it('should filter based on multiple rules (must match all rules) (matchAll on)', () => {
|
||||||
expect(
|
expect(
|
||||||
filterEntries(entries, [
|
filterEntries(
|
||||||
{ field: 'tags', value: ['tag-1', 'tag-4'], matchAll: true },
|
entries,
|
||||||
{ field: 'language', value: 'gr' },
|
[
|
||||||
]),
|
{ field: 'tags', value: ['tag-1', 'tag-4'], matchAll: true },
|
||||||
|
{ field: 'language', value: 'gr' },
|
||||||
|
],
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
).toEqual([mockIndexEntry, mockTags1and4Entry]);
|
).toEqual([mockIndexEntry, mockTags1and4Entry]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('file rule', () => {
|
describe('file rule', () => {
|
||||||
it('should filter based on file name', () => {
|
it('should filter based on file name', () => {
|
||||||
expect(filterEntries(entries, { pattern: '^index.md$' })).toEqual([mockIndexEntry]);
|
expect(filterEntries(entries, { pattern: '^index.md$' }, undefined)).toEqual([
|
||||||
|
mockIndexEntry,
|
||||||
|
]);
|
||||||
|
|
||||||
expect(filterEntries(entries, { pattern: '^_index.md$' })).toEqual([
|
expect(filterEntries(entries, { pattern: '^_index.md$' }, undefined)).toEqual([
|
||||||
mockUnderscoreIndexEntry,
|
mockUnderscoreIndexEntry,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(filterEntries(entries, { pattern: 'index.md$' })).toEqual([
|
expect(filterEntries(entries, { pattern: 'index.md$' }, undefined)).toEqual([
|
||||||
mockIndexEntry,
|
mockIndexEntry,
|
||||||
mockUnderscoreIndexEntry,
|
mockUnderscoreIndexEntry,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// File names containing the word file (case insensitive)
|
// File names containing the word file (case insensitive)
|
||||||
expect(filterEntries(entries, { pattern: '[fF][iI][lL][eE]' })).toEqual([
|
expect(filterEntries(entries, { pattern: '[fF][iI][lL][eE]' }, undefined)).toEqual([
|
||||||
mockEnglishEntry,
|
mockEnglishEntry,
|
||||||
mockFrenchEntry,
|
mockFrenchEntry,
|
||||||
mockRandomFileNameEntry,
|
mockRandomFileNameEntry,
|
||||||
@ -219,7 +236,7 @@ describe('filterEntries', () => {
|
|||||||
it('should filter based on multiple rules (must match all rules)', () => {
|
it('should filter based on multiple rules (must match all rules)', () => {
|
||||||
// File names containing the word file (case insensitive)
|
// File names containing the word file (case insensitive)
|
||||||
expect(
|
expect(
|
||||||
filterEntries(entries, [{ pattern: '[fF][iI][lL][eE]' }, { pattern: 'some' }]),
|
filterEntries(entries, [{ pattern: '[fF][iI][lL][eE]' }, { pattern: 'some' }], undefined),
|
||||||
).toEqual([mockRandomFileNameEntry]);
|
).toEqual([mockRandomFileNameEntry]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -227,16 +244,20 @@ describe('filterEntries', () => {
|
|||||||
describe('combined field and file rule', () => {
|
describe('combined field and file rule', () => {
|
||||||
it('should filter based on multiple rules (must match all rules)', () => {
|
it('should filter based on multiple rules (must match all rules)', () => {
|
||||||
expect(
|
expect(
|
||||||
filterEntries(entries, [{ pattern: 'index.md$' }, { field: 'tags', value: 'tag-3' }]),
|
filterEntries(
|
||||||
|
entries,
|
||||||
|
[{ pattern: 'index.md$' }, { field: 'tags', value: 'tag-3' }],
|
||||||
|
undefined,
|
||||||
|
),
|
||||||
).toEqual([mockUnderscoreIndexEntry]);
|
).toEqual([mockUnderscoreIndexEntry]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('nested fields', () => {
|
describe('nested fields', () => {
|
||||||
it('should filter based on multiple rules (must match all rules)', () => {
|
it('should filter based on multiple rules (must match all rules)', () => {
|
||||||
expect(filterEntries(entries, [{ field: 'nested.object.field', value: 'yes' }])).toEqual([
|
expect(
|
||||||
mockNestedEntry,
|
filterEntries(entries, [{ field: 'nested.object.field', value: 'yes' }], undefined),
|
||||||
]);
|
).toEqual([mockNestedEntry]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -91,22 +91,30 @@ export function getFieldValue(
|
|||||||
return entry.data?.[field.name];
|
return entry.data?.[field.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isHidden(field: Field, entry: Entry | undefined): boolean {
|
export function isHidden(
|
||||||
|
field: Field,
|
||||||
|
entry: Entry | undefined,
|
||||||
|
listItemPath: string | undefined,
|
||||||
|
): boolean {
|
||||||
if (field.condition) {
|
if (field.condition) {
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(field.condition)) {
|
if (Array.isArray(field.condition)) {
|
||||||
return !field.condition.find(r => entryMatchesFieldRule(entry, r));
|
return !field.condition.find(r => entryMatchesFieldRule(entry, r, listItemPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return !entryMatchesFieldRule(entry, field.condition);
|
return !entryMatchesFieldRule(entry, field.condition, listItemPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useHidden(field: Field, entry: Entry | undefined): boolean {
|
export function useHidden(
|
||||||
return useMemo(() => isHidden(field, entry), [entry, field]);
|
field: Field,
|
||||||
|
entry: Entry | undefined,
|
||||||
|
listItemPath: string | undefined,
|
||||||
|
): boolean {
|
||||||
|
return useMemo(() => isHidden(field, entry, listItemPath), [entry, field, listItemPath]);
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,15 @@ import get from 'lodash/get';
|
|||||||
|
|
||||||
import type { Entry, FieldFilterRule, FilterRule } from '@staticcms/core/interface';
|
import type { Entry, FieldFilterRule, FilterRule } from '@staticcms/core/interface';
|
||||||
|
|
||||||
export function entryMatchesFieldRule(entry: Entry, filterRule: FieldFilterRule): boolean {
|
export function entryMatchesFieldRule(
|
||||||
const fieldValue = get(entry.data, filterRule.field);
|
entry: Entry,
|
||||||
|
filterRule: FieldFilterRule,
|
||||||
|
listItemPath: string | undefined,
|
||||||
|
): boolean {
|
||||||
|
const fieldValue = get(
|
||||||
|
entry.data,
|
||||||
|
listItemPath ? `${listItemPath}.${filterRule.field}` : filterRule.field,
|
||||||
|
);
|
||||||
if ('pattern' in filterRule) {
|
if ('pattern' in filterRule) {
|
||||||
if (Array.isArray(fieldValue)) {
|
if (Array.isArray(fieldValue)) {
|
||||||
return Boolean(fieldValue.find(v => new RegExp(filterRule.pattern).test(String(v))));
|
return Boolean(fieldValue.find(v => new RegExp(filterRule.pattern).test(String(v))));
|
||||||
@ -48,20 +55,24 @@ export function entryMatchesFieldRule(entry: Entry, filterRule: FieldFilterRule)
|
|||||||
return String(fieldValue) === String(filterRule.value);
|
return String(fieldValue) === String(filterRule.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function entryMatchesRule(entry: Entry, filterRule: FilterRule) {
|
function entryMatchesRule(entry: Entry, filterRule: FilterRule, listItemPath: string | undefined) {
|
||||||
if ('field' in filterRule) {
|
if ('field' in filterRule) {
|
||||||
return entryMatchesFieldRule(entry, filterRule);
|
return entryMatchesFieldRule(entry, filterRule, listItemPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RegExp(filterRule.pattern).test(parse(entry.path).base);
|
return new RegExp(filterRule.pattern).test(parse(entry.path).base);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function filterEntries(entries: Entry[], filterRule: FilterRule | FilterRule[]) {
|
export default function filterEntries(
|
||||||
|
entries: Entry[],
|
||||||
|
filterRule: FilterRule | FilterRule[],
|
||||||
|
listItemPath: string | undefined,
|
||||||
|
) {
|
||||||
return entries.filter(entry => {
|
return entries.filter(entry => {
|
||||||
if (Array.isArray(filterRule)) {
|
if (Array.isArray(filterRule)) {
|
||||||
return filterRule.every(r => entryMatchesRule(entry, r));
|
return filterRule.every(r => entryMatchesRule(entry, r, listItemPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return entryMatchesRule(entry, filterRule);
|
return entryMatchesRule(entry, filterRule, listItemPath);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -207,6 +207,7 @@ const ListItem: FC<ListItemProps> = ({
|
|||||||
locale={locale}
|
locale={locale}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
forList={true}
|
forList={true}
|
||||||
|
listItemPath={`${path}.${objectField.name}`}
|
||||||
forSingleList={isSingleList}
|
forSingleList={isSingleList}
|
||||||
/>
|
/>
|
||||||
</ListItemWrapper>
|
</ListItemWrapper>
|
||||||
|
@ -69,8 +69,6 @@ const InsertLinkToolbarButton: FC<InsertLinkToolbarButtonProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('editor.selection', editor.selection);
|
|
||||||
|
|
||||||
deleteText(editor, {
|
deleteText(editor, {
|
||||||
at: editor.selection as unknown as Location,
|
at: editor.selection as unknown as Location,
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ const ObjectControl: FC<WidgetControlProps<ObjectValue, ObjectField>> = ({
|
|||||||
errors,
|
errors,
|
||||||
disabled,
|
disabled,
|
||||||
value = {},
|
value = {},
|
||||||
|
listItemPath,
|
||||||
}) => {
|
}) => {
|
||||||
const objectLabel = useMemo(() => {
|
const objectLabel = useMemo(() => {
|
||||||
const summary = field.summary;
|
const summary = field.summary;
|
||||||
@ -59,6 +60,7 @@ const ObjectControl: FC<WidgetControlProps<ObjectValue, ObjectField>> = ({
|
|||||||
locale={locale}
|
locale={locale}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
forSingleList={forSingleList}
|
forSingleList={forSingleList}
|
||||||
|
listItemPath={listItemPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}) ?? null
|
}) ?? null
|
||||||
@ -75,6 +77,7 @@ const ObjectControl: FC<WidgetControlProps<ObjectValue, ObjectField>> = ({
|
|||||||
locale,
|
locale,
|
||||||
i18n,
|
i18n,
|
||||||
forSingleList,
|
forSingleList,
|
||||||
|
listItemPath,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (fields.length) {
|
if (fields.length) {
|
||||||
|
@ -65,6 +65,7 @@ export const createMockWidgetControlProps = <
|
|||||||
hasErrors,
|
hasErrors,
|
||||||
submitted: false,
|
submitted: false,
|
||||||
forList: false,
|
forList: false,
|
||||||
|
listItemPath: undefined,
|
||||||
forSingleList: false,
|
forSingleList: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
locale: undefined,
|
locale: undefined,
|
||||||
|
@ -81,7 +81,7 @@ The `condition` option can take a single filter rule or a list of filter rules.
|
|||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
The example below creates a collection based on a nested field's value.
|
The example below conditionally shows fields based on the values of other fields.
|
||||||
|
|
||||||
<CodeTabs>
|
<CodeTabs>
|
||||||
```yaml
|
```yaml
|
||||||
@ -163,7 +163,7 @@ collections: [
|
|||||||
|
|
||||||
### Nested Field Example
|
### Nested Field Example
|
||||||
|
|
||||||
The example below creates a collection based on a nested field's value.
|
The example below conditionally shows fields based on the values of other nested fields.
|
||||||
|
|
||||||
<CodeTabs>
|
<CodeTabs>
|
||||||
```yaml
|
```yaml
|
||||||
@ -241,3 +241,96 @@ collections: [
|
|||||||
```
|
```
|
||||||
|
|
||||||
</CodeTabs>
|
</CodeTabs>
|
||||||
|
|
||||||
|
### List Field Example
|
||||||
|
|
||||||
|
The example below conditionally shows fields inside a list based on the values of other fields in the same list item. This works with both `fields` or `types`.
|
||||||
|
|
||||||
|
<CodeTabs>
|
||||||
|
```yaml
|
||||||
|
collections:
|
||||||
|
- name: list-field-filtered-collection
|
||||||
|
label: List Field Filtered Collection
|
||||||
|
folder: _list_field_condition
|
||||||
|
create: true
|
||||||
|
fields:
|
||||||
|
- name: list
|
||||||
|
label: List Field
|
||||||
|
widget: list
|
||||||
|
fields:
|
||||||
|
- name: value
|
||||||
|
label: Value 1
|
||||||
|
widget: string
|
||||||
|
condition:
|
||||||
|
field: nested.object.field
|
||||||
|
value: yes
|
||||||
|
- name: nested
|
||||||
|
label: Nested
|
||||||
|
widget: object
|
||||||
|
fields:
|
||||||
|
- name: object
|
||||||
|
label: Object
|
||||||
|
widget: object
|
||||||
|
fields:
|
||||||
|
- name: field
|
||||||
|
label: Field
|
||||||
|
widget: select
|
||||||
|
options:
|
||||||
|
- yes
|
||||||
|
- no
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
collections: [
|
||||||
|
{
|
||||||
|
name: "list-field-filtered-collection",
|
||||||
|
label: "List Field Filtered Collection",
|
||||||
|
folder: "_list_field_condition",
|
||||||
|
create: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "list",
|
||||||
|
label: "List Field",
|
||||||
|
widget: "list",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "value",
|
||||||
|
label: "Value 1",
|
||||||
|
widget: "string",
|
||||||
|
condition: {
|
||||||
|
field: "nested.object.field",
|
||||||
|
value: "yes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested",
|
||||||
|
label: "Nested",
|
||||||
|
widget: "object",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "object",
|
||||||
|
label: "Object",
|
||||||
|
widget: "object",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "field",
|
||||||
|
label: "Field",
|
||||||
|
widget: "select",
|
||||||
|
options: [
|
||||||
|
"yes",
|
||||||
|
"no"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTabs>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user