strapi-plugin-navigation 2.2.3 → 2.2.5

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.
Files changed (66) hide show
  1. package/README.md +35 -6
  2. package/admin/src/components/AdditionalFieldInput/index.js +2 -2
  3. package/admin/src/components/AdditionalFieldInput/types.d.ts +1 -0
  4. package/admin/src/components/Item/ItemCardHeader/icons.d.ts +1 -1
  5. package/admin/src/components/Item/ItemCardHeader/icons.js +2 -1
  6. package/admin/src/components/Item/ItemCardHeader/index.d.ts +1 -0
  7. package/admin/src/components/Item/ItemCardHeader/index.js +10 -8
  8. package/admin/src/components/Item/index.js +7 -6
  9. package/admin/src/components/NavigationItemList/index.d.ts +2 -1
  10. package/admin/src/components/NavigationItemList/index.js +2 -2
  11. package/admin/src/hooks/useNavigationManager.d.ts +0 -1
  12. package/admin/src/index.js +1 -1
  13. package/admin/src/pages/DataManagerProvider/index.js +2 -0
  14. package/admin/src/pages/DataManagerProvider/reducer.d.ts +1 -1
  15. package/admin/src/pages/NoAccessPage/index.d.ts +3 -0
  16. package/admin/src/pages/NoAccessPage/index.js +31 -0
  17. package/admin/src/pages/SettingsPage/index.d.ts +0 -1
  18. package/admin/src/pages/SettingsPage/index.js +9 -0
  19. package/admin/src/pages/SettingsPage/types.d.ts +1 -1
  20. package/admin/src/pages/View/components/NavigationHeader/index.d.ts +2 -1
  21. package/admin/src/pages/View/components/NavigationHeader/index.js +9 -8
  22. package/admin/src/pages/View/components/NavigationItemForm/index.js +36 -24
  23. package/admin/src/pages/View/components/NavigationItemForm/types.d.ts +8 -1
  24. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupFooter.d.ts +4 -2
  25. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupFooter.js +5 -1
  26. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.d.ts +2 -1
  27. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +6 -2
  28. package/admin/src/pages/View/components/NavigationItemPopup/index.d.ts +4 -1
  29. package/admin/src/pages/View/components/NavigationItemPopup/index.js +5 -3
  30. package/admin/src/pages/View/components/NavigationManager/AllNavigations/icons.d.ts +0 -1
  31. package/admin/src/pages/View/components/NavigationManager/AllNavigations/index.d.ts +0 -1
  32. package/admin/src/pages/View/components/NavigationManager/DeletionConfirm/index.d.ts +0 -1
  33. package/admin/src/pages/View/components/NavigationManager/ErrorDetails/index.d.ts +0 -1
  34. package/admin/src/pages/View/components/NavigationManager/Form/index.d.ts +0 -1
  35. package/admin/src/pages/View/components/NavigationManager/NavigationUpdate/index.d.ts +0 -1
  36. package/admin/src/pages/View/components/NavigationManager/NewNavigation/index.d.ts +0 -1
  37. package/admin/src/pages/View/components/NavigationManager/index.d.ts +0 -1
  38. package/admin/src/pages/View/index.js +40 -20
  39. package/admin/src/permissions.d.ts +4 -0
  40. package/admin/src/permissions.js +1 -0
  41. package/admin/src/translations/en.json +8 -0
  42. package/package.json +2 -2
  43. package/permissions.d.ts +1 -0
  44. package/permissions.js +1 -0
  45. package/server/bootstrap/index.js +6 -0
  46. package/server/config/index.js +0 -1
  47. package/server/config/setupStrategy.js +0 -9
  48. package/server/content-types/index.d.ts +2 -0
  49. package/server/content-types/navigation/index.d.ts +1 -0
  50. package/server/content-types/navigation/schema.d.ts +1 -0
  51. package/server/content-types/navigation/schema.js +2 -1
  52. package/server/content-types/navigation-item/index.d.ts +1 -0
  53. package/server/content-types/navigation-item/schema.d.ts +1 -0
  54. package/server/content-types/navigation-item/schema.js +2 -1
  55. package/server/controllers/admin.js +13 -0
  56. package/server/index.d.ts +2 -0
  57. package/server/routes/admin.js +10 -0
  58. package/server/services/admin.js +2 -10
  59. package/server/services/client.js +10 -14
  60. package/server/services/common.js +27 -1
  61. package/strapi-server.d.ts +2 -0
  62. package/tsconfig.tsbuildinfo +1 -1
  63. package/types/config.d.ts +0 -1
  64. package/types/controllers.d.ts +5 -0
  65. package/types/services.d.ts +1 -0
  66. package/types/utils.d.ts +5 -0
@@ -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> = {
@@ -60,6 +60,10 @@ export declare type NavigationItemFormProps = {
60
60
  availableLocale: string[];
61
61
  readNavigationItemFromLocale: ToBeFixed;
62
62
  inputsPrefix: string;
63
+ slugify: (q: string) => Promise<{
64
+ slug: string;
65
+ }>;
66
+ permissions: PluginPermissions;
63
67
  };
64
68
  export declare type ContentTypeSearchQuery = ToBeFixed;
65
69
  export declare type RawFormPayload = {
@@ -86,5 +90,8 @@ export declare type SanitizedFormPayload = {
86
90
  singleRelatedItem: ContentTypeEntity | undefined;
87
91
  uiRouterKey: string | undefined;
88
92
  };
93
+ export declare type Slugify = (q: string) => Promise<{
94
+ slug: string;
95
+ }>;
89
96
  export {};
90
97
  //# sourceMappingURL=types.d.ts.map
@@ -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
- }): JSX.Element;
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
@@ -1,4 +1,5 @@
1
- export function NavigationItemPopupHeader({ isNewItem }: {
1
+ export function NavigationItemPopupHeader({ isNewItem, canUpdate }: {
2
2
  isNewItem: any;
3
+ canUpdate: any;
3
4
  }): JSX.Element;
4
5
  //# sourceMappingURL=NavigationItemPopupHeader.d.ts.map
@@ -8,9 +8,13 @@ const react_1 = __importDefault(require("react"));
8
8
  const Typography_1 = require("@strapi/design-system/Typography");
9
9
  const ModalLayout_1 = require("@strapi/design-system/ModalLayout");
10
10
  const utils_1 = require("../../../../utils");
11
- const NavigationItemPopupHeader = ({ isNewItem }) => {
11
+ const NavigationItemPopupHeader = ({ isNewItem, canUpdate }) => {
12
+ let modalType = 'view';
13
+ if (canUpdate) {
14
+ modalType = isNewItem ? 'new' : 'edit';
15
+ }
12
16
  return (react_1.default.createElement(ModalLayout_1.ModalHeader, null,
13
- react_1.default.createElement(Typography_1.Typography, { variant: "omega", fontWeight: "bold", textColor: "neutral800", as: "h2", id: "asset-dialog-title" }, (0, utils_1.getMessage)(`popup.item.header.${isNewItem ? 'new' : 'edit'}`))));
17
+ react_1.default.createElement(Typography_1.Typography, { variant: "omega", fontWeight: "bold", textColor: "neutral800", as: "h2", id: "asset-dialog-title" }, (0, utils_1.getMessage)(`popup.item.header.${modalType}`))));
14
18
  };
15
19
  exports.NavigationItemPopupHeader = NavigationItemPopupHeader;
16
20
  //# sourceMappingURL=NavigationItemPopupHeader.js.map
@@ -1,5 +1,5 @@
1
1
  export default NavigationItemPopUp;
2
- declare function NavigationItemPopUp({ availableLocale, isOpen, isLoading, data, config, onSubmit, onClose, usedContentTypeItems, getContentTypeItems, usedContentTypesData, locale, readNavigationItemFromLocale, }: {
2
+ declare function NavigationItemPopUp({ availableLocale, isOpen, isLoading, data, config, onSubmit, onClose, usedContentTypeItems, getContentTypeItems, usedContentTypesData, locale, readNavigationItemFromLocale, slugify, permissions, }: {
3
3
  availableLocale: any;
4
4
  isOpen: any;
5
5
  isLoading: any;
@@ -12,6 +12,8 @@ declare function NavigationItemPopUp({ availableLocale, isOpen, isLoading, data,
12
12
  usedContentTypesData: any;
13
13
  locale: any;
14
14
  readNavigationItemFromLocale: any;
15
+ slugify: any;
16
+ permissions?: {} | undefined;
15
17
  }): JSX.Element;
16
18
  declare namespace NavigationItemPopUp {
17
19
  namespace propTypes {
@@ -24,6 +26,7 @@ declare namespace NavigationItemPopUp {
24
26
  const getContentTypeItems: PropTypes.Validator<(...args: any[]) => any>;
25
27
  const locale: PropTypes.Requireable<string>;
26
28
  const readNavigationItemFromLocale: PropTypes.Validator<(...args: any[]) => any>;
29
+ const slugify: PropTypes.Validator<(...args: any[]) => any>;
27
30
  }
28
31
  }
29
32
  import PropTypes from "prop-types";
@@ -34,12 +34,13 @@ const NavigationItemForm_1 = __importDefault(require("../NavigationItemForm"));
34
34
  const parsers_1 = require("../../utils/parsers");
35
35
  const NavigationItemPopupHeader_1 = require("./NavigationItemPopupHeader");
36
36
  const utils_1 = require("../../../../utils");
37
- const NavigationItemPopUp = ({ availableLocale, isOpen, isLoading, data, config = {}, onSubmit, onClose, usedContentTypeItems, getContentTypeItems, usedContentTypesData, locale, readNavigationItemFromLocale, }) => {
37
+ const NavigationItemPopUp = ({ availableLocale, isOpen, isLoading, data, config = {}, onSubmit, onClose, usedContentTypeItems, getContentTypeItems, usedContentTypesData, locale, readNavigationItemFromLocale, slugify, permissions = {}, }) => {
38
38
  const handleOnSubmit = (payload) => {
39
39
  onSubmit(payload);
40
40
  };
41
41
  const { related, relatedType } = data;
42
42
  const { availableAudience = [], additionalFields, contentTypes, contentTypeItems, contentTypesNameFields = {}, } = config;
43
+ const { canUpdate } = permissions;
43
44
  const appendLabelPublicationStatus = (label = '', item = {}, isCollection = false) => {
44
45
  const appendix = (0, parsers_1.isRelationPublished)({
45
46
  relatedRef: item,
@@ -70,8 +71,8 @@ const NavigationItemPopUp = ({ availableLocale, isOpen, isLoading, data, config
70
71
  const preparedData = (0, react_1.useMemo)(prepareFormData.bind(null, data), [data]);
71
72
  const hasViewId = !!data.viewId;
72
73
  return (react_1.default.createElement(ModalLayout_1.ModalLayout, { labelledBy: "condition-modal-breadcrumbs", onClose: onClose, isOpen: isOpen },
73
- react_1.default.createElement(NavigationItemPopupHeader_1.NavigationItemPopupHeader, { isNewItem: !hasViewId }),
74
- react_1.default.createElement(NavigationItemForm_1.default, { availableLocale: availableLocale, config: config, data: preparedData, isLoading: isLoading, additionalFields: additionalFields, contentTypesNameFields: contentTypesNameFields, availableAudience: availableAudience, contentTypes: contentTypes, contentTypeEntities: contentTypeItems, usedContentTypeEntities: usedContentTypeItems, getContentTypeEntities: getContentTypeItems, usedContentTypesData: usedContentTypesData, onSubmit: handleOnSubmit, onCancel: onClose, appendLabelPublicationStatus: appendLabelPublicationStatus, locale: locale, readNavigationItemFromLocale: readNavigationItemFromLocale })));
74
+ react_1.default.createElement(NavigationItemPopupHeader_1.NavigationItemPopupHeader, { isNewItem: !hasViewId, canUpdate: canUpdate }),
75
+ react_1.default.createElement(NavigationItemForm_1.default, { availableLocale: availableLocale, config: config, data: preparedData, isLoading: isLoading, additionalFields: additionalFields, contentTypesNameFields: contentTypesNameFields, availableAudience: availableAudience, contentTypes: contentTypes, contentTypeEntities: contentTypeItems, usedContentTypeEntities: usedContentTypeItems, getContentTypeEntities: getContentTypeItems, usedContentTypesData: usedContentTypesData, onSubmit: handleOnSubmit, onCancel: onClose, appendLabelPublicationStatus: appendLabelPublicationStatus, locale: locale, readNavigationItemFromLocale: readNavigationItemFromLocale, slugify: slugify, permissions: permissions })));
75
76
  };
76
77
  NavigationItemPopUp.propTypes = {
77
78
  data: prop_types_1.default.object.isRequired,
@@ -83,6 +84,7 @@ NavigationItemPopUp.propTypes = {
83
84
  getContentTypeItems: prop_types_1.default.func.isRequired,
84
85
  locale: prop_types_1.default.string,
85
86
  readNavigationItemFromLocale: prop_types_1.default.func.isRequired,
87
+ slugify: prop_types_1.default.func.isRequired,
86
88
  };
87
89
  exports.default = NavigationItemPopUp;
88
90
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  export declare const edit: JSX.Element;
3
2
  export declare const deleteIcon: JSX.Element;
4
3
  //# sourceMappingURL=icons.d.ts.map
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Footer } from "../Footer";
3
2
  import { CommonProps, ListState } from "../types";
4
3
  interface Props extends ListState, CommonProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Footer } from "../Footer";
3
2
  import { CommonProps, DeleteState } from "../types";
4
3
  interface Props extends DeleteState, CommonProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Footer } from "../Footer";
3
2
  import { CommonProps, ErrorState } from "../types";
4
3
  interface Props extends ErrorState, CommonProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { useIntl } from "react-intl";
3
2
  import { Effect } from "../../../../../../../types";
4
3
  import { Navigation } from "../types";
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Footer } from "../Footer";
3
2
  import { CommonProps, EditState } from "../types";
4
3
  interface Props extends EditState, CommonProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { Footer } from "../Footer";
3
2
  import { CommonProps, CreateState, Navigation } from "../types";
4
3
  interface Props extends CreateState, CommonProps {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { VoidEffect } from "../../../../../../types";
3
2
  import { State } from "./types";
4
3
  interface Props {
@@ -40,6 +40,7 @@ const Select_1 = require("@strapi/design-system/Select");
40
40
  const helper_plugin_1 = require("@strapi/helper-plugin");
41
41
  const EmptyDocuments_1 = __importDefault(require("@strapi/icons/EmptyDocuments"));
42
42
  const Plus_1 = __importDefault(require("@strapi/icons/Plus"));
43
+ const permissions_1 = __importDefault(require("../../permissions"));
43
44
  const NavigationItemList_1 = __importDefault(require("../../components/NavigationItemList"));
44
45
  const NavigationContentHeader_1 = __importDefault(require("./components/NavigationContentHeader"));
45
46
  const NavigationHeader_1 = __importDefault(require("./components/NavigationHeader"));
@@ -49,9 +50,15 @@ const Search_1 = __importDefault(require("../../components/Search"));
49
50
  const useDataManager_1 = __importDefault(require("../../hooks/useDataManager"));
50
51
  const translations_1 = require("../../translations");
51
52
  const parsers_1 = require("./utils/parsers");
53
+ const NoAccessPage_1 = __importDefault(require("../NoAccessPage"));
52
54
  const View = () => {
53
55
  const toggleNotification = (0, helper_plugin_1.useNotification)();
54
- const { items: availableNavigations, activeItem: activeNavigation, changedActiveItem: changedActiveNavigation, config, navigationItemPopupOpened, isLoading, isLoadingForAdditionalDataToBeSet, isLoadingForSubmit, handleChangeNavigationItemPopupVisibility, handleChangeSelection, handleChangeNavigationData, handleResetNavigationData, handleSubmitNavigation, handleLocalizationSelection, handleI18nCopy, getContentTypeItems, error, availableLocale: allAvailableLocale, readNavigationItemFromLocale, } = (0, useDataManager_1.default)();
56
+ const { items: availableNavigations, activeItem: activeNavigation, changedActiveItem: changedActiveNavigation, config, navigationItemPopupOpened, isLoading, isLoadingForAdditionalDataToBeSet, isLoadingForSubmit, handleChangeNavigationItemPopupVisibility, handleChangeSelection, handleChangeNavigationData, handleResetNavigationData, handleSubmitNavigation, handleLocalizationSelection, handleI18nCopy, getContentTypeItems, error, availableLocale: allAvailableLocale, readNavigationItemFromLocale, slugify, } = (0, useDataManager_1.default)();
57
+ const viewPermissions = (0, react_1.useMemo)(() => ({
58
+ access: permissions_1.default.access || permissions_1.default.update,
59
+ update: permissions_1.default.update,
60
+ }), []);
61
+ const { isLoading: isLoadingForPermissions, allowedActions: { canAccess, canUpdate, }, } = (0, helper_plugin_1.useRBAC)(viewPermissions);
55
62
  const availableLocale = (0, react_1.useMemo)(() => allAvailableLocale.filter(locale => locale !== changedActiveNavigation?.localeCode), [changedActiveNavigation, allAvailableLocale]);
56
63
  const { i18nCopyItemsModal, i18nCopySourceLocale, setI18nCopyModalOpened, setI18nCopySourceLocale } = (0, useI18nCopyNavigationItemsModal_1.useI18nCopyNavigationItemsModal)((0, react_1.useCallback)((sourceLocale) => {
57
64
  const source = activeNavigation?.localizations?.find(({ localeCode }) => localeCode === sourceLocale);
@@ -80,15 +87,17 @@ const View = () => {
80
87
  handleChangeNavigationItemPopupVisibility(visible);
81
88
  };
82
89
  const addNewNavigationItem = (0, react_1.useCallback)((event, viewParentId = null, isMenuAllowedLevel = true, levelPath = '', parentAttachedToMenu = true, structureId = "0") => {
83
- event.preventDefault();
84
- event.stopPropagation();
85
- changeNavigationItemPopupState(true, {
86
- viewParentId,
87
- isMenuAllowedLevel,
88
- levelPath,
89
- parentAttachedToMenu,
90
- structureId,
91
- });
90
+ if (canUpdate) {
91
+ event.preventDefault();
92
+ event.stopPropagation();
93
+ changeNavigationItemPopupState(true, {
94
+ viewParentId,
95
+ isMenuAllowedLevel,
96
+ levelPath,
97
+ parentAttachedToMenu,
98
+ structureId,
99
+ });
100
+ }
92
101
  }, [changeNavigationItemPopupState]);
93
102
  const usedContentTypesData = (0, react_1.useMemo)(() => changedActiveNavigation ? (0, parsers_1.usedContentTypes)(changedActiveNavigation.items) : [], [changedActiveNavigation]);
94
103
  const pullUsedContentTypeItem = (items = []) => items.reduce((prev, curr) => [...prev, curr.relatedRef ? {
@@ -198,7 +207,9 @@ const View = () => {
198
207
  tradId: 'header.action.collapseAll',
199
208
  margin: '8px',
200
209
  },
201
- {
210
+ ];
211
+ if (canUpdate) {
212
+ endActions.push({
202
213
  onClick: addNewNavigationItem,
203
214
  startIcon: react_1.default.createElement(Plus_1.default, null),
204
215
  disabled: isLoadingForSubmit,
@@ -206,12 +217,17 @@ const View = () => {
206
217
  variant: "default",
207
218
  tradId: 'header.action.newItem',
208
219
  margin: '16px',
209
- },
210
- ];
220
+ });
221
+ }
222
+ if (!canAccess && !isLoadingForPermissions) {
223
+ return (react_1.default.createElement(NoAccessPage_1.default, null));
224
+ }
211
225
  return (react_1.default.createElement(Main_1.Main, { labelledBy: "title", "aria-busy": isLoadingForSubmit },
212
- react_1.default.createElement(NavigationHeader_1.default, { structureHasErrors: structureHasErrors, structureHasChanged: structureChanged, availableNavigations: availableNavigations, activeNavigation: activeNavigation, handleChangeSelection: handleChangeNavigationSelection, handleSave: handleSave, handleLocalizationSelection: handleLocalizationSelection, config: config }),
226
+ react_1.default.createElement(NavigationHeader_1.default, { structureHasErrors: structureHasErrors, structureHasChanged: structureChanged, availableNavigations: availableNavigations, activeNavigation: activeNavigation, handleChangeSelection: handleChangeNavigationSelection, handleSave: handleSave, handleLocalizationSelection: handleLocalizationSelection, config: config, permissions: {
227
+ canAccess, canUpdate
228
+ } }),
213
229
  react_1.default.createElement(Layout_1.ContentLayout, null,
214
- isLoading && react_1.default.createElement(helper_plugin_1.LoadingIndicatorPage, null),
230
+ (isLoading || isLoadingForPermissions) && react_1.default.createElement(helper_plugin_1.LoadingIndicatorPage, null),
215
231
  changedActiveNavigation && (react_1.default.createElement(react_1.default.Fragment, null,
216
232
  react_1.default.createElement(NavigationContentHeader_1.default, { startActions: react_1.default.createElement(Search_1.default, { value: searchValue, setValue: setSearchValue }), endActions: endActions.map(({ tradId, margin, ...item }, i) => react_1.default.createElement(Box_1.Box, { marginLeft: margin, key: i },
217
233
  react_1.default.createElement(Button_1.Button, { ...item },
@@ -222,8 +238,8 @@ const View = () => {
222
238
  react_1.default.createElement(Icon_1.Icon, { as: EmptyDocuments_1.default, width: "160px", height: "88px", color: "" }),
223
239
  react_1.default.createElement(Box_1.Box, { padding: 4 },
224
240
  react_1.default.createElement(Typography_1.Typography, { variant: "beta", textColor: "neutral600" }, formatMessage((0, translations_1.getTrad)('empty')))),
225
- react_1.default.createElement(Button_1.Button, { variant: 'secondary', startIcon: react_1.default.createElement(Plus_1.default, null), label: formatMessage((0, translations_1.getTrad)('empty.cta')), onClick: addNewNavigationItem }, formatMessage((0, translations_1.getTrad)('empty.cta'))),
226
- config.i18nEnabled && availableLocale.length ? (react_1.default.createElement(Flex_1.Flex, { direction: "column", justifyContent: "center" },
241
+ canUpdate && (react_1.default.createElement(Button_1.Button, { variant: 'secondary', startIcon: react_1.default.createElement(Plus_1.default, null), label: formatMessage((0, translations_1.getTrad)('empty.cta')), onClick: addNewNavigationItem }, formatMessage((0, translations_1.getTrad)('empty.cta')))),
242
+ canUpdate && config.i18nEnabled && availableLocale.length ? (react_1.default.createElement(Flex_1.Flex, { direction: "column", justifyContent: "center" },
227
243
  react_1.default.createElement(Box_1.Box, { paddingTop: 3, paddingBottom: 3 },
228
244
  react_1.default.createElement(Typography_1.Typography, { variant: "beta", textColor: "neutral600" }, formatMessage((0, translations_1.getTrad)('view.i18n.fill.cta')))),
229
245
  react_1.default.createElement(Flex_1.Flex, { direction: "row", justifyContent: "center", alignItems: "center" },
@@ -232,9 +248,13 @@ const View = () => {
232
248
  react_1.default.createElement(Box_1.Box, { paddingLeft: 1, paddingRight: 1 },
233
249
  react_1.default.createElement(Button_1.Button, { variant: "tertiary", onClick: openI18nCopyModalOpened, disabled: !i18nCopySourceLocale, size: "S" }, formatMessage((0, translations_1.getTrad)('view.i18n.fill.cta.button'))))))) : null)),
234
250
  !(0, lodash_1.isEmpty)(changedActiveNavigation.items || [])
235
- && react_1.default.createElement(NavigationItemList_1.default, { items: isSearchEmpty ? changedActiveNavigation.items || [] : filteredList, onItemLevelAdd: addNewNavigationItem, onItemRemove: handleItemRemove, onItemEdit: handleItemEdit, onItemRestore: handleItemRestore, onItemReOrder: handleItemReOrder, onItemToggleCollapse: handleItemToggleCollapse, displayFlat: !isSearchEmpty, root: true, error: error, allowedLevels: config.allowedLevels, contentTypes: config.contentTypes, isParentAttachedToMenu: true, contentTypesNameFields: config.contentTypesNameFields })))),
236
- navigationItemPopupOpened && react_1.default.createElement(NavigationItemPopup_1.default, { availableLocale: availableLocale, isLoading: isLoadingForAdditionalDataToBeSet, data: activeNavigationItem, config: config, usedContentTypesData: usedContentTypesData, usedContentTypeItems: usedContentTypeItems, getContentTypeItems: getContentTypeItems, onSubmit: handleSubmitNavigationItem, onClose: onPopUpClose, locale: activeNavigation.localeCode, readNavigationItemFromLocale: readNavigationItemFromLocale }),
237
- i18nCopyItemsModal));
251
+ && react_1.default.createElement(NavigationItemList_1.default, { items: isSearchEmpty ? changedActiveNavigation.items || [] : filteredList, onItemLevelAdd: addNewNavigationItem, onItemRemove: handleItemRemove, onItemEdit: handleItemEdit, onItemRestore: handleItemRestore, onItemReOrder: handleItemReOrder, onItemToggleCollapse: handleItemToggleCollapse, displayFlat: !isSearchEmpty, root: true, error: error, allowedLevels: config.allowedLevels, contentTypes: config.contentTypes, isParentAttachedToMenu: true, contentTypesNameFields: config.contentTypesNameFields, permissions: {
252
+ canAccess, canUpdate
253
+ } })))),
254
+ navigationItemPopupOpened && react_1.default.createElement(NavigationItemPopup_1.default, { availableLocale: availableLocale, isLoading: isLoadingForAdditionalDataToBeSet, data: activeNavigationItem, config: config, usedContentTypesData: usedContentTypesData, usedContentTypeItems: usedContentTypeItems, getContentTypeItems: getContentTypeItems, onSubmit: handleSubmitNavigationItem, onClose: onPopUpClose, locale: activeNavigation.localeCode, readNavigationItemFromLocale: readNavigationItemFromLocale, slugify: slugify, permissions: {
255
+ canAccess, canUpdate
256
+ } }),
257
+ canUpdate && i18nCopyItemsModal));
238
258
  };
239
259
  exports.default = (0, react_1.memo)(View);
240
260
  //# sourceMappingURL=index.js.map
@@ -8,5 +8,9 @@ declare namespace pluginPermissions {
8
8
  action: string;
9
9
  subject: null;
10
10
  }[];
11
+ const settings: {
12
+ action: string;
13
+ subject: null;
14
+ }[];
11
15
  }
12
16
  //# sourceMappingURL=permissions.d.ts.map
@@ -7,6 +7,7 @@ const permissions_1 = __importDefault(require("../../permissions"));
7
7
  const pluginPermissions = {
8
8
  access: [{ action: permissions_1.default.render(permissions_1.default.navigation.read), subject: null }],
9
9
  update: [{ action: permissions_1.default.render(permissions_1.default.navigation.update), subject: null }],
10
+ settings: [{ action: permissions_1.default.render(permissions_1.default.navigation.settings), subject: null }],
10
11
  };
11
12
  exports.default = pluginPermissions;
12
13
  //# sourceMappingURL=permissions.js.map
@@ -10,6 +10,7 @@
10
10
  "submit.cta.save": "Save",
11
11
  "empty": "Your navigation is empty",
12
12
  "empty.cta": "Create first item",
13
+ "popup.item.header.view": "View navigation item",
13
14
  "popup.item.header.edit": "Edit navigation item",
14
15
  "popup.item.header.new": "New navigation item",
15
16
  "popup.item.form.title.label": "Title",
@@ -83,6 +84,8 @@
83
84
  "notification.error": "Error while processing request.",
84
85
  "notification.error.customField.type": "Unsupported type of custom field",
85
86
  "notification.error.item.relation": "Relations provided in some items are incorrect",
87
+ "page.auth.noAccess": "No access",
88
+ "page.auth.not.allowed": "Oops! It seems like You do not have access to this page...",
86
89
  "pages.main.search.placeholder": "Type to start searching...",
87
90
  "pages.main.header.localization.select.placeholder": "Select locale",
88
91
  "pages.settings.general.title": "General settings",
@@ -185,6 +188,10 @@
185
188
  "pages.settings.form.customFields.popup.options.description": "Enabling this field will not change already exiting navigation items",
186
189
  "pages.settings.form.customFields.popup.multi.label": "Enable multiple options input",
187
190
  "components.navigationItem.action.newItem": "Add nested item",
191
+ "components.navigationItem.action.edit": "Edit",
192
+ "components.navigationItem.action.view": "View",
193
+ "components.navigationItem.action.restore": "Restore",
194
+ "components.navigationItem.action.remove": "Remove",
188
195
  "components.navigationItem.badge.removed": "Removed",
189
196
  "components.navigationItem.badge.draft": "Draft",
190
197
  "components.navigationItem.badge.published": "Published",
@@ -192,6 +199,7 @@
192
199
  "components.confirmation.dialog.button.confirm": "Confirm",
193
200
  "components.confirmation.dialog.description": "Do you want to continue?",
194
201
  "components.confirmation.dialog.header": "Confirmation",
202
+ "components.notAccessPage.back": "Back to homepage",
195
203
  "view.i18n.fill.cta": "or bootstrap",
196
204
  "view.i18n.fill.option": "{locale} locale",
197
205
  "view.i18n.fill.cta.button": "copy"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-navigation",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "description": "Strapi - Navigation plugin",
5
5
  "strapi": {
6
6
  "name": "navigation",
@@ -15,7 +15,7 @@
15
15
  "scripts": {},
16
16
  "dependencies": {
17
17
  "@sindresorhus/slugify": "1.1.0",
18
- "@strapi/utils": "^4.5.5",
18
+ "@strapi/utils": "^4.9.0",
19
19
  "lodash": "^4.17.11",
20
20
  "pluralize": "^8.0.0",
21
21
  "react": "^16.9.0",
package/permissions.d.ts CHANGED
@@ -3,6 +3,7 @@ declare const _default: {
3
3
  navigation: {
4
4
  read: string;
5
5
  update: string;
6
+ settings: string;
6
7
  };
7
8
  };
8
9
  export default _default;
package/permissions.js CHANGED
@@ -7,6 +7,7 @@ exports.default = {
7
7
  navigation: {
8
8
  read: 'read',
9
9
  update: 'update',
10
+ settings: 'settings',
10
11
  },
11
12
  };
12
13
  //# sourceMappingURL=permissions.js.map
@@ -28,6 +28,12 @@ const setupPermissions = async ({ strapi }) => {
28
28
  uid: permissions_1.default.navigation.update,
29
29
  pluginName: "navigation",
30
30
  },
31
+ {
32
+ section: "plugins",
33
+ displayName: "Settings",
34
+ uid: permissions_1.default.navigation.settings,
35
+ pluginName: "navigation",
36
+ },
31
37
  ];
32
38
  await strapi.admin.services.permission.actionProvider.registerMany(actions);
33
39
  };
@@ -28,7 +28,6 @@ const config = {
28
28
  i18nEnabled: false,
29
29
  pathDefaultFields: {},
30
30
  pruneObsoleteI18nNavigations: false,
31
- slugify: {},
32
31
  cascadeMenuAttached: true,
33
32
  },
34
33
  };
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.configSetupStrategy = void 0;
4
- const lodash_1 = require("lodash");
5
4
  const types_1 = require("../../types");
6
5
  const utils_1 = require("../utils");
7
6
  const configSetupStrategy = async ({ strapi }) => {
@@ -23,7 +22,6 @@ const configSetupStrategy = async ({ strapi }) => {
23
22
  allowedLevels: getWithFallback("allowedLevels"),
24
23
  gql: getWithFallback("gql"),
25
24
  i18nEnabled: hasI18nPlugin && getWithFallback("i18nEnabled"),
26
- slugify: (0, lodash_1.pick)(getWithFallback("slugify"), validSlugifyFields),
27
25
  pruneObsoleteI18nNavigations: false,
28
26
  pathDefaultFields: getWithFallback("pathDefaultFields"),
29
27
  cascadeMenuAttached: getWithFallback("cascadeMenuAttached"),
@@ -41,11 +39,4 @@ const getWithFallbackFactory = (config, fallback) => (key) => {
41
39
  (0, types_1.assertNotEmpty)(value, new Error(`[Navigation] Config "${key}" is undefined`));
42
40
  return value;
43
41
  };
44
- const validSlugifyFields = [
45
- "separator",
46
- "lowercase",
47
- "decamelize",
48
- "customReplacements",
49
- "preserveLeadingUnderscore",
50
- ];
51
42
  //# sourceMappingURL=setupStrategy.js.map
@@ -67,6 +67,7 @@ declare const _default: {
67
67
  relation: string;
68
68
  target: string;
69
69
  configurable: boolean;
70
+ mappedBy: string;
70
71
  };
71
72
  localizations: {
72
73
  type: string;
@@ -167,6 +168,7 @@ declare const _default: {
167
168
  relation: string;
168
169
  target: string;
169
170
  configurable: boolean;
171
+ inversedBy: string;
170
172
  };
171
173
  audience: {
172
174
  type: string;
@@ -41,6 +41,7 @@ declare const _default: {
41
41
  relation: string;
42
42
  target: string;
43
43
  configurable: boolean;
44
+ mappedBy: string;
44
45
  };
45
46
  localizations: {
46
47
  type: string;
@@ -40,6 +40,7 @@ declare const _default: {
40
40
  relation: string;
41
41
  target: string;
42
42
  configurable: boolean;
43
+ mappedBy: string;
43
44
  };
44
45
  localizations: {
45
46
  type: string;
@@ -41,7 +41,8 @@ exports.default = {
41
41
  type: "relation",
42
42
  relation: "oneToMany",
43
43
  target: "plugin::navigation.navigation-item",
44
- configurable: false
44
+ configurable: false,
45
+ mappedBy: "master"
45
46
  },
46
47
  localizations: {
47
48
  type: "relation",
@@ -86,6 +86,7 @@ declare const _default: {
86
86
  relation: string;
87
87
  target: string;
88
88
  configurable: boolean;
89
+ inversedBy: string;
89
90
  };
90
91
  audience: {
91
92
  type: string;
@@ -85,6 +85,7 @@ declare const _default: {
85
85
  relation: string;
86
86
  target: string;
87
87
  configurable: boolean;
88
+ inversedBy: string;
88
89
  };
89
90
  audience: {
90
91
  type: string;
@@ -90,7 +90,8 @@ exports.default = {
90
90
  type: "relation",
91
91
  relation: "manyToOne",
92
92
  target: "plugin::navigation.navigation",
93
- configurable: false
93
+ configurable: false,
94
+ inversedBy: "items",
94
95
  },
95
96
  audience: {
96
97
  type: "relation",
@@ -121,6 +121,19 @@ const adminControllers = {
121
121
  throw error;
122
122
  }
123
123
  },
124
+ getSlug(ctx) {
125
+ const { query: { q } } = ctx;
126
+ try {
127
+ (0, types_1.assertNotEmpty)(q);
128
+ return this.getService("common").getSlug(q).then((slug) => ({ slug }));
129
+ }
130
+ catch (error) {
131
+ if (error instanceof Error) {
132
+ return ctx.badRequest(error.message);
133
+ }
134
+ throw error;
135
+ }
136
+ },
124
137
  };
125
138
  const assertCopyParams = (source, target) => {
126
139
  (0, types_1.assertIsNumber)(source, new InvalidParamNavigationError_1.InvalidParamNavigationError("Source's id is not a number"));
package/server/index.d.ts CHANGED
@@ -70,6 +70,7 @@ declare const _default: {
70
70
  relation: string;
71
71
  target: string;
72
72
  configurable: boolean;
73
+ mappedBy: string;
73
74
  };
74
75
  localizations: {
75
76
  type: string;
@@ -170,6 +171,7 @@ declare const _default: {
170
171
  relation: string;
171
172
  target: string;
172
173
  configurable: boolean;
174
+ inversedBy: string;
173
175
  };
174
176
  audience: {
175
177
  type: string;
@@ -28,6 +28,16 @@ const routes = {
28
28
  path: '/config',
29
29
  handler: 'admin.restoreConfig',
30
30
  },
31
+ {
32
+ method: 'GET',
33
+ path: '/slug',
34
+ handler: 'admin.getSlug',
35
+ config: {
36
+ policies: [
37
+ 'admin::isAuthenticatedAdmin'
38
+ ]
39
+ }
40
+ },
31
41
  {
32
42
  method: 'GET',
33
43
  path: '/:id',