strapi-plugin-navigation 2.1.0-beta.2 → 2.1.0

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 (56) hide show
  1. package/README.md +3 -3
  2. package/admin/src/components/Item/index.js +4 -3
  3. package/admin/src/hooks/useI18nCopyNavigationItemsModal.d.ts +9 -0
  4. package/admin/src/hooks/useI18nCopyNavigationItemsModal.js +51 -0
  5. package/admin/src/hooks/useNavigationManager.d.ts +7 -0
  6. package/admin/src/hooks/useNavigationManager.js +41 -0
  7. package/admin/src/pages/DataManagerProvider/index.js +41 -3
  8. package/admin/src/pages/View/components/I18nCopyNavigationItems/index.d.ts +3 -9
  9. package/admin/src/pages/View/components/I18nCopyNavigationItems/index.js +2 -47
  10. package/admin/src/pages/View/components/NavigationHeader/index.js +11 -6
  11. package/admin/src/pages/View/components/NavigationItemForm/index.d.ts +7 -1
  12. package/admin/src/pages/View/components/NavigationItemForm/index.js +96 -14
  13. package/admin/src/pages/View/components/NavigationItemForm/utils/form.js +7 -4
  14. package/admin/src/pages/View/components/NavigationItemPopup/index.d.ts +4 -1
  15. package/admin/src/pages/View/components/NavigationItemPopup/index.js +31 -5
  16. package/admin/src/pages/View/components/NavigationManager/Create/index.d.ts +9 -0
  17. package/admin/src/pages/View/components/NavigationManager/Create/index.js +57 -0
  18. package/admin/src/pages/View/components/NavigationManager/Delete/index.d.ts +8 -0
  19. package/admin/src/pages/View/components/NavigationManager/Delete/index.js +28 -0
  20. package/admin/src/pages/View/components/NavigationManager/Edit/index.d.ts +8 -0
  21. package/admin/src/pages/View/components/NavigationManager/Edit/index.js +54 -0
  22. package/admin/src/pages/View/components/NavigationManager/Error/index.d.ts +8 -0
  23. package/admin/src/pages/View/components/NavigationManager/Error/index.js +53 -0
  24. package/admin/src/pages/View/components/NavigationManager/Form/index.d.ts +19 -0
  25. package/admin/src/pages/View/components/NavigationManager/Form/index.js +98 -0
  26. package/admin/src/pages/View/components/NavigationManager/List/index.d.ts +8 -0
  27. package/admin/src/pages/View/components/NavigationManager/List/index.js +138 -0
  28. package/admin/src/pages/View/components/NavigationManager/index.d.ts +10 -0
  29. package/admin/src/pages/View/components/NavigationManager/index.js +189 -0
  30. package/admin/src/pages/View/components/NavigationManager/types.d.ts +53 -0
  31. package/admin/src/pages/View/components/NavigationManager/types.js +3 -0
  32. package/admin/src/pages/View/index.js +11 -9
  33. package/admin/src/pages/View/utils/parsers.d.ts +6 -1
  34. package/admin/src/pages/View/utils/parsers.js +10 -2
  35. package/admin/src/translations/en.json +31 -0
  36. package/admin/src/utils/index.d.ts +13 -0
  37. package/admin/src/utils/index.js +16 -1
  38. package/package.json +4 -5
  39. package/server/content-types/navigation/schema.js +1 -1
  40. package/server/controllers/admin.js +67 -14
  41. package/server/controllers/client.js +41 -18
  42. package/server/i18n/serviceEnhancers.d.ts +2 -1
  43. package/server/i18n/serviceEnhancers.js +45 -6
  44. package/server/i18n/types.d.ts +6 -0
  45. package/server/navigation/setupStrategy.js +3 -1
  46. package/server/routes/admin.js +10 -0
  47. package/server/services/admin.js +86 -31
  48. package/server/services/common.js +1 -1
  49. package/server/utils/functions.d.ts +1 -0
  50. package/server/utils/functions.js +3 -1
  51. package/tsconfig.tsbuildinfo +1 -1
  52. package/types/controllers.d.ts +21 -8
  53. package/types/services.d.ts +7 -1
  54. package/types/utils.d.ts +4 -0
  55. package/types/utils.js +4 -3
  56. package/utils/InvalidParamNavigationError.js +0 -3
package/README.md CHANGED
@@ -102,10 +102,10 @@ yarn develop --watch-admin
102
102
 
103
103
  The **UI Navigation** plugin should appear in the **Plugins** section of Strapi sidebar after you run app again.
104
104
 
105
- You can manage your multiple navigation containers by going to the **Navigation** section of the **Content Manager**.
105
+ You can manage your multiple navigation containers by going to the **Navigation** manage view by clicking "Manage" button.
106
106
 
107
107
  <div style="margin: 20px 0" align="center">
108
- <img style="width: 100%; height: auto;" src="public/assets/content-manager.png" alt="Strapi In-App Marketplace" />
108
+ <img style="width: 100%; height: auto;" src="public/assets/manager-view.png" alt="Navigation Manager View" />
109
109
  </div>
110
110
 
111
111
  As a next step you must configure your the plugin by the way you want to. See [**Configuration**](#🔧-configuration) section.
@@ -118,7 +118,7 @@ Complete installation requirements are exact same as for Strapi itself and can b
118
118
 
119
119
  **Supported Strapi versions**:
120
120
 
121
- - Strapi v4.1.11 (recently tested)
121
+ - Strapi v4.2.0 (recently tested)
122
122
  - Strapi v4.x
123
123
 
124
124
  > 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).
@@ -48,8 +48,8 @@ const utils_1 = require("../../utils");
48
48
  const CollapseButton_1 = __importDefault(require("../CollapseButton"));
49
49
  const Item = (props) => {
50
50
  const { item, isLast = false, level = 0, levelPath = '', allowedLevels, relatedRef, isParentAttachedToMenu, onItemLevelAdd, onItemRemove, onItemRestore, onItemEdit, onItemReOrder, onItemToggleCollapse, error, displayChildren, config = {}, } = props;
51
- const { viewId, title, type, path, removed, externalPath, menuAttached, collapsed, } = item;
52
- const { contentTypes, contentTypesNameFields } = config;
51
+ const { viewId, title, type, path, removed, externalPath, menuAttached, collapsed, structureId, items = [], } = item;
52
+ const { contentTypes = [], contentTypesNameFields } = config;
53
53
  const isExternal = type === enums_1.navigationItemType.EXTERNAL;
54
54
  const isWrapper = type === enums_1.navigationItemType.WRAPPER;
55
55
  const isHandledByPublishFlow = contentTypes.find(_ => _.uid === relatedRef?.__collectionUid)?.draftAndPublish;
@@ -111,6 +111,7 @@ const Item = (props) => {
111
111
  const { isSingle } = contentType;
112
112
  return `/content-manager/${isSingle ? 'singleType' : 'collectionType'}/${entity?.__collectionUid}${!isSingle ? '/' + entity?.id : ''}`;
113
113
  };
114
+ const onNewItemClick = (0, react_1.useCallback)((event) => onItemLevelAdd(event, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, `${structureId}.${items.length}`), [viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, structureId, items]);
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)),
@@ -126,7 +127,7 @@ const Item = (props) => {
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: (e) => onItemLevelAdd(e, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached) },
130
+ react_1.default.createElement(TextButton_1.TextButton, { disabled: removed, startIcon: react_1.default.createElement(icons_1.Plus, null), onClick: onNewItemClick },
130
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'}` }))),
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { ConfirmEffect } from "../pages/View/components/I18nCopyNavigationItems";
3
+ export declare const useI18nCopyNavigationItemsModal: (onConfirm: ConfirmEffect) => {
4
+ setI18nCopyModalOpened: React.Dispatch<React.SetStateAction<boolean>>;
5
+ setI18nCopySourceLocale: React.Dispatch<React.SetStateAction<string | undefined>>;
6
+ i18nCopyItemsModal: JSX.Element | null;
7
+ i18nCopySourceLocale: string | undefined;
8
+ };
9
+ //# sourceMappingURL=useI18nCopyNavigationItemsModal.d.ts.map
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.useI18nCopyNavigationItemsModal = void 0;
27
+ const react_1 = __importStar(require("react"));
28
+ const I18nCopyNavigationItems_1 = require("../pages/View/components/I18nCopyNavigationItems");
29
+ const useI18nCopyNavigationItemsModal = (onConfirm) => {
30
+ const [isOpened, setIsOpened] = (0, react_1.useState)(false);
31
+ const [sourceLocale, setSourceLocale] = (0, react_1.useState)(undefined);
32
+ const onCancel = (0, react_1.useCallback)(() => {
33
+ setIsOpened(false);
34
+ }, [setIsOpened]);
35
+ const onConfirmWithModalClose = (0, react_1.useCallback)(() => {
36
+ if (!sourceLocale) {
37
+ return;
38
+ }
39
+ onConfirm(sourceLocale);
40
+ setIsOpened(false);
41
+ }, [onConfirm, sourceLocale]);
42
+ const modal = (0, react_1.useMemo)(() => isOpened ? (react_1.default.createElement(I18nCopyNavigationItems_1.I18nCopyNavigationItemsModal, { onConfirm: onConfirmWithModalClose, onCancel: onCancel })) : null, [isOpened, onConfirmWithModalClose, onCancel]);
43
+ return (0, react_1.useMemo)(() => ({
44
+ setI18nCopyModalOpened: setIsOpened,
45
+ setI18nCopySourceLocale: setSourceLocale,
46
+ i18nCopyItemsModal: modal,
47
+ i18nCopySourceLocale: sourceLocale,
48
+ }), [setSourceLocale, setIsOpened, modal, sourceLocale]);
49
+ };
50
+ exports.useI18nCopyNavigationItemsModal = useI18nCopyNavigationItemsModal;
51
+ //# sourceMappingURL=useI18nCopyNavigationItemsModal.js.map
@@ -0,0 +1,7 @@
1
+ /// <reference types="react" />
2
+ export declare const useNavigationManager: () => {
3
+ navigationManagerModal: JSX.Element | null;
4
+ openNavigationManagerModal: () => void;
5
+ closeNavigationManagerModal: () => void;
6
+ };
7
+ //# sourceMappingURL=useNavigationManager.d.ts.map
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.useNavigationManager = void 0;
27
+ const react_1 = __importStar(require("react"));
28
+ const NavigationManager_1 = require("../pages/View/components/NavigationManager");
29
+ const useNavigationManager = () => {
30
+ const [isOpened, setIsOpened] = (0, react_1.useState)(false);
31
+ const open = (0, react_1.useCallback)(() => setIsOpened(true), [setIsOpened]);
32
+ const close = (0, react_1.useCallback)(() => setIsOpened(false), [setIsOpened]);
33
+ const modal = (0, react_1.useMemo)(() => isOpened ? (react_1.default.createElement(NavigationManager_1.NavigationManager, { initialState: { view: "INITIAL" }, isOpened: true, onClose: close })) : null, [isOpened, close]);
34
+ return (0, react_1.useMemo)(() => ({
35
+ navigationManagerModal: modal,
36
+ openNavigationManagerModal: open,
37
+ closeNavigationManagerModal: close,
38
+ }), [modal, open, close]);
39
+ };
40
+ exports.useNavigationManager = useNavigationManager;
41
+ //# sourceMappingURL=useNavigationManager.js.map
@@ -39,6 +39,7 @@ const init_1 = __importDefault(require("./init"));
39
39
  const reducer_1 = __importStar(require("./reducer"));
40
40
  const actions_1 = require("./actions");
41
41
  const parsers_1 = require("../View/utils/parsers");
42
+ const utils_1 = require("../../utils");
42
43
  const i18nAwareItems = ({ items, config }) => config.i18nEnabled ? items.filter(({ localeCode }) => localeCode === config.defaultLocale) : items;
43
44
  const DataManagerProvider = ({ children }) => {
44
45
  const [reducerState, dispatch] = (0, react_1.useReducer)(reducer_1.default, reducer_1.initialState, init_1.default);
@@ -60,7 +61,7 @@ const DataManagerProvider = ({ children }) => {
60
61
  const passedActiveItems = (0, react_1.useMemo)(() => {
61
62
  return i18nAwareItems({ config, items });
62
63
  }, [config, items]);
63
- const getNavigation = async (id, cfg) => {
64
+ const getNavigation = async (id, navigationConfig) => {
64
65
  try {
65
66
  if (activeId || id) {
66
67
  dispatch({
@@ -74,7 +75,10 @@ const DataManagerProvider = ({ children }) => {
74
75
  type: actions_1.GET_NAVIGATION_DATA_SUCCEEDED,
75
76
  activeItem: {
76
77
  ...activeItem,
77
- items: (0, parsers_1.prepareItemToViewPayload)(activeItem.items, null, cfg),
78
+ items: (0, parsers_1.prepareItemToViewPayload)({
79
+ config: navigationConfig,
80
+ items: activeItem.items,
81
+ }),
78
82
  },
79
83
  });
80
84
  }
@@ -186,6 +190,26 @@ const DataManagerProvider = ({ children }) => {
186
190
  });
187
191
  handleChangeSelection(targetId);
188
192
  };
193
+ const readNavigationItemFromLocale = async ({ locale, structureId }) => {
194
+ try {
195
+ const source = changedActiveItem.localizations?.find((navigation) => navigation.locale === locale);
196
+ if (!source) {
197
+ return (0, utils_1.errorStatusResourceFor)(['popup.item.form.i18n.locale.error.unavailable']);
198
+ }
199
+ const url = `/navigation/i18n/item/read/${source.id}/${changedActiveItem.id}?path=${structureId}`;
200
+ return (0, utils_1.resolvedResourceFor)(await (0, helper_plugin_1.request)(url, {
201
+ method: "GET",
202
+ signal,
203
+ }));
204
+ }
205
+ catch (error) {
206
+ let messageKey;
207
+ if (error instanceof Error) {
208
+ messageKey = (0, lodash_1.get)(error, 'response.payload.error.details.messageKey');
209
+ }
210
+ return (0, utils_1.errorStatusResourceFor)([messageKey ?? 'popup.item.form.i18n.locale.error.generic']);
211
+ }
212
+ };
189
213
  const handleChangeNavigationPopupVisibility = (visible) => {
190
214
  dispatch({
191
215
  type: actions_1.CHANGE_NAVIGATION_POPUP_VISIBILITY,
@@ -227,7 +251,10 @@ const DataManagerProvider = ({ children }) => {
227
251
  type: actions_1.SUBMIT_NAVIGATION_SUCCEEDED,
228
252
  navigation: {
229
253
  ...navigation,
230
- items: (0, parsers_1.prepareItemToViewPayload)(navigation.items, null, config),
254
+ items: (0, parsers_1.prepareItemToViewPayload)({
255
+ config,
256
+ items: navigation.items,
257
+ }),
231
258
  },
232
259
  });
233
260
  toggleNotification({
@@ -255,6 +282,14 @@ const DataManagerProvider = ({ children }) => {
255
282
  });
256
283
  }
257
284
  };
285
+ const handleNavigationsDeletion = async (ids) => Promise.all(ids.map((id) => handleNavigationDeletion(id)));
286
+ const handleNavigationDeletion = (id) => (0, helper_plugin_1.request)(`/${pluginId_1.default}/${id}`, {
287
+ method: "DELETE",
288
+ signal,
289
+ });
290
+ const hardReset = () => {
291
+ return getDataRef.current();
292
+ };
258
293
  return (react_1.default.createElement(DataManagerContext_1.default.Provider, { value: {
259
294
  items: passedActiveItems,
260
295
  activeItem,
@@ -280,6 +315,9 @@ const DataManagerProvider = ({ children }) => {
280
315
  isInDevelopmentMode,
281
316
  error,
282
317
  availableLocale,
318
+ readNavigationItemFromLocale,
319
+ handleNavigationsDeletion,
320
+ hardReset,
283
321
  } }, isLoading ? react_1.default.createElement(helper_plugin_1.LoadingIndicatorPage, null) : children));
284
322
  };
285
323
  DataManagerProvider.propTypes = {
@@ -1,8 +1,8 @@
1
- import React, { VFC } from "react";
2
- interface ConfirmEffect {
1
+ import { VFC } from "react";
2
+ export interface ConfirmEffect {
3
3
  (source: string): void;
4
4
  }
5
- interface CancelEffect {
5
+ export interface CancelEffect {
6
6
  (): void;
7
7
  }
8
8
  interface Props {
@@ -10,11 +10,5 @@ interface Props {
10
10
  onCancel: CancelEffect;
11
11
  }
12
12
  export declare const I18nCopyNavigationItemsModal: VFC<Props>;
13
- export declare const useI18nCopyNavigationItemsModal: (onConfirm: ConfirmEffect) => {
14
- setI18nCopyModalOpened: React.Dispatch<React.SetStateAction<boolean>>;
15
- setI18nCopySourceLocale: React.Dispatch<React.SetStateAction<string | undefined>>;
16
- i18nCopyItemsModal: JSX.Element | null;
17
- i18nCopySourceLocale: string | undefined;
18
- };
19
13
  export {};
20
14
  //# sourceMappingURL=index.d.ts.map
@@ -1,33 +1,10 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
4
  };
28
5
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.useI18nCopyNavigationItemsModal = exports.I18nCopyNavigationItemsModal = void 0;
30
- const react_1 = __importStar(require("react"));
6
+ exports.I18nCopyNavigationItemsModal = void 0;
7
+ const react_1 = __importDefault(require("react"));
31
8
  const ConfirmationDialog_1 = __importDefault(require("../../../../components/ConfirmationDialog"));
32
9
  const utils_1 = require("../../../../utils");
33
10
  const refreshIcon = react_1.default.createElement(react_1.default.Fragment, null);
@@ -35,26 +12,4 @@ const I18nCopyNavigationItemsModal = ({ onConfirm, onCancel, }) => {
35
12
  return (react_1.default.createElement(ConfirmationDialog_1.default, { isVisible: true, header: (0, utils_1.getMessage)("pages.view.actions.i18nCopyItems.confirmation.header"), labelConfirm: (0, utils_1.getMessage)("pages.view.actions.i18nCopyItems.confirmation.confirm"), iconConfirm: refreshIcon, mainIcon: refreshIcon, onConfirm: onConfirm, onCancel: onCancel }, (0, utils_1.getMessage)("pages.view.actions.i18nCopyItems.confirmation.content")));
36
13
  };
37
14
  exports.I18nCopyNavigationItemsModal = I18nCopyNavigationItemsModal;
38
- const useI18nCopyNavigationItemsModal = (onConfirm) => {
39
- const [isOpened, setIsOpened] = (0, react_1.useState)(false);
40
- const [sourceLocale, setSourceLocale] = (0, react_1.useState)(undefined);
41
- const onCancel = (0, react_1.useCallback)(() => {
42
- setIsOpened(false);
43
- }, [setIsOpened]);
44
- const onConfirmWithModalClose = (0, react_1.useCallback)(() => {
45
- if (!sourceLocale) {
46
- return;
47
- }
48
- onConfirm(sourceLocale);
49
- setIsOpened(false);
50
- }, [onConfirm, sourceLocale]);
51
- const modal = (0, react_1.useMemo)(() => isOpened ? (react_1.default.createElement(exports.I18nCopyNavigationItemsModal, { onConfirm: onConfirmWithModalClose, onCancel: onCancel })) : null, [isOpened, onConfirmWithModalClose, onCancel]);
52
- return (0, react_1.useMemo)(() => ({
53
- setI18nCopyModalOpened: setIsOpened,
54
- setI18nCopySourceLocale: setSourceLocale,
55
- i18nCopyItemsModal: modal,
56
- i18nCopySourceLocale: sourceLocale,
57
- }), [setSourceLocale, setIsOpened, modal, sourceLocale]);
58
- };
59
- exports.useI18nCopyNavigationItemsModal = useI18nCopyNavigationItemsModal;
60
15
  //# sourceMappingURL=index.js.map
@@ -37,6 +37,7 @@ const Select_1 = require("@strapi/design-system/Select");
37
37
  const Box_1 = require("@strapi/design-system/Box");
38
38
  const Grid_1 = require("@strapi/design-system/Grid");
39
39
  const lodash_1 = require("lodash");
40
+ const useNavigationManager_1 = require("../../../../hooks/useNavigationManager");
40
41
  const submitIcon = react_1.default.createElement(Check_1.default, null);
41
42
  const pickDefaultLocaleNavigation = ({ activeNavigation, config }) => config.i18nEnabled
42
43
  ? activeNavigation
@@ -52,18 +53,22 @@ const NavigationHeader = ({ activeNavigation, availableNavigations, structureHas
52
53
  : [], [activeNavigation, config]);
53
54
  const hasLocalizations = config.i18nEnabled && allLocaleVersions.length;
54
55
  const passedActiveNavigation = pickDefaultLocaleNavigation({ activeNavigation, config });
56
+ const { closeNavigationManagerModal, openNavigationManagerModal, navigationManagerModal } = (0, useNavigationManager_1.useNavigationManager)();
55
57
  return (react_1.default.createElement(Layout_1.HeaderLayout, { primaryAction: react_1.default.createElement(Stack_1.Stack, { horizontal: true, size: 2 },
56
- react_1.default.createElement(Box_1.Box, { width: "20vw", marginRight: "8px" },
58
+ react_1.default.createElement(Box_1.Box, { width: "27vw", marginRight: "8px" },
57
59
  react_1.default.createElement(Grid_1.Grid, { gap: 4 },
58
- !hasLocalizations ? (react_1.default.createElement(Grid_1.GridItem, { col: 3 })) : null,
59
- react_1.default.createElement(Grid_1.GridItem, { col: 6 },
60
+ !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 },
60
64
  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)))),
61
65
  hasLocalizations
62
- ? react_1.default.createElement(Grid_1.GridItem, { col: 3 },
66
+ ? react_1.default.createElement(Grid_1.GridItem, { col: 2 },
63
67
  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))))
64
68
  : null,
65
- react_1.default.createElement(Grid_1.GridItem, { col: "3" },
66
- react_1.default.createElement(Button_1.Button, { onClick: handleSave, startIcon: submitIcon, disabled: structureHasErrors || !structureHasChanged, type: "submit" }, formatMessage((0, translations_1.getTrad)('submit.cta.save'))))))), title: formatMessage({
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({
67
72
  id: (0, translations_1.getTrad)('header.title'),
68
73
  defaultMessage: 'UI Navigation',
69
74
  }), subtitle: formatMessage({
@@ -1,5 +1,7 @@
1
1
  export default NavigationItemForm;
2
- declare function NavigationItemForm({ isLoading, inputsPrefix, data, contentTypes, contentTypeEntities, usedContentTypeEntities, availableAudience, additionalFields, contentTypesNameFields, onSubmit, onCancel, getContentTypeEntities, usedContentTypesData, appendLabelPublicationStatus, locale, }: {
2
+ declare function NavigationItemForm({ config, availableLocale, isLoading: isPreloading, inputsPrefix, data, contentTypes, contentTypeEntities, usedContentTypeEntities, availableAudience, additionalFields, contentTypesNameFields, onSubmit, onCancel, getContentTypeEntities, usedContentTypesData, appendLabelPublicationStatus, locale, readNavigationItemFromLocale, }: {
3
+ config: any;
4
+ availableLocale: any;
3
5
  isLoading: any;
4
6
  inputsPrefix: any;
5
7
  data?: {} | undefined;
@@ -15,6 +17,7 @@ declare function NavigationItemForm({ isLoading, inputsPrefix, data, contentType
15
17
  usedContentTypesData: any;
16
18
  appendLabelPublicationStatus?: (() => string) | undefined;
17
19
  locale: any;
20
+ readNavigationItemFromLocale: any;
18
21
  }): JSX.Element;
19
22
  declare namespace NavigationItemForm {
20
23
  namespace defaultProps {
@@ -25,6 +28,8 @@ declare namespace NavigationItemForm {
25
28
  const requestError: null;
26
29
  }
27
30
  namespace propTypes {
31
+ export const config: PropTypes.Validator<object>;
32
+ export const availableLocale: PropTypes.Requireable<(string | null | undefined)[]>;
28
33
  export const isLoading: PropTypes.Requireable<boolean>;
29
34
  const fieldsToDisable_1: PropTypes.Requireable<any[]>;
30
35
  export { fieldsToDisable_1 as fieldsToDisable };
@@ -45,6 +50,7 @@ declare namespace NavigationItemForm {
45
50
  export const getContentTypeEntities: PropTypes.Validator<(...args: any[]) => any>;
46
51
  export const appendLabelPublicationStatus: PropTypes.Requireable<(...args: any[]) => any>;
47
52
  export const onCancel: PropTypes.Requireable<(...args: any[]) => any>;
53
+ export const readNavigationItemFromLocale: PropTypes.Validator<(...args: any[]) => any>;
48
54
  }
49
55
  }
50
56
  import PropTypes from "prop-types";
@@ -35,6 +35,7 @@ const ModalLayout_1 = require("@strapi/design-system/ModalLayout");
35
35
  const Select_1 = require("@strapi/design-system/Select");
36
36
  const Grid_1 = require("@strapi/design-system/Grid");
37
37
  const helper_plugin_1 = require("@strapi/helper-plugin");
38
+ const Button_1 = require("@strapi/design-system/Button");
38
39
  const NavigationItemPopupFooter_1 = require("../NavigationItemPopup/NavigationItemPopupFooter");
39
40
  const enums_1 = require("../../utils/enums");
40
41
  const parsers_1 = require("../../utils/parsers");
@@ -42,7 +43,9 @@ const form_1 = require("./utils/form");
42
43
  const form_2 = require("../../utils/form");
43
44
  const translations_1 = require("../../../../translations");
44
45
  const utils_1 = require("../../../../utils");
45
- const NavigationItemForm = ({ isLoading, inputsPrefix, data = {}, contentTypes = [], contentTypeEntities = [], usedContentTypeEntities = [], availableAudience = [], additionalFields = [], contentTypesNameFields = {}, onSubmit, onCancel, getContentTypeEntities, usedContentTypesData, appendLabelPublicationStatus = () => '', locale, }) => {
46
+ const appendLabelPublicationStatusFallback = () => '';
47
+ const NavigationItemForm = ({ config, availableLocale, isLoading: isPreloading, inputsPrefix, data = {}, contentTypes = [], contentTypeEntities = [], usedContentTypeEntities = [], availableAudience = [], additionalFields = [], contentTypesNameFields = {}, onSubmit, onCancel, getContentTypeEntities, usedContentTypesData, appendLabelPublicationStatus = appendLabelPublicationStatusFallback, locale, readNavigationItemFromLocale, }) => {
48
+ const [isLoading, setIsLoading] = (0, react_1.useState)(isPreloading);
46
49
  const [hasBeenInitialized, setInitializedState] = (0, react_1.useState)(false);
47
50
  const [hasChanged, setChangedState] = (0, react_1.useState)(false);
48
51
  const [contentTypeSearchQuery, setContentTypeSearchQuery] = (0, react_1.useState)(undefined);
@@ -50,6 +53,17 @@ const NavigationItemForm = ({ isLoading, inputsPrefix, data = {}, contentTypes =
50
53
  const [form, setFormState] = (0, react_1.useState)({});
51
54
  const [formErrors, setFormErrorsState] = (0, react_1.useState)({});
52
55
  const { relatedType } = form;
56
+ const isI18nBootstrapAvailable = !!(config.i18nEnabled && availableLocale && availableLocale.length);
57
+ const availableLocaleOptions = (0, react_1.useMemo)(() => availableLocale.map((locale) => ({
58
+ value: locale,
59
+ label: locale,
60
+ metadatas: {
61
+ intlLabel: {
62
+ id: `i18n.locale.${locale}`,
63
+ defaultMessage: locale,
64
+ }
65
+ },
66
+ })), [availableLocale]);
53
67
  const relatedFieldName = `${inputsPrefix}related`;
54
68
  if (!hasBeenInitialized && !(0, lodash_1.isEmpty)(data)) {
55
69
  setInitializedState(true);
@@ -242,20 +256,80 @@ const NavigationItemForm = ({ isLoading, inputsPrefix, data = {}, contentTypes =
242
256
  }, [isSingleSelected, relatedSelectOptions]);
243
257
  (0, react_1.useEffect)(() => {
244
258
  const value = relatedType;
245
- const fetchContentTypeEntities = async () => {
246
- if (value) {
247
- const item = (0, lodash_1.find)(contentTypes, (_) => _.uid === value);
248
- if (item) {
249
- await getContentTypeEntities({
250
- modelUID: item.uid,
251
- query: contentTypeSearchQuery,
252
- locale,
253
- }, item.plugin);
254
- }
259
+ if (value) {
260
+ const item = (0, lodash_1.find)(contentTypes, (_) => _.uid === value);
261
+ if (item) {
262
+ getContentTypeEntities({
263
+ modelUID: item.uid,
264
+ query: contentTypeSearchQuery,
265
+ locale,
266
+ }, item.plugin);
255
267
  }
256
- };
257
- fetchContentTypeEntities();
268
+ }
258
269
  }, [relatedType, contentTypeSearchQuery]);
270
+ const resetCopyItemFormErrors = () => {
271
+ setFormErrorsState((prevState) => ({
272
+ ...prevState,
273
+ [itemLocaleCopyField]: null,
274
+ }));
275
+ };
276
+ const itemLocaleCopyField = `${inputsPrefix}i18n.locale`;
277
+ const itemLocaleCopyValue = form[itemLocaleCopyField];
278
+ const onCopyFromLocale = (0, react_1.useCallback)(async (event) => {
279
+ event.preventDefault();
280
+ event.stopPropagation();
281
+ setIsLoading(true);
282
+ resetCopyItemFormErrors();
283
+ try {
284
+ const result = await readNavigationItemFromLocale({
285
+ locale: itemLocaleCopyValue,
286
+ structureId: data.structureId
287
+ });
288
+ if (result.type === utils_1.ResourceState.RESOLVED) {
289
+ const { value: { related, ...rest } } = result;
290
+ setFormState((prevState) => ({
291
+ ...prevState,
292
+ ...rest,
293
+ }));
294
+ if (related) {
295
+ const relatedType = relatedTypeSelectOptions
296
+ .find(({ value }) => value === related.__contentType)?.value;
297
+ setFormState((prevState) => ({
298
+ ...prevState,
299
+ relatedType,
300
+ [relatedFieldName]: related.id,
301
+ }));
302
+ }
303
+ }
304
+ if (result.type === utils_1.ResourceState.ERROR) {
305
+ setFormErrorsState((prevState) => ({
306
+ ...prevState,
307
+ [itemLocaleCopyField]: (0, utils_1.getMessage)(result.errors[0]),
308
+ }));
309
+ }
310
+ }
311
+ catch (error) {
312
+ setFormErrorsState((prevState) => ({
313
+ ...prevState,
314
+ [itemLocaleCopyField]: (0, utils_1.getMessage)('popup.item.form.i18n.locale.error.generic'),
315
+ }));
316
+ }
317
+ setIsLoading(false);
318
+ }, [setIsLoading, setFormState, setFormErrorsState]);
319
+ const onChangeLocaleCopy = (0, react_1.useCallback)(({ target: { value } }) => {
320
+ resetCopyItemFormErrors();
321
+ onChange({ target: { name: itemLocaleCopyField, value } });
322
+ }, [onChange, itemLocaleCopyField]);
323
+ const itemCopyProps = (0, react_1.useMemo)(() => ({
324
+ intlLabel: {
325
+ id: (0, translations_1.getTradId)('popup.item.form.i18n.locale.label'),
326
+ defaultMessage: 'Copy details from'
327
+ },
328
+ placeholder: {
329
+ id: (0, translations_1.getTradId)('popup.item.form.i18n.locale.placeholder'),
330
+ defaultMessage: 'locale'
331
+ },
332
+ }), [translations_1.getTradId]);
259
333
  return (react_1.default.createElement(react_1.default.Fragment, null,
260
334
  react_1.default.createElement(formik_1.Formik, null,
261
335
  react_1.default.createElement(helper_plugin_1.Form, null,
@@ -321,7 +395,12 @@ const NavigationItemForm = ({ isLoading, inputsPrefix, data = {}, contentTypes =
321
395
  additionalFields.includes(enums_1.navigationItemAdditionalFields.AUDIENCE) && (react_1.default.createElement(Grid_1.GridItem, { key: `${inputsPrefix}audience`, col: 6, lg: 12 },
322
396
  react_1.default.createElement(Select_1.Select, { id: `${inputsPrefix}audience`, placeholder: (0, utils_1.getMessage)('popup.item.form.audience.placeholder'), label: (0, utils_1.getMessage)('popup.item.form.audience.label'), onChange: onAudienceChange, value: audience, hint: !isLoading && (0, lodash_1.isEmpty)(audienceOptions)
323
397
  ? (0, utils_1.getMessage)('popup.item.form.audience.empty', 'There are no more audiences')
324
- : 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))))))))),
398
+ : 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)))))),
399
+ isI18nBootstrapAvailable ? (react_1.default.createElement(Grid_1.Grid, { gap: 5, paddingTop: 5 },
400
+ react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12 },
401
+ react_1.default.createElement(helper_plugin_1.GenericInput, { ...itemCopyProps, type: "select", name: itemLocaleCopyField, error: (0, lodash_1.get)(formErrors, itemLocaleCopyField), onChange: onChangeLocaleCopy, options: availableLocaleOptions, value: itemLocaleCopyValue, disabled: isLoading })),
402
+ react_1.default.createElement(Grid_1.GridItem, { col: 6, lg: 12, paddingTop: 6 },
403
+ react_1.default.createElement(Button_1.Button, { variant: "tertiary", onClick: onCopyFromLocale, disabled: isLoading || !itemLocaleCopyValue }, (0, utils_1.getMessage)('popup.item.form.i18n.locale.button'))))) : null))),
325
404
  react_1.default.createElement(NavigationItemPopupFooter_1.NavigationItemPopupFooter, { handleSubmit: handleSubmit, handleCancel: onCancel, submitDisabled: submitDisabled })));
326
405
  };
327
406
  NavigationItemForm.defaultProps = {
@@ -332,6 +411,8 @@ NavigationItemForm.defaultProps = {
332
411
  requestError: null,
333
412
  };
334
413
  NavigationItemForm.propTypes = {
414
+ config: prop_types_1.default.object.isRequired,
415
+ availableLocale: prop_types_1.default.arrayOf(prop_types_1.default.string),
335
416
  isLoading: prop_types_1.default.bool,
336
417
  fieldsToDisable: prop_types_1.default.array,
337
418
  formErrors: prop_types_1.default.object.isRequired,
@@ -347,6 +428,7 @@ NavigationItemForm.propTypes = {
347
428
  getContentTypeEntities: prop_types_1.default.func.isRequired,
348
429
  appendLabelPublicationStatus: prop_types_1.default.func,
349
430
  onCancel: prop_types_1.default.func,
431
+ readNavigationItemFromLocale: prop_types_1.default.func.isRequired,
350
432
  };
351
433
  exports.default = NavigationItemForm;
352
434
  //# sourceMappingURL=index.js.map
@@ -32,6 +32,12 @@ const lodash_1 = require("lodash");
32
32
  const helper_plugin_1 = require("@strapi/helper-plugin");
33
33
  const enums_1 = require("../../../utils/enums");
34
34
  const pluginId_1 = __importDefault(require("../../../../../pluginId"));
35
+ const externalPathRegexps = [
36
+ /^mailto:[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/,
37
+ /^tel:(\+\d{1,3})?[\s]?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{3,4}$/,
38
+ /^#.*/,
39
+ /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/,
40
+ ];
35
41
  exports.form = {
36
42
  fieldsToDisable: [],
37
43
  fieldsToOmit: [],
@@ -57,10 +63,7 @@ exports.form = {
57
63
  is: val => val === enums_1.navigationItemType.EXTERNAL,
58
64
  then: yup.string()
59
65
  .required(helper_plugin_1.translatedErrors.required)
60
- .matches(/(#.*)|(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/, {
61
- excludeEmptyString: true,
62
- message: `${pluginId_1.default}.popup.item.form.externalPath.validation.type`,
63
- }),
66
+ .test(`${pluginId_1.default}.popup.item.form.externalPath.validation.type`, externalPath => externalPath ? externalPathRegexps.some(re => re.test(externalPath)) : true),
64
67
  otherwise: yup.string().notRequired(),
65
68
  }),
66
69
  menuAttached: yup.boolean(),
@@ -1,5 +1,6 @@
1
1
  export default NavigationItemPopUp;
2
- declare function NavigationItemPopUp({ isOpen, isLoading, data, config, onSubmit, onClose, usedContentTypeItems, getContentTypeItems, usedContentTypesData, locale, }: {
2
+ declare function NavigationItemPopUp({ availableLocale, isOpen, isLoading, data, config, onSubmit, onClose, usedContentTypeItems, getContentTypeItems, usedContentTypesData, locale, readNavigationItemFromLocale, }: {
3
+ availableLocale: any;
3
4
  isOpen: any;
4
5
  isLoading: any;
5
6
  data: any;
@@ -10,6 +11,7 @@ declare function NavigationItemPopUp({ isOpen, isLoading, data, config, onSubmit
10
11
  getContentTypeItems: any;
11
12
  usedContentTypesData: any;
12
13
  locale: any;
14
+ readNavigationItemFromLocale: any;
13
15
  }): JSX.Element;
14
16
  declare namespace NavigationItemPopUp {
15
17
  namespace propTypes {
@@ -21,6 +23,7 @@ declare namespace NavigationItemPopUp {
21
23
  const onClose: PropTypes.Validator<(...args: any[]) => any>;
22
24
  const getContentTypeItems: PropTypes.Validator<(...args: any[]) => any>;
23
25
  const locale: PropTypes.Requireable<string>;
26
+ const readNavigationItemFromLocale: PropTypes.Validator<(...args: any[]) => any>;
24
27
  }
25
28
  }
26
29
  import PropTypes from "prop-types";