Skip to content

Commit f6a909d

Browse files
feat: default content type (#583)
* feat: add default content type to config * feat: refactor settings page * feat: update readme * chore: refactor tests * chore: fix build and refresh lockfile
1 parent 2539302 commit f6a909d

35 files changed

Lines changed: 1290 additions & 1070 deletions

File tree

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ Config for this plugin is stored as a part of the `config/plugins.{js|ts}` or `c
147147
config: {
148148
additionalFields: ['audience', { name: 'my_custom_field', type: 'boolean', label: 'My custom field' }],
149149
contentTypes: ['api::page.page'],
150+
defaultContentTypes: 'api::page.page',
150151
contentTypesNameFields: {
151152
'api::page.page': ['title']
152153
},
@@ -165,6 +166,7 @@ Config for this plugin is stored as a part of the `config/plugins.{js|ts}` or `c
165166
- `additionalFields` - Additional fields for navigation items. More **[ here ](#additional-fields)**
166167
- `allowedLevels` - Maximum level for which you're able to mark item as "Menu attached"
167168
- `contentTypes` - UIDs of related content types
169+
- `defaultContentTypes` - UID of content type that will be selected by default while creating a new navigation item
168170
- `contentTypesNameFields` - Definition of content type title fields like `'api::<collection name>.<content type name>': ['field_name_1', 'field_name_2']`, if not set titles are pulled from fields like `['title', 'subject', 'name']`. **TIP** - Proper content type uid you can find in the URL of Content Manager where you're managing relevant entities like: `admin/content-manager/collectionType/< THE UID HERE >?page=1&pageSize=10&sort=Title:ASC&plugins[i18n][locale]=en`
169171
- `pathDefaultFields` - The attribute to copy the default path from per content type. Syntax: `'api::<collection name>.<content type name>': ['url_slug', 'path']`
170172
- `gql` - If you're using GraphQL that's the right place to put all necessary settings. More **[ here ](#gql-configuration)**
@@ -689,13 +691,16 @@ Example:
689691
const navigationCommonService = strapi.plugin('navigation').service('common');
690692

691693
navigationCommonService.updateUpdateNavigationSchema((schema: ZodObject) => {
692-
return schema.refine((data) => {
693-
if (!data.visible) {
694-
return false;
695-
}
694+
return schema.refine(
695+
(data) => {
696+
if (!data.visible) {
697+
return false;
698+
}
696699

697-
return true;
698-
}, { message: "Hidden navigation updated." });
700+
return true;
701+
},
702+
{ message: 'Hidden navigation updated.' }
703+
);
699704
});
700705
```
701706

admin/src/api/validators.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export const configSchema = z.object({
133133
})
134134
.array(),
135135
contentTypes: z.array(z.string()),
136+
defaultContentType: z.string().optional(),
136137
contentTypesNameFields: z.record(z.string(), z.array(z.string())),
137138
contentTypesPopulate: z.record(z.string(), z.array(z.string())),
138139
gql: z.object({

admin/src/pages/HomePage/components/NavigationItemForm/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,12 @@ export const NavigationItemForm: React.FC<NavigationItemFormProps> = ({
551551
contentTypeItemsQuery.data,
552552
]);
553553

554+
useEffect(() => {
555+
if (currentType === 'INTERNAL' && currentRelatedType === '') {
556+
setFormValueItem("relatedType", configQuery.data?.defaultContentType);
557+
}
558+
}, [configQuery.data?.defaultContentType, currentType, currentRelatedType]);
559+
554560
return (
555561
<>
556562
<Modal.Body>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
11
export const customFieldsTypes = ['string', 'boolean', 'select', 'media'] as const;
2+
3+
export const BOX_DEFAULT_PROPS = {
4+
background: 'neutral0',
5+
hasRadius: true,
6+
shadow: 'filterShadow',
7+
padding: 6,
8+
};
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { useIntl } from 'react-intl';
2+
import { isObject } from 'lodash';
3+
4+
import { Box, Flex, Grid, NumberInput, Toggle, Typography } from '@strapi/design-system';
5+
import { Field } from '@sensinum/strapi-utils';
6+
7+
import { BOX_DEFAULT_PROPS } from '../../common';
8+
import { getTrad } from '../../../../translations';
9+
import { FormChangeEvent } from '../../../../types';
10+
import { useConfig } from '../../hooks';
11+
import { useSettingsContext } from '../../context';
12+
13+
export const AdditionalSettingsPanel = () => {
14+
const configQuery = useConfig();
15+
const { formatMessage } = useIntl();
16+
17+
const { values, onChange, handleChange, restartStatus } = useSettingsContext();
18+
19+
return (
20+
<Box {...BOX_DEFAULT_PROPS} width="100%">
21+
<Flex direction="column" alignItems="flex-start" gap={2}>
22+
<Typography variant="delta" as="h2">
23+
{formatMessage(getTrad('pages.settings.additional.title'))}
24+
</Typography>
25+
26+
<Grid.Root gap={4} width="100%">
27+
<Grid.Item col={4} s={12} xs={12}>
28+
<Box width="100%">
29+
<Field
30+
name="allowedLevels"
31+
label={formatMessage(getTrad('pages.settings.form.allowedLevels.label'))}
32+
hint={formatMessage(getTrad('pages.settings.form.allowedLevels.hint'))}
33+
>
34+
<NumberInput
35+
width="100%"
36+
name="allowedLevels"
37+
type="number"
38+
placeholder={formatMessage(
39+
getTrad('pages.settings.form.allowedLevels.placeholder')
40+
)}
41+
onChange={(eventOrPath: FormChangeEvent, value?: any) => {
42+
if (isObject(eventOrPath)) {
43+
const parsedVal = parseInt(eventOrPath.target.value);
44+
return handleChange(
45+
eventOrPath.target.name,
46+
isNaN(parsedVal) ? 0 : parsedVal,
47+
onChange
48+
);
49+
}
50+
return handleChange(eventOrPath, value, onChange);
51+
}}
52+
value={values.allowedLevels}
53+
disabled={restartStatus.required}
54+
/>
55+
</Field>
56+
</Box>
57+
</Grid.Item>
58+
<Grid.Item col={4} s={12} xs={12}>
59+
<Field
60+
name="cascadeMenuAttached"
61+
label={formatMessage(getTrad('pages.settings.form.cascadeMenuAttached.label'))}
62+
hint={formatMessage(getTrad('pages.settings.form.cascadeMenuAttached.hint'))}
63+
>
64+
<Toggle
65+
width="100%"
66+
name="cascadeMenuAttached"
67+
checked={values.cascadeMenuAttached}
68+
onChange={(eventOrPath: FormChangeEvent) =>
69+
handleChange(eventOrPath, !values.cascadeMenuAttached, onChange)
70+
}
71+
onLabel={formatMessage(getTrad('components.toggle.enabled'))}
72+
offLabel={formatMessage(getTrad('components.toggle.disabled'))}
73+
disabled={restartStatus.required}
74+
/>
75+
</Field>
76+
</Grid.Item>
77+
<Grid.Item col={4} s={12} xs={12}>
78+
<Field
79+
name="audienceFieldChecked"
80+
label={formatMessage(getTrad('pages.settings.form.audience.label'))}
81+
hint={formatMessage(getTrad('pages.settings.form.audience.hint'))}
82+
>
83+
<Toggle
84+
name="audienceFieldChecked"
85+
checked={values.audienceFieldChecked}
86+
onChange={(eventOrPath: FormChangeEvent) =>
87+
handleChange(eventOrPath, !values.audienceFieldChecked, onChange)
88+
}
89+
onLabel={formatMessage(getTrad('components.toggle.enabled'))}
90+
offLabel={formatMessage(getTrad('components.toggle.disabled'))}
91+
disabled={restartStatus.required}
92+
width="100%"
93+
/>
94+
</Field>
95+
</Grid.Item>
96+
{configQuery.data?.isCachePluginEnabled && (
97+
<Grid.Item col={12} s={12} xs={12}>
98+
<Field
99+
name="isCacheEnabled"
100+
label={formatMessage(getTrad('pages.settings.form.cache.label'))}
101+
hint={formatMessage(getTrad('pages.settings.form.cache.hint'))}
102+
>
103+
<Toggle
104+
name="isCacheEnabled"
105+
checked={values.isCacheEnabled}
106+
onChange={(eventOrPath: FormChangeEvent) =>
107+
handleChange(eventOrPath, !values.isCacheEnabled, onChange)
108+
}
109+
onLabel={formatMessage(getTrad('components.toggle.enabled'))}
110+
offLabel={formatMessage(getTrad('components.toggle.disabled'))}
111+
disabled={restartStatus.required}
112+
width="100%"
113+
/>
114+
</Field>
115+
</Grid.Item>
116+
)}
117+
</Grid.Root>
118+
</Flex>
119+
</Box>
120+
);
121+
};

admin/src/pages/SettingsPage/components/CustomFieldForm/index.tsx renamed to admin/src/pages/SettingsPage/components/CustomFieldsPanel/CustomFieldForm/index.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ import {
1212
} from '@strapi/design-system';
1313
import { Field } from '@sensinum/strapi-utils';
1414

15-
import TextArrayInput from '../../../../components/TextArrayInput';
16-
import { navigationItemCustomField, NavigationItemCustomField } from '../../../../schemas';
17-
import { getTrad } from '../../../../translations';
15+
import TextArrayInput from '../../../../../components/TextArrayInput';
16+
import { navigationItemCustomField, NavigationItemCustomField } from '../../../../../schemas';
17+
import { getTrad } from '../../../../../translations';
1818
import {
1919
Effect,
2020
FormChangeEvent,
2121
FormItemErrorSchema,
2222
ToBeFixed,
2323
VoidEffect,
24-
} from '../../../../types';
25-
import { customFieldsTypes } from '../../common';
24+
} from '../../../../../types';
25+
import { customFieldsTypes } from '../../../common';
2626
import { get, isNil, isObject, isString, set } from 'lodash';
2727

2828
const tradPrefix = 'pages.settings.form.customFields.popup.';

admin/src/pages/SettingsPage/components/CustomFieldModal/index.tsx renamed to admin/src/pages/SettingsPage/components/CustomFieldsPanel/CustomFieldModal/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { Modal, Typography } from '@strapi/design-system';
22
import React from 'react';
33

44
import { useIntl } from 'react-intl';
5-
import { NavigationItemCustomField } from '../../../../schemas';
6-
import { getTrad } from '../../../../translations';
7-
import { Effect, VoidEffect } from '../../../../types';
5+
import { NavigationItemCustomField } from '../../../../../schemas';
6+
import { getTrad } from '../../../../../translations';
7+
import { Effect, VoidEffect } from '../../../../../types';
88
import CustomFieldForm from '../CustomFieldForm';
99

1010
interface ICustomFieldModalProps {

admin/src/pages/SettingsPage/components/CustomFieldTable/index.tsx renamed to admin/src/pages/SettingsPage/components/CustomFieldsPanel/CustomFieldTable/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import {
1919
Typography,
2020
} from '@strapi/design-system';
2121
import { useIntl } from 'react-intl';
22-
import { ConfirmationDialog } from '../../../../components/ConfirmationDialog';
23-
import { NavigationItemCustomField } from '../../../../schemas';
24-
import { getTrad, getTradId } from '../../../../translations';
25-
import { Effect } from '../../../../types';
22+
import { ConfirmationDialog } from '../../../../../components/ConfirmationDialog';
23+
import { NavigationItemCustomField } from '../../../../../schemas';
24+
import { getTrad, getTradId } from '../../../../../translations';
25+
import { Effect } from '../../../../../types';
2626

2727
interface ICustomFieldTableProps {
2828
data: (NavigationItemCustomField | string)[];
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { useIntl } from 'react-intl';
2+
import { useState } from 'react';
3+
4+
import { Box, Typography } from '@strapi/design-system';
5+
6+
import { BOX_DEFAULT_PROPS } from '../../common';
7+
import { getTrad } from '../../../../translations';
8+
import CustomFieldTable from './CustomFieldTable';
9+
import CustomFieldModal from './CustomFieldModal';
10+
import { NavigationItemCustomField } from '../../../../schemas';
11+
import { useSettingsContext } from '../../context';
12+
13+
export const CustomFieldsPanel = () => {
14+
const { formatMessage } = useIntl();
15+
16+
const {
17+
values: { additionalFields },
18+
setFormValueItem,
19+
} = useSettingsContext();
20+
21+
const [isCustomFieldModalOpen, setIsCustomFieldModalOpen] = useState<boolean>(false);
22+
const [customFieldSelected, setCustomFieldSelected] = useState<NavigationItemCustomField | null>(
23+
null
24+
);
25+
26+
const handleOpenCustomFieldModal = (field: NavigationItemCustomField | null) => {
27+
setCustomFieldSelected(field);
28+
setIsCustomFieldModalOpen(true);
29+
};
30+
31+
const handleRemoveCustomField = (field: NavigationItemCustomField) => {
32+
const filteredFields = additionalFields.filter((f) =>
33+
typeof f !== 'string' ? f.name !== field.name : true
34+
);
35+
36+
setFormValueItem('additionalFields', filteredFields);
37+
38+
setCustomFieldSelected(null);
39+
setIsCustomFieldModalOpen(false);
40+
};
41+
42+
const handleToggleCustomField = (current: NavigationItemCustomField) => {
43+
const next = { ...current, enabled: !current.enabled };
44+
45+
const nextAdditionalFields = additionalFields.map((field) =>
46+
typeof field !== 'string' && current.name === field.name ? next : field
47+
);
48+
49+
setFormValueItem('additionalFields', nextAdditionalFields);
50+
};
51+
52+
const handleSubmitCustomField = (next: NavigationItemCustomField) => {
53+
const hasFieldAlready = !!additionalFields.find((field) =>
54+
typeof field !== 'string' ? field.name === next.name : false
55+
);
56+
const nextAdditionalFields = hasFieldAlready
57+
? additionalFields.map((field) =>
58+
typeof field !== 'string' && next.name === field.name ? next : field
59+
)
60+
: [...additionalFields, next];
61+
62+
setFormValueItem('additionalFields', nextAdditionalFields);
63+
64+
setCustomFieldSelected(null);
65+
setIsCustomFieldModalOpen(false);
66+
};
67+
68+
return (
69+
<Box {...BOX_DEFAULT_PROPS} width="100%">
70+
<Typography variant="delta" as="h2">
71+
{formatMessage(getTrad('pages.settings.customFields.title'))}
72+
</Typography>
73+
<Box padding={1} />
74+
<CustomFieldTable
75+
data={additionalFields}
76+
onOpenModal={handleOpenCustomFieldModal}
77+
onRemoveCustomField={handleRemoveCustomField}
78+
onToggleCustomField={handleToggleCustomField}
79+
/>
80+
{isCustomFieldModalOpen && (
81+
<CustomFieldModal
82+
onClose={() => setIsCustomFieldModalOpen(false)}
83+
onSubmit={handleSubmitCustomField}
84+
isOpen={isCustomFieldModalOpen}
85+
data={customFieldSelected}
86+
/>
87+
)}
88+
</Box>
89+
);
90+
};

0 commit comments

Comments
 (0)