strapi-plugin-navigation 2.2.4 β 2.2.6
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/README.md +36 -7
- package/admin/src/components/AdditionalFieldInput/index.js +2 -2
- package/admin/src/components/AdditionalFieldInput/types.d.ts +1 -0
- package/admin/src/components/Item/ItemCardHeader/icons.d.ts +1 -1
- package/admin/src/components/Item/ItemCardHeader/icons.js +2 -1
- package/admin/src/components/Item/ItemCardHeader/index.d.ts +1 -0
- package/admin/src/components/Item/ItemCardHeader/index.js +6 -6
- package/admin/src/components/Item/index.js +7 -6
- package/admin/src/components/NavigationItemList/index.d.ts +2 -1
- package/admin/src/components/NavigationItemList/index.js +2 -2
- package/admin/src/hooks/useNavigationManager.d.ts +0 -1
- package/admin/src/index.js +1 -1
- package/admin/src/pages/DataManagerProvider/index.js +14 -1
- package/admin/src/pages/DataManagerProvider/reducer.d.ts +1 -1
- package/admin/src/pages/NoAccessPage/index.d.ts +3 -0
- package/admin/src/pages/NoAccessPage/index.js +31 -0
- package/admin/src/pages/SettingsPage/index.d.ts +0 -1
- package/admin/src/pages/SettingsPage/index.js +9 -0
- package/admin/src/pages/View/components/NavigationHeader/index.d.ts +2 -1
- package/admin/src/pages/View/components/NavigationHeader/index.js +9 -8
- package/admin/src/pages/View/components/NavigationItemForm/index.js +14 -13
- package/admin/src/pages/View/components/NavigationItemForm/types.d.ts +2 -1
- package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupFooter.d.ts +4 -2
- package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupFooter.js +5 -1
- package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.d.ts +2 -1
- package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +6 -2
- package/admin/src/pages/View/components/NavigationItemPopup/index.d.ts +2 -1
- package/admin/src/pages/View/components/NavigationItemPopup/index.js +4 -3
- package/admin/src/pages/View/components/NavigationManager/AllNavigations/icons.d.ts +0 -1
- package/admin/src/pages/View/components/NavigationManager/AllNavigations/index.d.ts +0 -1
- package/admin/src/pages/View/components/NavigationManager/DeletionConfirm/index.d.ts +0 -1
- package/admin/src/pages/View/components/NavigationManager/ErrorDetails/index.d.ts +0 -1
- package/admin/src/pages/View/components/NavigationManager/Form/index.d.ts +0 -1
- package/admin/src/pages/View/components/NavigationManager/NavigationUpdate/index.d.ts +0 -1
- package/admin/src/pages/View/components/NavigationManager/NewNavigation/index.d.ts +0 -1
- package/admin/src/pages/View/components/NavigationManager/index.d.ts +0 -1
- package/admin/src/pages/View/index.js +26 -19
- package/admin/src/permissions.d.ts +4 -0
- package/admin/src/permissions.js +1 -0
- package/admin/src/translations/en.json +8 -0
- package/package.json +2 -2
- package/permissions.d.ts +1 -0
- package/permissions.js +1 -0
- package/server/bootstrap/index.js +6 -0
- package/server/routes/admin.js +99 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/types/utils.d.ts +5 -0
package/README.md
CHANGED
|
@@ -45,12 +45,14 @@ Strapi Navigation Plugin provides a website navigation / menu builder feature fo
|
|
|
45
45
|
- [Plugin file](#in-v202-and-older--default-configuration-state-for-v203-and-newer)
|
|
46
46
|
5. [π§ GraphQL Configuration](#-gql-configuration)
|
|
47
47
|
6. [π i18n Internationalization](#-i18n-internationalization)
|
|
48
|
-
7. [
|
|
48
|
+
7. [π€ RBAC](#-rbac)
|
|
49
|
+
8. [π Authorization strategy](#-authorization-strategy)
|
|
50
|
+
9. [πΈοΈ Public API specification](#%EF%B8%8F-public-api-specification)
|
|
49
51
|
- [REST API](#rest-api)
|
|
50
52
|
- [GraphQL API](#graphql-api)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
10. [π¬ FAQ](#-faq)
|
|
54
|
+
11. [π€ Contributing](#-contributing)
|
|
55
|
+
12. [π¨βπ» Community support](#-community-support)
|
|
54
56
|
|
|
55
57
|
## β¨ Features
|
|
56
58
|
|
|
@@ -118,7 +120,7 @@ Complete installation requirements are exact same as for Strapi itself and can b
|
|
|
118
120
|
|
|
119
121
|
**Supported Strapi versions**:
|
|
120
122
|
|
|
121
|
-
- Strapi v4.
|
|
123
|
+
- Strapi v4.9.0 (recently tested)
|
|
122
124
|
- Strapi v4.x
|
|
123
125
|
|
|
124
126
|
> This plugin is designed for **Strapi v4** and is not working with v3.x. To get version for **Strapi v3** install version [v1.x](https://github.com/VirtusLab-Open-Source/strapi-plugin-navigation/tree/strapi-v3).
|
|
@@ -163,6 +165,9 @@ Config for this plugin is stored as a part of the `config/plugins.js` or `config
|
|
|
163
165
|
contentTypesNameFields: {
|
|
164
166
|
'api::page.page': ['title']
|
|
165
167
|
},
|
|
168
|
+
pathDefaultFields: {
|
|
169
|
+
'api::page.page': ['slug']
|
|
170
|
+
},
|
|
166
171
|
allowedLevels: 2,
|
|
167
172
|
gql: {...},
|
|
168
173
|
}
|
|
@@ -175,6 +180,7 @@ Config for this plugin is stored as a part of the `config/plugins.js` or `config
|
|
|
175
180
|
- `allowedLevels` - Maximum level for which you're able to mark item as "Menu attached"
|
|
176
181
|
- `contentTypes` - UIDs of related content types
|
|
177
182
|
- `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`
|
|
183
|
+
- `pathDefaultFields` - The attribute to copy the default path from per content type. Syntax: `'api::<collection name>.<content type name>': ['url_slug', 'path']`
|
|
178
184
|
- `gql` - If you're using GraphQL that's the right place to put all necessary settings. More **[ here ](#gql-configuration)**
|
|
179
185
|
- `i18nEnabled` - should you want to manage multi-locale content via navigation set this value `Enabled`. More **[ here ](#i18n-internationalization)**
|
|
180
186
|
- `cascadeMenuAttached` - If you don't want "Menu attached" to cascade on child items set this value `Disabled`.
|
|
@@ -249,12 +255,35 @@ Of course if you know that `fr` version is present at id `2` you can just query
|
|
|
249
255
|
If feature is enabled GQL render navigation query is expanded to handle `locale` param(it will work the same as regular requests). Checkout schema provided by GraphQL plugin.
|
|
250
256
|
|
|
251
257
|
## π€ RBAC
|
|
252
|
-
Plugin provides granular permissions based on Strapi RBAC functionality.
|
|
258
|
+
Plugin provides granular permissions based on **Strapi RBAC** functionality within the editorial interface & **Admin API**. Those settings are editable via the _Setings_ -> _Administration Panel_ -> _Roles_.
|
|
253
259
|
|
|
254
|
-
### Mandatory permissions
|
|
255
260
|
For any role different than **Super Admin**, to access the **Navigation panel** you must set following permissions:
|
|
261
|
+
|
|
262
|
+
### Mandatory permissions
|
|
256
263
|
- _Plugins_ -> _Navigation_ -> _Read_ - gives you the access to **Navigation Panel**
|
|
257
264
|
|
|
265
|
+
### Other permissions
|
|
266
|
+
- _Plugins_ -> _Navigation_ -> _Update_ - with this permission user is able to change Navigation structure
|
|
267
|
+
- _Plugins_ -> _Navigation_ -> _Settings_ - special permission for users that should be able to change plugin settings
|
|
268
|
+
|
|
269
|
+
## π Authorization strategy
|
|
270
|
+
Is applied for **Public API** both for REST and GraphQL. You can manage is by two different ways. Those settings are editable via the _Setings_ -> _Users & Permissions Plugin_ -> _Roles_.
|
|
271
|
+
|
|
272
|
+
## User based
|
|
273
|
+
- _Public_ - as per description it's default role for any not authenticated user. By enabling **Public API** of the plugin here you're making it **fully public**, without **any permissions check**.
|
|
274
|
+
- _Authenticated_ - as per description this is default role for Strapi Users. If you enable **Public API** here, for any call made you must use the User authentication token as `Bearer <token>`.
|
|
275
|
+
|
|
276
|
+
## Token based
|
|
277
|
+
- _Full Access_ - gives full access to every Strapi Content API including our plugin endpoints as well.
|
|
278
|
+
- _Custom_ - granural access management to every Strapi Content API endpoints as well as plugin **Public API** - _(recomended approach)_
|
|
279
|
+
|
|
280
|
+
> _Note: Token usage & Read-Only tokens_
|
|
281
|
+
> If you're aiming to use token based approach, for every call you must provide proper token in headers as `Bearer <token>`.
|
|
282
|
+
>
|
|
283
|
+
> Important: As the Read-Only tokens are dedicated to support just `find` and `findAll` endpoints from Strapi Content API, they are not covering access to plugin **Public API** `render` and `renderChild` endpoints. We recommend to use the `Custom` token type for fully granural and secured approach instead of `Full Access` ones.
|
|
284
|
+
>
|
|
285
|
+
> Reference: [Strapi - API Tokens](https://docs.strapi.io/dev-docs/configurations/api-tokens#usage)
|
|
286
|
+
|
|
258
287
|
## Base Navigation Item model
|
|
259
288
|
|
|
260
289
|
### Flat
|
|
@@ -36,14 +36,14 @@ const DEFAULT_STRING_VALUE = "";
|
|
|
36
36
|
const handlerFactory = ({ field, prop, onChange }) => ({ target }) => {
|
|
37
37
|
onChange(field.name, target[prop]);
|
|
38
38
|
};
|
|
39
|
-
const AdditionalFieldInput = ({ field, isLoading, onChange, value, error }) => {
|
|
39
|
+
const AdditionalFieldInput = ({ field, isLoading, onChange, value, disabled, error }) => {
|
|
40
40
|
const toggleNotification = (0, helper_plugin_1.useNotification)();
|
|
41
41
|
const { formatMessage } = (0, react_intl_1.useIntl)();
|
|
42
42
|
const defaultInputProps = (0, react_1.useMemo)(() => ({
|
|
43
43
|
id: field.name,
|
|
44
44
|
name: field.name,
|
|
45
45
|
label: field.label,
|
|
46
|
-
disabled: isLoading,
|
|
46
|
+
disabled: isLoading || disabled,
|
|
47
47
|
error: error && formatMessage(error),
|
|
48
48
|
}), [field, isLoading, error]);
|
|
49
49
|
const handleBoolean = (0, react_1.useMemo)(() => handlerFactory({ field, onChange, prop: "checked" }), [onChange, field]);
|
|
@@ -5,6 +5,7 @@ export declare type AdditionalFieldInputProps = {
|
|
|
5
5
|
isLoading: boolean;
|
|
6
6
|
onChange: (name: string, value: string) => void;
|
|
7
7
|
value: string | boolean | string[] | null;
|
|
8
|
+
disabled: boolean;
|
|
8
9
|
error: MessageDescriptor | null;
|
|
9
10
|
};
|
|
10
11
|
export declare type TargetProp = "value" | "checked";
|
|
@@ -3,10 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.trashIcon = exports.refreshIcon = exports.pencilIcon = void 0;
|
|
6
|
+
exports.eyeIcon = exports.trashIcon = exports.refreshIcon = exports.pencilIcon = void 0;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
8
|
const icons_1 = require("@strapi/icons");
|
|
9
9
|
exports.pencilIcon = react_1.default.createElement(icons_1.Pencil, null);
|
|
10
10
|
exports.refreshIcon = react_1.default.createElement(icons_1.Refresh, null);
|
|
11
11
|
exports.trashIcon = react_1.default.createElement(icons_1.Trash, null);
|
|
12
|
+
exports.eyeIcon = react_1.default.createElement(icons_1.Eye, null);
|
|
12
13
|
//# sourceMappingURL=icons.js.map
|
|
@@ -15,9 +15,9 @@ const utils_1 = require("../../../utils");
|
|
|
15
15
|
const icons_1 = require("./icons");
|
|
16
16
|
const wrapperStyle = { zIndex: 2 };
|
|
17
17
|
const pathWrapperStyle = { maxWidth: "425px" };
|
|
18
|
-
const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit, onItemRestore, dragRef }) => (react_1.default.createElement(Wrapper_1.default, null,
|
|
18
|
+
const ItemCardHeader = ({ title, path, icon, removed, canUpdate, onItemRemove, onItemEdit, onItemRestore, dragRef }) => (react_1.default.createElement(Wrapper_1.default, null,
|
|
19
19
|
react_1.default.createElement(Flex_1.Flex, { alignItems: "center" },
|
|
20
|
-
react_1.default.createElement(DragButton_1.default, { ref: dragRef }),
|
|
20
|
+
canUpdate && (react_1.default.createElement(DragButton_1.default, { ref: dragRef })),
|
|
21
21
|
react_1.default.createElement(Typography_1.Typography, { variant: "omega", fontWeight: "bold" }, title),
|
|
22
22
|
react_1.default.createElement(Typography_1.Typography, { variant: "omega", fontWeight: "bold", textColor: 'neutral500', ellipsis: true, style: pathWrapperStyle }, path),
|
|
23
23
|
react_1.default.createElement(Flex_1.Flex, null,
|
|
@@ -25,9 +25,9 @@ const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit,
|
|
|
25
25
|
react_1.default.createElement(Flex_1.Flex, { alignItems: "center", style: wrapperStyle },
|
|
26
26
|
removed &&
|
|
27
27
|
(react_1.default.createElement(ItemCardBadge_1.default, { borderColor: "danger200", backgroundColor: "danger100", textColor: "danger600" }, (0, utils_1.getMessage)("components.navigationItem.badge.removed"))),
|
|
28
|
-
react_1.default.createElement(IconButton_1.IconButton, { disabled: removed, onClick: onItemEdit, label:
|
|
29
|
-
removed ?
|
|
30
|
-
react_1.default.createElement(IconButton_1.IconButton, { onClick: onItemRestore, label: "Restore", icon: icons_1.refreshIcon }) :
|
|
31
|
-
react_1.default.createElement(IconButton_1.IconButton, { onClick: onItemRemove, label: "Remove", icon: icons_1.trashIcon }))));
|
|
28
|
+
react_1.default.createElement(IconButton_1.IconButton, { disabled: removed, onClick: onItemEdit, label: (0, utils_1.getMessage)(`components.navigationItem.action.${canUpdate ? 'edit' : 'view'}`, canUpdate ? 'Edit' : 'View'), icon: canUpdate ? icons_1.pencilIcon : icons_1.eyeIcon }),
|
|
29
|
+
canUpdate && (react_1.default.createElement(react_1.default.Fragment, null, removed ?
|
|
30
|
+
react_1.default.createElement(IconButton_1.IconButton, { onClick: onItemRestore, label: (0, utils_1.getMessage)('components.navigationItem.action.restore', "Restore"), icon: icons_1.refreshIcon }) :
|
|
31
|
+
react_1.default.createElement(IconButton_1.IconButton, { onClick: onItemRemove, label: (0, utils_1.getMessage)('components.navigationItem.action.remove', "Remove"), icon: icons_1.trashIcon }))))));
|
|
32
32
|
exports.default = ItemCardHeader;
|
|
33
33
|
//# sourceMappingURL=index.js.map
|
|
@@ -46,7 +46,7 @@ const ItemCardRemovedOverlay_1 = require("./ItemCardRemovedOverlay");
|
|
|
46
46
|
const utils_1 = require("../../utils");
|
|
47
47
|
const CollapseButton_1 = __importDefault(require("../CollapseButton"));
|
|
48
48
|
const Item = (props) => {
|
|
49
|
-
const { item, isLast = false, level = 0, levelPath = '', allowedLevels, relatedRef, isParentAttachedToMenu, onItemLevelAdd, onItemRemove, onItemRestore, onItemEdit, onItemReOrder, onItemToggleCollapse, error, displayChildren, config = {}, } = props;
|
|
49
|
+
const { item, isLast = false, level = 0, levelPath = '', allowedLevels, relatedRef, isParentAttachedToMenu, onItemLevelAdd, onItemRemove, onItemRestore, onItemEdit, onItemReOrder, onItemToggleCollapse, error, displayChildren, config = {}, permissions = {}, } = props;
|
|
50
50
|
const { viewId, title, type, path, removed, externalPath, menuAttached, collapsed, structureId, items = [], } = item;
|
|
51
51
|
const { contentTypes = [], contentTypesNameFields } = config;
|
|
52
52
|
const isExternal = type === utils_1.navigationItemType.EXTERNAL;
|
|
@@ -60,6 +60,7 @@ const Item = (props) => {
|
|
|
60
60
|
const relatedItemLabel = !isExternal ? (0, parsers_1.extractRelatedItemLabel)(relatedRef, contentTypesNameFields, { contentTypes }) : '';
|
|
61
61
|
const relatedTypeLabel = relatedRef?.labelSingular;
|
|
62
62
|
const relatedBadgeColor = isPublished ? 'success' : 'secondary';
|
|
63
|
+
const { canUpdate } = permissions;
|
|
63
64
|
const dragRef = (0, react_1.useRef)(null);
|
|
64
65
|
const dropRef = (0, react_1.useRef)(null);
|
|
65
66
|
const previewRef = (0, react_1.useRef)(null);
|
|
@@ -110,7 +111,7 @@ const Item = (props) => {
|
|
|
110
111
|
const { isSingle } = contentType;
|
|
111
112
|
return `/content-manager/${isSingle ? 'singleType' : 'collectionType'}/${entity?.__collectionUid}${!isSingle ? '/' + entity?.id : ''}`;
|
|
112
113
|
};
|
|
113
|
-
const onNewItemClick = (0, react_1.useCallback)((event) => onItemLevelAdd(event, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, `${structureId}.${items.length}`), [viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, structureId, items]);
|
|
114
|
+
const onNewItemClick = (0, react_1.useCallback)((event) => canUpdate && onItemLevelAdd(event, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, `${structureId}.${items.length}`), [viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, structureId, items, canUpdate]);
|
|
114
115
|
return (react_1.default.createElement(Wrapper_1.default, { level: level, isLast: isLast, style: { opacity: isDragging ? 0.2 : 1 }, ref: refs ? refs.dropRef : null },
|
|
115
116
|
react_1.default.createElement(Card_1.Card, { style: { width: "728px", zIndex: 1, position: "relative", overflow: 'hidden' } },
|
|
116
117
|
removed && (react_1.default.createElement(ItemCardRemovedOverlay_1.ItemCardRemovedOverlay, null)),
|
|
@@ -120,14 +121,14 @@ const Item = (props) => {
|
|
|
120
121
|
...item,
|
|
121
122
|
isMenuAllowedLevel,
|
|
122
123
|
isParentAttachedToMenu,
|
|
123
|
-
}, levelPath, isParentAttachedToMenu), onItemRestore: () => onItemRestore(item), dragRef: refs.dragRef, removed: removed })),
|
|
124
|
+
}, levelPath, isParentAttachedToMenu), onItemRestore: () => onItemRestore(item), dragRef: refs.dragRef, removed: removed, canUpdate: canUpdate })),
|
|
124
125
|
react_1.default.createElement(Divider_1.Divider, null),
|
|
125
126
|
!isExternal && (react_1.default.createElement(Card_1.CardBody, { style: { padding: '8px' } },
|
|
126
127
|
react_1.default.createElement(Flex_1.Flex, { style: { width: '100%' }, direction: "row", alignItems: "center", justifyContent: "space-between" },
|
|
127
128
|
react_1.default.createElement(Flex_1.Flex, null,
|
|
128
129
|
!(0, lodash_1.isEmpty)(item.items) && react_1.default.createElement(CollapseButton_1.default, { toggle: () => onItemToggleCollapse(item), collapsed: collapsed, itemsCount: item.items.length }),
|
|
129
|
-
react_1.default.createElement(TextButton_1.TextButton, { disabled: removed, startIcon: react_1.default.createElement(icons_1.Plus, null), onClick: onNewItemClick },
|
|
130
|
-
react_1.default.createElement(Typography_1.Typography, { variant: "pi", fontWeight: "bold", textColor: removed ? "neutral600" : "primary600" }, (0, utils_1.getMessage)("components.navigationItem.action.newItem")))),
|
|
130
|
+
canUpdate && (react_1.default.createElement(TextButton_1.TextButton, { disabled: removed, startIcon: react_1.default.createElement(icons_1.Plus, null), onClick: onNewItemClick },
|
|
131
|
+
react_1.default.createElement(Typography_1.Typography, { variant: "pi", fontWeight: "bold", textColor: removed ? "neutral600" : "primary600" }, (0, utils_1.getMessage)("components.navigationItem.action.newItem"))))),
|
|
131
132
|
relatedItemLabel && (react_1.default.createElement(Flex_1.Flex, { justifyContent: 'center', alignItems: 'center' },
|
|
132
133
|
isHandledByPublishFlow && (react_1.default.createElement(ItemCardBadge_1.default, { borderColor: `${relatedBadgeColor}200`, backgroundColor: `${relatedBadgeColor}100`, textColor: `${relatedBadgeColor}600`, className: "action", small: true }, (0, utils_1.getMessage)({ id: `components.navigationItem.badge.${isPublished ? 'published' : 'draft'}` }))),
|
|
133
134
|
react_1.default.createElement(Typography_1.Typography, { variant: "omega", textColor: 'neutral600' },
|
|
@@ -135,7 +136,7 @@ const Item = (props) => {
|
|
|
135
136
|
"\u00A0/\u00A0"),
|
|
136
137
|
react_1.default.createElement(Typography_1.Typography, { variant: "omega", textColor: 'neutral800' }, relatedItemLabel),
|
|
137
138
|
react_1.default.createElement(Link_1.Link, { to: `/content-manager/collectionType/${relatedRef?.__collectionUid}/${relatedRef?.id}`, endIcon: react_1.default.createElement(icons_1.ArrowRight, null) }, "\u00A0")))))))),
|
|
138
|
-
hasChildren && !removed && !collapsed && react_1.default.createElement(NavigationItemList_1.default, { onItemLevelAdd: onItemLevelAdd, onItemRemove: onItemRemove, onItemEdit: onItemEdit, onItemRestore: onItemRestore, onItemReOrder: onItemReOrder, onItemToggleCollapse: onItemToggleCollapse, error: error, allowedLevels: allowedLevels, isParentAttachedToMenu: menuAttached, items: item.items, level: level + 1, levelPath: absolutePath, contentTypes: contentTypes, contentTypesNameFields: contentTypesNameFields })));
|
|
139
|
+
hasChildren && !removed && !collapsed && react_1.default.createElement(NavigationItemList_1.default, { onItemLevelAdd: onItemLevelAdd, onItemRemove: onItemRemove, onItemEdit: onItemEdit, onItemRestore: onItemRestore, onItemReOrder: onItemReOrder, onItemToggleCollapse: onItemToggleCollapse, error: error, allowedLevels: allowedLevels, isParentAttachedToMenu: menuAttached, items: item.items, level: level + 1, levelPath: absolutePath, contentTypes: contentTypes, contentTypesNameFields: contentTypesNameFields, permissions: permissions })));
|
|
139
140
|
};
|
|
140
141
|
Item.propTypes = {
|
|
141
142
|
item: prop_types_1.default.shape({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default List;
|
|
2
|
-
declare function List({ allowedLevels, error, isParentAttachedToMenu, items, level, levelPath, onItemEdit, onItemLevelAdd, onItemRemove, onItemRestore, onItemReOrder, onItemToggleCollapse, displayFlat, contentTypes, contentTypesNameFields, }: {
|
|
2
|
+
declare function List({ allowedLevels, error, isParentAttachedToMenu, items, level, levelPath, onItemEdit, onItemLevelAdd, onItemRemove, onItemRestore, onItemReOrder, onItemToggleCollapse, displayFlat, contentTypes, contentTypesNameFields, permissions, }: {
|
|
3
3
|
allowedLevels: any;
|
|
4
4
|
error: any;
|
|
5
5
|
isParentAttachedToMenu?: boolean | undefined;
|
|
@@ -15,6 +15,7 @@ declare function List({ allowedLevels, error, isParentAttachedToMenu, items, lev
|
|
|
15
15
|
displayFlat: any;
|
|
16
16
|
contentTypes: any;
|
|
17
17
|
contentTypesNameFields: any;
|
|
18
|
+
permissions: any;
|
|
18
19
|
}): JSX.Element;
|
|
19
20
|
declare namespace List {
|
|
20
21
|
namespace propTypes {
|
|
@@ -7,12 +7,12 @@ const react_1 = __importDefault(require("react"));
|
|
|
7
7
|
const prop_types_1 = __importDefault(require("prop-types"));
|
|
8
8
|
const Item_1 = __importDefault(require("../Item"));
|
|
9
9
|
const Wrapper_1 = __importDefault(require("./Wrapper"));
|
|
10
|
-
const List = ({ allowedLevels, error, isParentAttachedToMenu = false, items, level = 0, levelPath = '', onItemEdit, onItemLevelAdd, onItemRemove, onItemRestore, onItemReOrder, onItemToggleCollapse, displayFlat, contentTypes, contentTypesNameFields, }) => (react_1.default.createElement(Wrapper_1.default, { level: level }, items.map((item, n) => {
|
|
10
|
+
const List = ({ allowedLevels, error, isParentAttachedToMenu = false, items, level = 0, levelPath = '', onItemEdit, onItemLevelAdd, onItemRemove, onItemRestore, onItemReOrder, onItemToggleCollapse, displayFlat, contentTypes, contentTypesNameFields, permissions, }) => (react_1.default.createElement(Wrapper_1.default, { level: level }, items.map((item, n) => {
|
|
11
11
|
const { relatedRef, ...itemProps } = item;
|
|
12
12
|
return (react_1.default.createElement(Item_1.default, { key: `list-item-${item.viewId || n}`, item: itemProps, isLast: n === items.length - 1, relatedRef: relatedRef, level: level, levelPath: levelPath, isParentAttachedToMenu: isParentAttachedToMenu, allowedLevels: allowedLevels, onItemRestore: onItemRestore, onItemLevelAdd: onItemLevelAdd, onItemRemove: onItemRemove, onItemEdit: onItemEdit, onItemReOrder: onItemReOrder, onItemToggleCollapse: onItemToggleCollapse, error: error, displayChildren: displayFlat, config: {
|
|
13
13
|
contentTypes,
|
|
14
14
|
contentTypesNameFields
|
|
15
|
-
} }));
|
|
15
|
+
}, permissions: permissions }));
|
|
16
16
|
})));
|
|
17
17
|
List.propTypes = {
|
|
18
18
|
allowedLevels: prop_types_1.default.number,
|
package/admin/src/index.js
CHANGED
|
@@ -50,7 +50,7 @@ exports.default = {
|
|
|
50
50
|
const component = await Promise.resolve().then(() => __importStar(require('./pages/SettingsPage')));
|
|
51
51
|
return component;
|
|
52
52
|
},
|
|
53
|
-
permissions: permissions_1.default.
|
|
53
|
+
permissions: permissions_1.default.settings,
|
|
54
54
|
}
|
|
55
55
|
]);
|
|
56
56
|
app.addMenuLink({
|
|
@@ -40,6 +40,8 @@ const reducer_1 = __importStar(require("./reducer"));
|
|
|
40
40
|
const actions_1 = require("./actions");
|
|
41
41
|
const parsers_1 = require("../View/utils/parsers");
|
|
42
42
|
const utils_1 = require("../../utils");
|
|
43
|
+
const NoAccessPage_1 = __importDefault(require("../NoAccessPage"));
|
|
44
|
+
const permissions_1 = __importDefault(require("../../permissions"));
|
|
43
45
|
const i18nAwareItems = ({ items, config }) => config.i18nEnabled ? items.filter(({ localeCode }) => localeCode === config.defaultLocale) : items;
|
|
44
46
|
const DataManagerProvider = ({ children }) => {
|
|
45
47
|
const [reducerState, dispatch] = (0, react_1.useReducer)(reducer_1.default, reducer_1.initialState, init_1.default);
|
|
@@ -50,6 +52,11 @@ const DataManagerProvider = ({ children }) => {
|
|
|
50
52
|
const { pathname } = (0, react_router_dom_1.useLocation)();
|
|
51
53
|
const formatMessageRef = (0, react_1.useRef)();
|
|
52
54
|
formatMessageRef.current = formatMessage;
|
|
55
|
+
const viewPermissions = (0, react_1.useMemo)(() => ({
|
|
56
|
+
access: permissions_1.default.access || permissions_1.default.update,
|
|
57
|
+
update: permissions_1.default.update,
|
|
58
|
+
}), []);
|
|
59
|
+
const { isLoading: isLoadingForPermissions, allowedActions: { canAccess, canUpdate, }, } = (0, helper_plugin_1.useRBAC)(viewPermissions);
|
|
53
60
|
const getLayoutSettingRef = (0, react_1.useRef)();
|
|
54
61
|
getLayoutSettingRef.current = (settingName) => (0, lodash_1.get)({}, ["settings", settingName], "");
|
|
55
62
|
const isInDevelopmentMode = autoReload;
|
|
@@ -289,6 +296,9 @@ const DataManagerProvider = ({ children }) => {
|
|
|
289
296
|
});
|
|
290
297
|
const slugify = (query) => (0, helper_plugin_1.request)(`/${pluginId_1.default}/slug?q=${query}`, { method: "GET", signal });
|
|
291
298
|
const hardReset = () => getDataRef.current();
|
|
299
|
+
if (!canAccess && !isLoadingForPermissions) {
|
|
300
|
+
return (react_1.default.createElement(NoAccessPage_1.default, null));
|
|
301
|
+
}
|
|
292
302
|
return (react_1.default.createElement(DataManagerContext_1.default.Provider, { value: {
|
|
293
303
|
items: passedActiveItems,
|
|
294
304
|
activeItem,
|
|
@@ -318,7 +328,10 @@ const DataManagerProvider = ({ children }) => {
|
|
|
318
328
|
handleNavigationsDeletion,
|
|
319
329
|
hardReset,
|
|
320
330
|
slugify,
|
|
321
|
-
|
|
331
|
+
permissions: {
|
|
332
|
+
canAccess, canUpdate
|
|
333
|
+
},
|
|
334
|
+
} }, isLoading || isLoadingForPermissions ? react_1.default.createElement(helper_plugin_1.LoadingIndicatorPage, null) : children));
|
|
322
335
|
};
|
|
323
336
|
DataManagerProvider.propTypes = {
|
|
324
337
|
children: prop_types_1.default.node.isRequired,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default reducer;
|
|
2
|
-
declare function reducer(state: any, action: any): any
|
|
2
|
+
declare function reducer(state: any, action: any): (base?: ((draftState: any) => any) | undefined, ...args: unknown[]) => ((draftState: any) => any) | Promise<(draftState: any) => any>;
|
|
3
3
|
export namespace initialState {
|
|
4
4
|
const items: never[];
|
|
5
5
|
const activeItem: undefined;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
const helper_plugin_1 = require("@strapi/helper-plugin");
|
|
8
|
+
const Main_1 = require("@strapi/design-system/Main");
|
|
9
|
+
const Layout_1 = require("@strapi/design-system/Layout");
|
|
10
|
+
const EmptyStateLayout_1 = require("@strapi/design-system/EmptyStateLayout");
|
|
11
|
+
const icons_1 = require("@strapi/icons");
|
|
12
|
+
const react_intl_1 = require("react-intl");
|
|
13
|
+
const NoAcccessPage = () => {
|
|
14
|
+
const { formatMessage } = (0, react_intl_1.useIntl)();
|
|
15
|
+
(0, helper_plugin_1.useFocusWhenNavigate)();
|
|
16
|
+
return (react_1.default.createElement(Main_1.Main, { labelledBy: "title" },
|
|
17
|
+
react_1.default.createElement(Layout_1.HeaderLayout, { id: "title", title: formatMessage({
|
|
18
|
+
id: 'page.auth.noAccess',
|
|
19
|
+
defaultMessage: 'No access',
|
|
20
|
+
}) }),
|
|
21
|
+
react_1.default.createElement(Layout_1.ContentLayout, null,
|
|
22
|
+
react_1.default.createElement(EmptyStateLayout_1.EmptyStateLayout, { action: react_1.default.createElement(helper_plugin_1.LinkButton, { variant: "secondary", endIcon: react_1.default.createElement(icons_1.ArrowRight, null), to: "/" }, formatMessage({
|
|
23
|
+
id: 'components.notAccessPage.back',
|
|
24
|
+
defaultMessage: 'Back to homepage',
|
|
25
|
+
})), content: formatMessage({
|
|
26
|
+
id: 'page.auth.not.allowed',
|
|
27
|
+
defaultMessage: "Oops! It seems like You do not have access to this page...",
|
|
28
|
+
}), hasRadius: true, icon: react_1.default.createElement(icons_1.EmptyPictures, { width: "10rem" }), shadow: "tableShadow" }))));
|
|
29
|
+
};
|
|
30
|
+
exports.default = NoAcccessPage;
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -55,6 +55,8 @@ const styles_1 = require("../../components/Alert/styles");
|
|
|
55
55
|
const DisableI18nModal_1 = require("./components/DisableI18nModal");
|
|
56
56
|
const CustomFieldModal_1 = __importDefault(require("./components/CustomFieldModal"));
|
|
57
57
|
const CustomFieldTable_1 = __importDefault(require("./components/CustomFieldTable"));
|
|
58
|
+
const permissions_2 = __importDefault(require("../../permissions"));
|
|
59
|
+
const NoAccessPage_1 = __importDefault(require("../NoAccessPage"));
|
|
58
60
|
const RESTART_NOT_REQUIRED = { required: false };
|
|
59
61
|
const RESTART_REQUIRED = { required: true, reasons: [] };
|
|
60
62
|
const RELATION_ATTRIBUTE_TYPES = ['relation', 'media', 'component'];
|
|
@@ -77,6 +79,10 @@ const SettingsPage = () => {
|
|
|
77
79
|
const [contentTypeExpanded, setContentTypeExpanded] = (0, react_1.useState)(undefined);
|
|
78
80
|
const { data: navigationConfigData, isLoading: isConfigLoading, error: configErr, submitMutation, restoreMutation, restartMutation } = (0, useNavigationConfig_1.default)();
|
|
79
81
|
const { data: allContentTypesData, isLoading: isContentTypesLoading, error: contentTypesErr } = (0, useAllContentTypes_1.default)();
|
|
82
|
+
const viewPermissions = (0, react_1.useMemo)(() => ({
|
|
83
|
+
settings: permissions_2.default.settings
|
|
84
|
+
}), []);
|
|
85
|
+
const { isLoading: isLoadingForPermissions, allowedActions: { canSettings: canManageSettings, }, } = (0, helper_plugin_1.useRBAC)(viewPermissions);
|
|
80
86
|
const isLoading = isConfigLoading || isContentTypesLoading;
|
|
81
87
|
const isError = configErr || contentTypesErr;
|
|
82
88
|
const configContentTypes = navigationConfigData?.contentTypes || [];
|
|
@@ -155,6 +161,9 @@ const SettingsPage = () => {
|
|
|
155
161
|
};
|
|
156
162
|
const handleRestartDiscard = () => setRestartStatus(RESTART_NOT_REQUIRED);
|
|
157
163
|
const handleSetContentTypeExpanded = key => setContentTypeExpanded(key === contentTypeExpanded ? undefined : key);
|
|
164
|
+
if (!(isLoadingForPermissions || canManageSettings)) {
|
|
165
|
+
return (react_1.default.createElement(NoAccessPage_1.default, null));
|
|
166
|
+
}
|
|
158
167
|
if (isLoading || isError) {
|
|
159
168
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
160
169
|
react_1.default.createElement(helper_plugin_1.SettingsPageTitle, { name: (0, utils_2.getMessage)('Settings.email.plugin.title', 'Configuration') }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default NavigationHeader;
|
|
2
|
-
declare function NavigationHeader({ activeNavigation, availableNavigations, structureHasErrors, structureHasChanged, handleChangeSelection, handleLocalizationSelection, handleSave, config, }: {
|
|
2
|
+
declare function NavigationHeader({ activeNavigation, availableNavigations, structureHasErrors, structureHasChanged, handleChangeSelection, handleLocalizationSelection, handleSave, config, permissions, }: {
|
|
3
3
|
activeNavigation: any;
|
|
4
4
|
availableNavigations: any;
|
|
5
5
|
structureHasErrors: any;
|
|
@@ -8,5 +8,6 @@ declare function NavigationHeader({ activeNavigation, availableNavigations, stru
|
|
|
8
8
|
handleLocalizationSelection: any;
|
|
9
9
|
handleSave: any;
|
|
10
10
|
config: any;
|
|
11
|
+
permissions?: {} | undefined;
|
|
11
12
|
}): JSX.Element;
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -46,7 +46,7 @@ const pickDefaultLocaleNavigation = ({ activeNavigation, config }) => config.i18
|
|
|
46
46
|
: activeNavigation?.localizations.find(({ localeCode }) => localeCode === config.defaultLocale)
|
|
47
47
|
: null
|
|
48
48
|
: activeNavigation;
|
|
49
|
-
const NavigationHeader = ({ activeNavigation, availableNavigations, structureHasErrors, structureHasChanged, handleChangeSelection, handleLocalizationSelection, handleSave, config, }) => {
|
|
49
|
+
const NavigationHeader = ({ activeNavigation, availableNavigations, structureHasErrors, structureHasChanged, handleChangeSelection, handleLocalizationSelection, handleSave, config, permissions = {}, }) => {
|
|
50
50
|
const { formatMessage } = (0, react_intl_1.useIntl)();
|
|
51
51
|
const allLocaleVersions = (0, react_1.useMemo)(() => activeNavigation?.localizations.length && config.i18nEnabled
|
|
52
52
|
? (0, lodash_1.uniqBy)([activeNavigation, ...(activeNavigation.localizations ?? [])].sort((a, b) => a.localeCode.localeCompare(b.localeCode)), 'id')
|
|
@@ -54,21 +54,22 @@ const NavigationHeader = ({ activeNavigation, availableNavigations, structureHas
|
|
|
54
54
|
const hasLocalizations = config.i18nEnabled && allLocaleVersions.length;
|
|
55
55
|
const passedActiveNavigation = pickDefaultLocaleNavigation({ activeNavigation, config });
|
|
56
56
|
const { closeNavigationManagerModal, openNavigationManagerModal, navigationManagerModal } = (0, useNavigationManager_1.useNavigationManager)();
|
|
57
|
+
const { canUpdate } = permissions;
|
|
57
58
|
return (react_1.default.createElement(Layout_1.HeaderLayout, { primaryAction: react_1.default.createElement(Stack_1.Stack, { horizontal: true, size: 2 },
|
|
58
|
-
react_1.default.createElement(Box_1.Box, {
|
|
59
|
+
react_1.default.createElement(Box_1.Box, { marginRight: "8px" },
|
|
59
60
|
react_1.default.createElement(Grid_1.Grid, { gap: 4 },
|
|
60
61
|
!hasLocalizations ? (react_1.default.createElement(Grid_1.GridItem, { col: 2 })) : null,
|
|
61
|
-
react_1.default.createElement(Grid_1.GridItem, { col: 3 },
|
|
62
|
-
react_1.default.createElement(Button_1.Button, { onClick: openNavigationManagerModal, startIcon: null, type: "button", variant: "secondary", fullWidth: true, size: "S" }, formatMessage((0, translations_1.getTrad)('header.action.manage')))),
|
|
63
|
-
react_1.default.createElement(Grid_1.GridItem, { col: 4 },
|
|
62
|
+
canUpdate && (react_1.default.createElement(Grid_1.GridItem, { col: 3 },
|
|
63
|
+
react_1.default.createElement(Button_1.Button, { onClick: openNavigationManagerModal, startIcon: null, type: "button", variant: "secondary", fullWidth: true, size: "S" }, formatMessage((0, translations_1.getTrad)('header.action.manage'))))),
|
|
64
|
+
react_1.default.createElement(Grid_1.GridItem, { col: canUpdate ? 4 : 10 },
|
|
64
65
|
react_1.default.createElement(Select_1.Select, { type: "select", placeholder: "Change navigation", name: "navigationSelect", onChange: handleChangeSelection, value: passedActiveNavigation?.id, size: "S", style: null }, availableNavigations.map(({ id, name }) => react_1.default.createElement(Select_1.Option, { key: id, value: id }, name)))),
|
|
65
66
|
hasLocalizations
|
|
66
67
|
? react_1.default.createElement(Grid_1.GridItem, { col: 2 },
|
|
67
68
|
react_1.default.createElement(Select_1.Select, { type: "select", placeholder: formatMessage((0, translations_1.getTrad)('pages.main.header.localization.select.placeholder')), name: "navigationLocalizationSelect", onChange: handleLocalizationSelection, value: activeNavigation?.id, size: "S" }, allLocaleVersions.map(({ id, localeCode }) => react_1.default.createElement(Select_1.Option, { key: id, value: id }, localeCode))))
|
|
68
69
|
: null,
|
|
69
|
-
react_1.default.createElement(Grid_1.GridItem, { col: 3 },
|
|
70
|
-
react_1.default.createElement(Button_1.Button, { onClick: handleSave, startIcon: submitIcon, disabled: structureHasErrors || !structureHasChanged, type: "submit", fullWidth: true, size: "S" }, formatMessage((0, translations_1.getTrad)('submit.cta.save')))))),
|
|
71
|
-
navigationManagerModal), title: formatMessage({
|
|
70
|
+
canUpdate && (react_1.default.createElement(Grid_1.GridItem, { col: 3 },
|
|
71
|
+
react_1.default.createElement(Button_1.Button, { onClick: handleSave, startIcon: submitIcon, disabled: structureHasErrors || !structureHasChanged, type: "submit", fullWidth: true, size: "S" }, formatMessage((0, translations_1.getTrad)('submit.cta.save'))))))),
|
|
72
|
+
canUpdate && navigationManagerModal), title: formatMessage({
|
|
72
73
|
id: (0, translations_1.getTrad)('header.title'),
|
|
73
74
|
defaultMessage: 'UI Navigation',
|
|
74
75
|
}), subtitle: formatMessage({
|
|
@@ -44,12 +44,13 @@ const translations_1 = require("../../../../translations");
|
|
|
44
44
|
const types_1 = require("../../../../../../types");
|
|
45
45
|
const AdditionalFieldInput_1 = __importDefault(require("../../../../components/AdditionalFieldInput"));
|
|
46
46
|
const utils_2 = require("../../../../utils");
|
|
47
|
-
const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading, inputsPrefix, data, contentTypes = [], contentTypeEntities = [], usedContentTypeEntities = [], availableAudience = [], additionalFields = [], contentTypesNameFields = {}, onSubmit, onCancel, getContentTypeEntities, usedContentTypesData, appendLabelPublicationStatus = appendLabelPublicationStatusFallback, locale, readNavigationItemFromLocale, slugify, }) => {
|
|
47
|
+
const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading, inputsPrefix, data, contentTypes = [], contentTypeEntities = [], usedContentTypeEntities = [], availableAudience = [], additionalFields = [], contentTypesNameFields = {}, onSubmit, onCancel, getContentTypeEntities, usedContentTypesData, appendLabelPublicationStatus = appendLabelPublicationStatusFallback, locale, readNavigationItemFromLocale, slugify, permissions = {}, }) => {
|
|
48
48
|
const [isLoading, setIsLoading] = (0, react_1.useState)(isPreloading);
|
|
49
49
|
const [hasBeenInitialized, setInitializedState] = (0, react_1.useState)(false);
|
|
50
50
|
const [hasChanged, setChangedState] = (0, react_1.useState)(false);
|
|
51
51
|
const [contentTypeSearchQuery, setContentTypeSearchQuery] = (0, react_1.useState)(undefined);
|
|
52
52
|
const [contentTypeSearchInputValue, setContentTypeSearchInputValue] = (0, react_1.useState)(undefined);
|
|
53
|
+
const { canUpdate } = permissions;
|
|
53
54
|
const formik = (0, formik_1.useFormik)({
|
|
54
55
|
initialValues: formDefinition.defaultValues,
|
|
55
56
|
onSubmit: loadingAware(async (payload) => onSubmit(await sanitizePayload(slugify, payload, data)), setIsLoading),
|
|
@@ -380,20 +381,20 @@ const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading,
|
|
|
380
381
|
react_1.default.createElement(ModalLayout_1.ModalBody, null,
|
|
381
382
|
react_1.default.createElement(Grid_1.Grid, { gap: 5 },
|
|
382
383
|
react_1.default.createElement(Grid_1.GridItem, { key: "title", col: 12 },
|
|
383
|
-
react_1.default.createElement(helper_plugin_1.GenericInput, { autoFocused: true, intlLabel: (0, translations_1.getTrad)('popup.item.form.title.label', 'Title'), name: "title", placeholder: (0, translations_1.getTrad)("e.g. Blog", 'e.g. Blog'), description: (0, translations_1.getTrad)('popup.item.form.title.placeholder', 'e.g. Blog'), type: "text", error: formik.errors.title, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.title })),
|
|
384
|
+
react_1.default.createElement(helper_plugin_1.GenericInput, { autoFocused: true, intlLabel: (0, translations_1.getTrad)('popup.item.form.title.label', 'Title'), name: "title", placeholder: (0, translations_1.getTrad)("e.g. Blog", 'e.g. Blog'), description: (0, translations_1.getTrad)('popup.item.form.title.placeholder', 'e.g. Blog'), type: "text", disabled: !canUpdate, error: formik.errors.title, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.title })),
|
|
384
385
|
react_1.default.createElement(Grid_1.GridItem, { key: "type", col: 4, lg: 12 },
|
|
385
|
-
react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)('popup.item.form.type.label', 'Internal link'), name: "type", options: navigationItemTypeOptions, type: "select", error: formik.errors.type, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.type })),
|
|
386
|
+
react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)('popup.item.form.type.label', 'Internal link'), name: "type", options: navigationItemTypeOptions, type: "select", disabled: !canUpdate, error: formik.errors.type, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.type })),
|
|
386
387
|
react_1.default.createElement(Grid_1.GridItem, { key: "menuAttached", col: 4, lg: 12 },
|
|
387
|
-
react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)('popup.item.form.menuAttached.label', 'MenuAttached'), name: "menuAttached", type: "bool", error: formik.errors.menuAttached, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.menuAttached, disabled: config.cascadeMenuAttached ? !(data.isMenuAllowedLevel && data.parentAttachedToMenu) : false })),
|
|
388
|
+
react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)('popup.item.form.menuAttached.label', 'MenuAttached'), name: "menuAttached", type: "bool", error: formik.errors.menuAttached, onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values.menuAttached, disabled: !canUpdate || (config.cascadeMenuAttached ? !(data.isMenuAllowedLevel && data.parentAttachedToMenu) : false) })),
|
|
388
389
|
react_1.default.createElement(Grid_1.GridItem, { key: "path", col: 12 },
|
|
389
|
-
react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)(`popup.item.form.${pathSourceName}.label`, 'Path'), name: pathSourceName, placeholder: (0, translations_1.getTrad)(`popup.item.form.${pathSourceName}.placeholder`, 'e.g. Blog'), type: "text", error: formik.errors[pathSourceName], onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values[pathSourceName], description: generatePreviewPath() })),
|
|
390
|
+
react_1.default.createElement(helper_plugin_1.GenericInput, { intlLabel: (0, translations_1.getTrad)(`popup.item.form.${pathSourceName}.label`, 'Path'), name: pathSourceName, placeholder: (0, translations_1.getTrad)(`popup.item.form.${pathSourceName}.placeholder`, 'e.g. Blog'), type: "text", disabled: !canUpdate, error: formik.errors[pathSourceName], onChange: ({ target: { name, value } }) => onChange({ name, value }), value: formik.values[pathSourceName], description: generatePreviewPath() })),
|
|
390
391
|
formik.values.type === utils_1.navigationItemType.INTERNAL && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
391
392
|
react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12 },
|
|
392
|
-
react_1.default.createElement(helper_plugin_1.GenericInput, { type: "select", intlLabel: (0, translations_1.getTrad)('popup.item.form.relatedType.label', 'Related Type'), placeholder: (0, translations_1.getTrad)('popup.item.form.relatedType.placeholder', 'Related Type'), name: "relatedType", error: formik.errors.relatedType, onChange: onChangeRelatedType, options: relatedTypeSelectOptions, value: formik.values.relatedType, disabled: isLoading || (0, lodash_1.isEmpty)(relatedTypeSelectOptions), description: !isLoading && (0, lodash_1.isEmpty)(relatedTypeSelectOptions)
|
|
393
|
+
react_1.default.createElement(helper_plugin_1.GenericInput, { type: "select", intlLabel: (0, translations_1.getTrad)('popup.item.form.relatedType.label', 'Related Type'), placeholder: (0, translations_1.getTrad)('popup.item.form.relatedType.placeholder', 'Related Type'), name: "relatedType", error: formik.errors.relatedType, onChange: onChangeRelatedType, options: relatedTypeSelectOptions, value: formik.values.relatedType, disabled: isLoading || (0, lodash_1.isEmpty)(relatedTypeSelectOptions) || !canUpdate, description: !isLoading && (0, lodash_1.isEmpty)(relatedTypeSelectOptions)
|
|
393
394
|
? (0, translations_1.getTrad)('popup.item.form.relatedType.empty', 'There are no more content types')
|
|
394
395
|
: undefined })),
|
|
395
396
|
formik.values.relatedType && !isSingleSelected && (react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12 },
|
|
396
|
-
react_1.default.createElement(helper_plugin_1.GenericInput, { type: "select", intlLabel: (0, translations_1.getTrad)('popup.item.form.related.label', 'Related'), placeholder: (0, translations_1.getTrad)('popup.item.form.related.label', 'Related'), name: "related", error: formik.errors.related, onChange: ({ target: { name, value } }) => onChange({ name, value }), onInputChange: debounceContentTypeSearchQuery, inputValue: contentTypeSearchInputValue, options: relatedSelectOptions, value: formik.values.related, disabled: isLoading || thereAreNoMoreContentTypes, description: !isLoading && thereAreNoMoreContentTypes
|
|
397
|
+
react_1.default.createElement(helper_plugin_1.GenericInput, { type: "select", intlLabel: (0, translations_1.getTrad)('popup.item.form.related.label', 'Related'), placeholder: (0, translations_1.getTrad)('popup.item.form.related.label', 'Related'), name: "related", error: formik.errors.related, onChange: ({ target: { name, value } }) => onChange({ name, value }), onInputChange: debounceContentTypeSearchQuery, inputValue: contentTypeSearchInputValue, options: relatedSelectOptions, value: formik.values.related, disabled: isLoading || thereAreNoMoreContentTypes || !canUpdate, description: !isLoading && thereAreNoMoreContentTypes
|
|
397
398
|
? {
|
|
398
399
|
id: (0, translations_1.getTradId)('popup.item.form.related.empty'),
|
|
399
400
|
defaultMessage: 'There are no more entities',
|
|
@@ -405,19 +406,19 @@ const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading,
|
|
|
405
406
|
return (react_1.default.createElement(Grid_1.GridItem, { key: "audience", col: 6, lg: 12 },
|
|
406
407
|
react_1.default.createElement(Select_1.Select, { id: "audience", placeholder: (0, utils_2.getMessage)('popup.item.form.audience.placeholder'), label: (0, utils_2.getMessage)('popup.item.form.audience.label'), onChange: onAudienceChange, value: formik.values.audience, hint: !isLoading && (0, lodash_1.isEmpty)(audienceOptions)
|
|
407
408
|
? (0, utils_2.getMessage)('popup.item.form.audience.empty', 'There are no more audiences')
|
|
408
|
-
: undefined, multi: true, withTags: true, disabled: (0, lodash_1.isEmpty)(audienceOptions) }, audienceOptions.map(({ value, label }) => react_1.default.createElement(Select_1.Option, { key: value, value: value }, label)))));
|
|
409
|
+
: undefined, multi: true, withTags: true, disabled: (0, lodash_1.isEmpty)(audienceOptions) || !canUpdate }, audienceOptions.map(({ value, label }) => react_1.default.createElement(Select_1.Option, { key: value, value: value }, label)))));
|
|
409
410
|
}
|
|
410
411
|
else {
|
|
411
412
|
return (react_1.default.createElement(Grid_1.GridItem, { key: additionalField.name, col: 6, lg: 12 },
|
|
412
|
-
react_1.default.createElement(AdditionalFieldInput_1.default, { field: additionalField, isLoading: isLoading, onChange: onAdditionalFieldChange, value: (0, lodash_1.get)(formik.values, `additionalFields.${additionalField.name}`, null), error: (0, lodash_1.get)(formik.errors, `additionalFields.${additionalField.name}`, null) })));
|
|
413
|
+
react_1.default.createElement(AdditionalFieldInput_1.default, { field: additionalField, isLoading: isLoading, onChange: onAdditionalFieldChange, value: (0, lodash_1.get)(formik.values, `additionalFields.${additionalField.name}`, null), disabled: !canUpdate, error: (0, lodash_1.get)(formik.errors, `additionalFields.${additionalField.name}`, null) })));
|
|
413
414
|
}
|
|
414
415
|
})),
|
|
415
416
|
isI18nBootstrapAvailable ? (react_1.default.createElement(Grid_1.Grid, { gap: 5, paddingTop: 5 },
|
|
416
417
|
react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12 },
|
|
417
|
-
react_1.default.createElement(helper_plugin_1.GenericInput, { ...itemCopyProps, type: "select", name: itemLocaleCopyField, error: (0, lodash_1.get)(formik.errors, itemLocaleCopyField), onChange: onChangeLocaleCopy, options: availableLocaleOptions, value: itemLocaleCopyValue, disabled: isLoading })),
|
|
418
|
-
react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12, paddingTop: 6 },
|
|
419
|
-
react_1.default.createElement(Button_1.Button, { variant: "tertiary", onClick: onCopyFromLocale, disabled: isLoading || !itemLocaleCopyValue }, (0, utils_2.getMessage)('popup.item.form.i18n.locale.button'))))) : null)),
|
|
420
|
-
react_1.default.createElement(NavigationItemPopupFooter_1.NavigationItemPopupFooter, { handleSubmit: formik.handleSubmit, handleCancel: onCancel, submitDisabled: submitDisabled })));
|
|
418
|
+
react_1.default.createElement(helper_plugin_1.GenericInput, { ...itemCopyProps, type: "select", name: itemLocaleCopyField, error: (0, lodash_1.get)(formik.errors, itemLocaleCopyField), onChange: onChangeLocaleCopy, options: availableLocaleOptions, value: itemLocaleCopyValue, disabled: isLoading || !canUpdate })),
|
|
419
|
+
canUpdate && (react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12, paddingTop: 6 },
|
|
420
|
+
react_1.default.createElement(Button_1.Button, { variant: "tertiary", onClick: onCopyFromLocale, disabled: isLoading || !itemLocaleCopyValue }, (0, utils_2.getMessage)('popup.item.form.i18n.locale.button')))))) : null)),
|
|
421
|
+
react_1.default.createElement(NavigationItemPopupFooter_1.NavigationItemPopupFooter, { handleSubmit: formik.handleSubmit, handleCancel: onCancel, submitDisabled: submitDisabled, canUpdate: canUpdate })));
|
|
421
422
|
};
|
|
422
423
|
const appendLabelPublicationStatusFallback = () => "";
|
|
423
424
|
const loadingAware = (action, isLoading) => async (input) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Audience, Effect, ContentTypeEntity, NavigationItemAdditionalField, NavigationItemAdditionalFieldValues, NavigationItemType, NavigationPluginConfig, PluginConfigNameFields, ToBeFixed, VoidEffect } from '../../../../../../types';
|
|
1
|
+
import { Audience, Effect, ContentTypeEntity, NavigationItemAdditionalField, NavigationItemAdditionalFieldValues, NavigationItemType, NavigationPluginConfig, PluginConfigNameFields, PluginPermissions, ToBeFixed, VoidEffect } from '../../../../../../types';
|
|
2
2
|
import { Id } from 'strapi-typed';
|
|
3
3
|
import { StrapiContentTypeSchema } from '../../../SettingsPage/types';
|
|
4
4
|
export declare type FormEventTarget<TValue = unknown> = {
|
|
@@ -63,6 +63,7 @@ export declare type NavigationItemFormProps = {
|
|
|
63
63
|
slugify: (q: string) => Promise<{
|
|
64
64
|
slug: string;
|
|
65
65
|
}>;
|
|
66
|
+
permissions: PluginPermissions;
|
|
66
67
|
};
|
|
67
68
|
export declare type ContentTypeSearchQuery = ToBeFixed;
|
|
68
69
|
export declare type RawFormPayload = {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export function NavigationItemPopupFooter({ handleCancel, handleSubmit, submitDisabled, formViewId }: {
|
|
1
|
+
export function NavigationItemPopupFooter({ handleCancel, handleSubmit, submitDisabled, formViewId, canUpdate }: {
|
|
2
2
|
handleCancel: any;
|
|
3
3
|
handleSubmit: any;
|
|
4
4
|
submitDisabled: any;
|
|
5
5
|
formViewId: any;
|
|
6
|
-
|
|
6
|
+
canUpdate: any;
|
|
7
|
+
}): JSX.Element | null;
|
|
7
8
|
export namespace NavigationItemPopupFooter {
|
|
8
9
|
namespace defaultProps {
|
|
9
10
|
const onValidate: undefined;
|
|
@@ -17,6 +18,7 @@ export namespace NavigationItemPopupFooter {
|
|
|
17
18
|
export { submitDisabled_1 as submitDisabled };
|
|
18
19
|
const formViewId_1: PropTypes.Requireable<object>;
|
|
19
20
|
export { formViewId_1 as formViewId };
|
|
21
|
+
export const canUpdate: PropTypes.Requireable<boolean>;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
import PropTypes from "prop-types";
|
|
@@ -9,7 +9,10 @@ const prop_types_1 = __importDefault(require("prop-types"));
|
|
|
9
9
|
const Button_1 = require("@strapi/design-system/Button");
|
|
10
10
|
const ModalLayout_1 = require("@strapi/design-system/ModalLayout");
|
|
11
11
|
const utils_1 = require("../../../../utils");
|
|
12
|
-
const NavigationItemPopupFooter = ({ handleCancel, handleSubmit, submitDisabled, formViewId }) => {
|
|
12
|
+
const NavigationItemPopupFooter = ({ handleCancel, handleSubmit, submitDisabled, formViewId, canUpdate }) => {
|
|
13
|
+
if (!canUpdate) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
13
16
|
return (react_1.default.createElement(ModalLayout_1.ModalFooter, { startActions: react_1.default.createElement(Button_1.Button, { onClick: handleCancel, variant: "tertiary" }, (0, utils_1.getMessage)('popup.item.form.button.cancel')), endActions: react_1.default.createElement(Button_1.Button, { onClick: handleSubmit, disabled: submitDisabled }, (0, utils_1.getMessage)(`popup.item.form.button.save`)) }));
|
|
14
17
|
};
|
|
15
18
|
exports.NavigationItemPopupFooter = NavigationItemPopupFooter;
|
|
@@ -23,5 +26,6 @@ exports.NavigationItemPopupFooter.propTypes = {
|
|
|
23
26
|
handleSubmit: prop_types_1.default.func,
|
|
24
27
|
submitDisabled: prop_types_1.default.bool,
|
|
25
28
|
formViewId: prop_types_1.default.object,
|
|
29
|
+
canUpdate: prop_types_1.default.bool,
|
|
26
30
|
};
|
|
27
31
|
//# sourceMappingURL=NavigationItemPopupFooter.js.map
|