strapi-plugin-navigation 2.3.1 → 2.4.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 (75) hide show
  1. package/README.md +47 -6
  2. package/admin/src/components/AdditionalFieldInput/index.d.ts +2 -2
  3. package/admin/src/components/AdditionalFieldInput/index.js +36 -8
  4. package/admin/src/components/AdditionalFieldInput/types.d.ts +1 -1
  5. package/admin/src/components/DragButton/index.d.ts +3 -1
  6. package/admin/src/components/DragButton/index.js +2 -1
  7. package/admin/src/components/Item/ItemCardHeader/index.d.ts +1 -0
  8. package/admin/src/components/Item/ItemCardHeader/index.js +10 -5
  9. package/admin/src/components/Item/index.js +24 -4
  10. package/admin/src/components/NavigationItemList/index.js +1 -1
  11. package/admin/src/components/Search/index.d.ts +10 -4
  12. package/admin/src/components/Search/index.js +45 -6
  13. package/admin/src/components/TextArrayInput/index.d.ts +2 -1
  14. package/admin/src/hooks/useAllContentTypes.d.ts +1 -1
  15. package/admin/src/hooks/useNavigationConfig.d.ts +3 -3
  16. package/admin/src/index.d.ts +3 -1
  17. package/admin/src/pages/SettingsPage/common/const.d.ts +2 -0
  18. package/admin/src/pages/SettingsPage/common/const.js +5 -0
  19. package/admin/src/pages/SettingsPage/common/index.d.ts +2 -0
  20. package/admin/src/pages/SettingsPage/common/index.js +18 -0
  21. package/admin/src/pages/SettingsPage/components/CustomFieldForm/index.js +27 -12
  22. package/admin/src/pages/SettingsPage/components/CustomFieldTable/index.js +1 -1
  23. package/admin/src/pages/SettingsPage/components/DisableI18nModal/index.d.ts +2 -3
  24. package/admin/src/pages/SettingsPage/components/DisableI18nModal/index.js +12 -14
  25. package/admin/src/pages/SettingsPage/index.js +3 -2
  26. package/admin/src/pages/SettingsPage/utils/form.js +2 -1
  27. package/admin/src/pages/View/components/NavigationItemForm/index.js +49 -19
  28. package/admin/src/pages/View/components/NavigationItemForm/types.d.ts +2 -1
  29. package/admin/src/pages/View/components/NavigationItemForm/utils/form.js +2 -0
  30. package/admin/src/pages/View/index.js +20 -10
  31. package/admin/src/pages/View/utils/form.d.ts +1 -1
  32. package/admin/src/pages/View/utils/types.d.ts +3 -0
  33. package/admin/src/pages/View/utils/types.js +3 -0
  34. package/admin/src/translations/ca.json +1 -0
  35. package/admin/src/translations/en.json +3 -0
  36. package/admin/src/translations/fr.json +1 -0
  37. package/admin/src/utils/api.d.ts +4 -4
  38. package/admin/src/utils/api.js +1 -1
  39. package/package.json +8 -3
  40. package/server/content-types/index.d.ts +2 -0
  41. package/server/content-types/navigation/index.d.ts +1 -0
  42. package/server/content-types/navigation/index.js +3 -1
  43. package/server/content-types/navigation/lifecycles.d.ts +3 -0
  44. package/server/content-types/navigation/lifecycles.js +7 -0
  45. package/server/content-types/navigation-item/index.d.ts +1 -0
  46. package/server/content-types/navigation-item/index.js +3 -1
  47. package/server/content-types/navigation-item/lifecycles.d.ts +3 -0
  48. package/server/content-types/navigation-item/lifecycles.js +7 -0
  49. package/server/controllers/admin.js +19 -16
  50. package/server/controllers/client.js +6 -4
  51. package/server/graphql/queries/render-navigation-child.d.ts +1 -1
  52. package/server/graphql/queries/render-navigation.d.ts +1 -1
  53. package/server/graphql/types/index.js +1 -0
  54. package/server/graphql/types/navigation-item-additional-field-media.d.ts +5 -0
  55. package/server/graphql/types/navigation-item-additional-field-media.js +13 -0
  56. package/server/graphql/types/navigation-item.js +3 -0
  57. package/server/i18n/utils.d.ts +1 -0
  58. package/server/i18n/utils.js +4 -0
  59. package/server/index.d.ts +2 -0
  60. package/server/services/admin.js +18 -8
  61. package/server/services/client.js +42 -11
  62. package/server/services/common.js +17 -5
  63. package/server/utils/constant.d.ts +3 -0
  64. package/server/utils/constant.js +21 -1
  65. package/server/utils/functions.d.ts +16 -4
  66. package/server/utils/functions.js +54 -8
  67. package/strapi-server.d.ts +2 -0
  68. package/tsconfig.tsbuildinfo +1 -1
  69. package/types/contentTypes.d.ts +7 -2
  70. package/types/controllers.d.ts +4 -3
  71. package/types/index.d.ts +1 -0
  72. package/types/index.js +1 -0
  73. package/types/lifecycle.d.ts +22 -0
  74. package/types/lifecycle.js +3 -0
  75. package/types/services.d.ts +13 -1
package/README.md CHANGED
@@ -51,10 +51,11 @@ Strapi Navigation Plugin provides a website navigation / menu builder feature fo
51
51
  - [REST API](#rest-api)
52
52
  - [GraphQL API](#graphql-api)
53
53
  10. [🔌 Extensions](#-extensions)
54
- 11. [🧩 Examples](#-examples)
55
- 12. [💬 FAQ](#-faq)
56
- 13. [🤝 Contributing](#-contributing)
57
- 14. [👨‍💻 Community support](#-community-support)
54
+ 11. [🌿 Model lifecycle hooks](#model-life-cycle-hooks)
55
+ 12. [🧩 Examples](#-examples)
56
+ 13. [💬 FAQ](#-faq)
57
+ 14. [🤝 Contributing](#-contributing)
58
+ 15. [👨‍💻 Community support](#-community-support)
58
59
 
59
60
  ## ✨ Features
60
61
 
@@ -122,7 +123,7 @@ Complete installation requirements are exact same as for Strapi itself and can b
122
123
 
123
124
  **Supported Strapi versions**:
124
125
 
125
- - Strapi v4.15.x (recently tested)
126
+ - Strapi v4.20.x (recently tested)
126
127
  - Strapi v4.x
127
128
 
128
129
  > 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).
@@ -192,7 +193,7 @@ Config for this plugin is stored as a part of the `config/plugins.js` or `config
192
193
  ### Additional Fields
193
194
  It is advised to configure additional fields through the plugin's Settings Page. There you can find the table of custom fields and toggle input for the audience field. When enabled, the audience field can be customized through the content manager. Custom fields can be added, edited, toggled, and removed with the use of the table provided on the Settings Page. When removing custom fields be advised that their values in navigation items will be lost. Disabling the custom fields will not affect the data and can be done with no consequence of loosing information.
194
195
 
195
- Creating configuration for additional fields with the `config.js` file should be done with caution. Config object contains the `additionalFields` property of type `Array<CustomField | 'audience'>`, where CustomField is of type `{type: 'string' | 'boolean', name: string, label: string}`. When creating custom fields be advised that the `name` property has to be unique. When editing a custom field it is advised not to edit its `name` and `type` properties. After config has been restored the custom fields that are not present in `config.js` file will be deleted and their values in navigation items will be lost.
196
+ Creating configuration for additional fields with the `config.js` file should be done with caution. Config object contains the `additionalFields` property of type `Array<CustomField | 'audience'>`, where CustomField is of type `{ type: 'string' | 'boolean' | { "name": string, "url": string, "mime": string, "width": number, "height": number, "previewUrl": string }, name: string, label: string }`. When creating custom fields be advised that the `name` property has to be unique. When editing a custom field it is advised not to edit its `name` and `type` properties. After config has been restored the custom fields that are not present in `config.js` file will be deleted and their values in navigation items will be lost.
196
197
 
197
198
  ## 🔧 GQL Configuration
198
199
  Using navigation with GraphQL requires both plugins to be installed and working. You can find installation guide for GraphQL plugin **[here](https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#graphql)**. To properly configure GQL to work with navigation you should provide `gql` prop. This should contain union types that will be used to define GQL response format for your data while fetching:
@@ -733,6 +734,46 @@ module.exports = {
733
734
  };
734
735
  ```
735
736
 
737
+ ## Model lifecycle hooks
738
+
739
+ Navigation plugin allows to register lifecycle hooks for `Navigation` and `NavigationItem` content types.
740
+
741
+ You can read more about lifecycle hooks [here](https://docs.strapi.io/dev-docs/backend-customization/models#lifecycle-hooks). (You can set a listener for all of the hooks).
742
+
743
+ Lifecycle hooks can be register either in `register()` or `bootstrap()` methods of your server. You can register more than one listener for a specified lifecycle hook. For example: you want to do three things on navigation item creation and do not want to handle all of these actions in one big function. You can split logic in as many listeners as you want.
744
+
745
+ Listeners can by sync and `async`.
746
+
747
+ >Be aware that lifecycle hooks registered in `register()` may be fired by plugin's bootstrapping. If you want listen to events triggered after server's startup use `bootstrap()`.
748
+
749
+ Example:
750
+
751
+ ```ts
752
+ const navigationCommonService = strapi
753
+ .plugin("navigation")
754
+ .service("common");
755
+
756
+ navigationCommonService.registerLifecycleHook({
757
+ callback: async ({ action, result }) => {
758
+ const saveResult = await logIntoSystem(action, result);
759
+
760
+ console.log(saveResult);
761
+ },
762
+ contentTypeName: "navigation",
763
+ hookName: "afterCreate",
764
+ });
765
+
766
+ navigationCommonService.registerLifecycleHook({
767
+ callback: async ({ action, result }) => {
768
+ const saveResult = await logIntoSystem(action, result);
769
+
770
+ console.log(saveResult);
771
+ },
772
+ contentTypeName: "navigation-item",
773
+ hookName: "afterCreate",
774
+ });
775
+ ```
776
+
736
777
  ## 🧩 Examples
737
778
 
738
779
  Live example of plugin usage can be found in the [VirtusLab Strapi Examples](https://github.com/VirtusLab/strapi-examples/tree/master/strapi-plugin-navigation) repository.
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
- import { AdditionalFieldInputProps } from './types';
1
+ import React from "react";
2
+ import { AdditionalFieldInputProps } from "./types";
3
3
  declare const AdditionalFieldInput: React.FC<AdditionalFieldInputProps>;
4
4
  export default AdditionalFieldInput;
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -34,9 +34,24 @@ const lodash_1 = require("lodash");
34
34
  const react_intl_1 = require("react-intl");
35
35
  const DEFAULT_STRING_VALUE = "";
36
36
  const handlerFactory = ({ field, prop, onChange }) => ({ target }) => {
37
- onChange(field.name, target[prop]);
37
+ onChange(field.name, target[prop], field.type);
38
38
  };
39
- const AdditionalFieldInput = ({ field, isLoading, onChange, value, disabled, error }) => {
39
+ const mediaAttribute = {
40
+ type: "media",
41
+ multiple: false,
42
+ required: false,
43
+ allowedTypes: ["images"],
44
+ pluginOptions: {
45
+ i18n: {
46
+ localized: false,
47
+ },
48
+ },
49
+ };
50
+ const AdditionalFieldInput = ({ field, isLoading, onChange, value: baseValue, disabled, error, }) => {
51
+ const { fields } = (0, helper_plugin_1.useLibrary)();
52
+ const value = (0, react_1.useMemo)(() => field.type === "media" && baseValue
53
+ ? JSON.parse(baseValue)
54
+ : baseValue, [baseValue, field.type]);
40
55
  const toggleNotification = (0, helper_plugin_1.useNotification)();
41
56
  const { formatMessage } = (0, react_intl_1.useIntl)();
42
57
  const defaultInputProps = (0, react_1.useMemo)(() => ({
@@ -48,21 +63,34 @@ const AdditionalFieldInput = ({ field, isLoading, onChange, value, disabled, err
48
63
  }), [field, isLoading, error]);
49
64
  const handleBoolean = (0, react_1.useMemo)(() => handlerFactory({ field, onChange, prop: "checked" }), [onChange, field]);
50
65
  const handleString = (0, react_1.useMemo)(() => handlerFactory({ field, onChange, prop: "value" }), [onChange, field]);
66
+ const handleMedia = (0, react_1.useMemo)(() => handlerFactory({ field, onChange, prop: "value" }), [onChange, field]);
67
+ const MediaInput = (fields?.media ??
68
+ (() => react_1.default.createElement(react_1.default.Fragment, null)));
69
+ (0, react_1.useEffect)(() => {
70
+ if (!MediaInput) {
71
+ toggleNotification({
72
+ type: "warning",
73
+ message: (0, translations_1.getTrad)("notification.error.customField.media.missing"),
74
+ });
75
+ }
76
+ }, []);
51
77
  switch (field.type) {
52
- case 'boolean':
78
+ case "boolean":
53
79
  if (!(0, lodash_1.isNil)(value))
54
80
  (0, types_1.assertBoolean)(value);
55
81
  return (react_1.default.createElement(ToggleInput_1.ToggleInput, { ...defaultInputProps, checked: !!value, onChange: handleBoolean, onLabel: "true", offLabel: "false" }));
56
- case 'string':
82
+ case "string":
57
83
  if (!(0, lodash_1.isNil)(value))
58
84
  (0, types_1.assertString)(value);
59
85
  return (react_1.default.createElement(TextInput_1.TextInput, { ...defaultInputProps, onChange: handleString, value: value || DEFAULT_STRING_VALUE }));
60
- case 'select':
61
- return (react_1.default.createElement(Select_1.Select, { ...defaultInputProps, onChange: (v) => onChange(field.name, v), value: (0, lodash_1.isNil)(value) ? field.multi ? [] : null : value, multi: field.multi, withTags: field.multi }, field.options.map((option, index) => (react_1.default.createElement(Select_1.Option, { key: `${field.name}-option-${index}`, value: option }, option)))));
86
+ case "select":
87
+ return (react_1.default.createElement(Select_1.Select, { ...defaultInputProps, onChange: (v) => onChange(field.name, v, "select"), value: (0, lodash_1.isNil)(value) ? (field.multi ? [] : null) : value, multi: field.multi, withTags: field.multi }, field.options.map((option, index) => (react_1.default.createElement(Select_1.Option, { key: `${field.name}-option-${index}`, value: option }, option)))));
88
+ case "media":
89
+ return (react_1.default.createElement(MediaInput, { ...defaultInputProps, id: "navigation-item-media", onChange: handleMedia, value: value || [], intlLabel: defaultInputProps.label, attribute: mediaAttribute }));
62
90
  default:
63
91
  toggleNotification({
64
- type: 'warning',
65
- message: (0, translations_1.getTrad)('notification.error.customField.type'),
92
+ type: "warning",
93
+ message: (0, translations_1.getTrad)("notification.error.customField.type"),
66
94
  });
67
95
  throw new Error(`Type of custom field is unsupported`);
68
96
  }
@@ -3,7 +3,7 @@ import { NavigationItemCustomField } from "../../../../types";
3
3
  export type AdditionalFieldInputProps = {
4
4
  field: NavigationItemCustomField;
5
5
  isLoading: boolean;
6
- onChange: (name: string, value: string) => void;
6
+ onChange: (name: string, value: string, fieldType: string) => void;
7
7
  value: string | boolean | string[] | null;
8
8
  disabled: boolean;
9
9
  error: MessageDescriptor | null;
@@ -1,4 +1,6 @@
1
1
  import React from 'react';
2
- declare const DragButton: React.ForwardRefExoticComponent<React.RefAttributes<unknown>>;
2
+ declare const DragButton: React.ForwardRefExoticComponent<{
3
+ isActive?: boolean | undefined;
4
+ } & React.RefAttributes<unknown>>;
3
5
  export default DragButton;
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -16,10 +16,11 @@ const DragButtonWrapper = styled_components_1.default.span `
16
16
  width: ${DRAG_BUTTON_SIZE_IN_REM}rem;
17
17
  padding: ${({ theme }) => theme.spaces[2]};
18
18
 
19
- background: ${({ theme }) => theme.colors.neutral0};
19
+ background: ${({ theme, isActive }) => isActive ? theme.colors.neutral150 : theme.colors.neutral0};
20
20
  border: 1px solid ${({ theme }) => theme.colors.neutral200};
21
21
  border-radius: ${({ theme }) => theme.borderRadius};
22
22
  cursor: pointer;
23
+ transition: background-color 0.3s ease-in;
23
24
 
24
25
  svg {
25
26
  height: ${({ theme }) => theme.spaces[3]};
@@ -10,6 +10,7 @@ interface IProps {
10
10
  onItemEdit: VoidEffect;
11
11
  onItemRestore: VoidEffect;
12
12
  dragRef: React.MutableRefObject<HTMLHeadingElement>;
13
+ isSearchActive?: boolean;
13
14
  }
14
15
  declare const ItemCardHeader: React.FC<IProps>;
15
16
  export default ItemCardHeader;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const react_1 = __importDefault(require("react"));
7
+ const styled_components_1 = __importDefault(require("styled-components"));
7
8
  const Flex_1 = require("@strapi/design-system/Flex");
8
9
  const Typography_1 = require("@strapi/design-system/Typography");
9
10
  const IconButton_1 = require("@strapi/design-system/IconButton");
@@ -15,9 +16,9 @@ const utils_1 = require("../../../utils");
15
16
  const icons_1 = require("./icons");
16
17
  const wrapperStyle = { zIndex: 2 };
17
18
  const pathWrapperStyle = { maxWidth: "425px" };
18
- const ItemCardHeader = ({ title, path, icon, removed, canUpdate, onItemRemove, onItemEdit, onItemRestore, dragRef }) => (react_1.default.createElement(Wrapper_1.default, null,
19
+ const ItemCardHeader = ({ title, path, icon, removed, canUpdate, onItemRemove, onItemEdit, onItemRestore, dragRef, isSearchActive }) => (react_1.default.createElement(Wrapper_1.default, null,
19
20
  react_1.default.createElement(Flex_1.Flex, { alignItems: "center" },
20
- canUpdate && (react_1.default.createElement(DragButton_1.default, { ref: dragRef })),
21
+ canUpdate && (react_1.default.createElement(DragButton_1.default, { ref: dragRef, isActive: isSearchActive })),
21
22
  react_1.default.createElement(Typography_1.Typography, { variant: "omega", fontWeight: "bold" }, title),
22
23
  react_1.default.createElement(Typography_1.Typography, { variant: "omega", fontWeight: "bold", textColor: 'neutral500', ellipsis: true, style: pathWrapperStyle }, path),
23
24
  react_1.default.createElement(Flex_1.Flex, null,
@@ -25,9 +26,13 @@ const ItemCardHeader = ({ title, path, icon, removed, canUpdate, onItemRemove, o
25
26
  react_1.default.createElement(Flex_1.Flex, { alignItems: "center", style: wrapperStyle },
26
27
  removed &&
27
28
  (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: (0, utils_1.getMessage)(`components.navigationItem.action.${canUpdate ? 'edit' : 'view'}`, canUpdate ? 'Edit' : 'View'), icon: canUpdate ? icons_1.pencilIcon : icons_1.eyeIcon }),
29
+ react_1.default.createElement(IconButton, { isActive: isSearchActive, 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
30
  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 }))))));
31
+ react_1.default.createElement(IconButton, { isActive: isSearchActive, onClick: onItemRestore, label: (0, utils_1.getMessage)('components.navigationItem.action.restore', "Restore"), icon: icons_1.refreshIcon }) :
32
+ react_1.default.createElement(IconButton, { isActive: isSearchActive, onClick: onItemRemove, label: (0, utils_1.getMessage)('components.navigationItem.action.remove', "Remove"), icon: icons_1.trashIcon }))))));
33
+ const IconButton = (0, styled_components_1.default)(IconButton_1.IconButton) `
34
+ transition: background-color 0.3s ease-in;
35
+ ${({ isActive, theme }) => isActive ? `background-color: ${theme.colors.neutral150} ;` : ''}
36
+ `;
32
37
  exports.default = ItemCardHeader;
33
38
  //# sourceMappingURL=index.js.map
@@ -30,6 +30,7 @@ const react_1 = __importStar(require("react"));
30
30
  const prop_types_1 = __importDefault(require("prop-types"));
31
31
  const react_dnd_1 = require("react-dnd");
32
32
  const lodash_1 = require("lodash");
33
+ const styled_components_1 = require("styled-components");
33
34
  const Card_1 = require("@strapi/design-system/Card");
34
35
  const Divider_1 = require("@strapi/design-system/Divider");
35
36
  const Flex_1 = require("@strapi/design-system/Flex");
@@ -47,7 +48,7 @@ const utils_1 = require("../../utils");
47
48
  const CollapseButton_1 = __importDefault(require("../CollapseButton"));
48
49
  const Item = (props) => {
49
50
  const { item, isLast = false, level = 0, levelPath = '', allowedLevels, relatedRef, isParentAttachedToMenu, onItemLevelAdd, onItemRemove, onItemRestore, onItemEdit, onItemReOrder, onItemToggleCollapse, error, displayChildren, config = {}, permissions = {}, } = props;
50
- const { viewId, title, type, path, removed, externalPath, menuAttached, collapsed, structureId, items = [], } = item;
51
+ const { viewId, title, type, path, removed, externalPath, menuAttached, collapsed, structureId, items = [], isSearchActive, } = item;
51
52
  const { contentTypes = [], contentTypesNameFields } = config;
52
53
  const isExternal = type === utils_1.navigationItemType.EXTERNAL;
53
54
  const isWrapper = type === utils_1.navigationItemType.WRAPPER;
@@ -110,11 +111,29 @@ const Item = (props) => {
110
111
  const generatePreviewUrl = entity => {
111
112
  const { isSingle } = contentType;
112
113
  const entityLocale = entity?.locale ? `?plugins[i18n][locale]=${entity?.locale}` : '';
113
- return `/content-manager/${isSingle ? 'singleType' : 'collectionType'}/${entity?.__collectionUid}${!isSingle ? '/' + entity?.id : ''}${entityLocale}`;
114
+ return `/content-manager/${isSingle ? 'single-types' : 'collection-types'}/${entity?.__collectionUid}${!isSingle ? '/' + entity?.id : ''}${entityLocale}`;
114
115
  };
115
116
  const onNewItemClick = (0, react_1.useCallback)((event) => canUpdate && onItemLevelAdd(event, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, `${structureId}.${items.length}`), [viewId, isNextMenuAllowedLevel, absolutePath, menuAttached, structureId, items, canUpdate]);
117
+ (0, react_1.useEffect)(() => {
118
+ if (isSearchActive) {
119
+ refs.dropRef.current?.scrollIntoView?.({
120
+ behavior: "smooth",
121
+ block: "center",
122
+ inline: "center",
123
+ });
124
+ }
125
+ }, [isSearchActive, refs.dropRef.current]);
126
+ const theme = (0, styled_components_1.useTheme)();
116
127
  return (react_1.default.createElement(Wrapper_1.default, { level: level, isLast: isLast, style: { opacity: isDragging ? 0.2 : 1 }, ref: refs ? refs.dropRef : null },
117
- react_1.default.createElement(Card_1.Card, { style: { width: "728px", zIndex: 1, position: "relative", overflow: 'hidden' } },
128
+ react_1.default.createElement(Card_1.Card, { style: {
129
+ width: "728px",
130
+ zIndex: 1,
131
+ position: "relative",
132
+ overflow: "hidden",
133
+ backgroundColor: isSearchActive ? theme.colors.neutral150 : undefined,
134
+ borderColor: isSearchActive ? theme.colors.neutral300 : undefined,
135
+ transition: "background-color 0.3s ease-in"
136
+ } },
118
137
  removed && (react_1.default.createElement(ItemCardRemovedOverlay_1.ItemCardRemovedOverlay, null)),
119
138
  react_1.default.createElement("div", { ref: refs.previewRef },
120
139
  react_1.default.createElement(Card_1.CardBody, null,
@@ -122,7 +141,8 @@ const Item = (props) => {
122
141
  ...item,
123
142
  isMenuAllowedLevel,
124
143
  isParentAttachedToMenu,
125
- }, levelPath, isParentAttachedToMenu), onItemRestore: () => onItemRestore(item), dragRef: refs.dragRef, removed: removed, canUpdate: canUpdate })),
144
+ isSearchActive: false,
145
+ }, levelPath, isParentAttachedToMenu), onItemRestore: () => onItemRestore(item), dragRef: refs.dragRef, removed: removed, canUpdate: canUpdate, isSearchActive: isSearchActive })),
126
146
  react_1.default.createElement(Divider_1.Divider, null),
127
147
  !isExternal && (react_1.default.createElement(Card_1.CardBody, { style: { padding: '8px' } },
128
148
  react_1.default.createElement(Flex_1.Flex, { style: { width: '100%' }, direction: "row", alignItems: "center", justifyContent: "space-between" },
@@ -12,7 +12,7 @@ const List = ({ allowedLevels, error, isParentAttachedToMenu = false, items, lev
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
- }, permissions: permissions }));
15
+ }, permissions: permissions, isSearchActive: !!item.isSearchActive }));
16
16
  })));
17
17
  List.propTypes = {
18
18
  allowedLevels: prop_types_1.default.number,
@@ -1,6 +1,12 @@
1
+ import { Effect } from "../../../../types";
2
+ interface Props {
3
+ value: string;
4
+ setValue: Effect<{
5
+ value: string;
6
+ index: number;
7
+ }>;
8
+ initialIndex?: number;
9
+ }
10
+ declare const Search: ({ value, setValue, initialIndex }: Props) => JSX.Element;
1
11
  export default Search;
2
- declare function Search({ value, setValue }: {
3
- value: any;
4
- setValue: any;
5
- }): JSX.Element;
6
12
  //# sourceMappingURL=index.d.ts.map
@@ -30,25 +30,64 @@ const react_1 = __importStar(require("react"));
30
30
  const react_intl_1 = require("react-intl");
31
31
  const IconButton_1 = require("@strapi/design-system/IconButton");
32
32
  const Searchbar_1 = require("@strapi/design-system/Searchbar");
33
+ const Typography_1 = require("@strapi/design-system/Typography");
33
34
  const Search_1 = __importDefault(require("@strapi/icons/Search"));
34
35
  const translations_1 = require("../../translations");
35
- const Search = ({ value, setValue }) => {
36
+ const DEFAULT_INDEX = 0;
37
+ const Search = ({ value, setValue, initialIndex = DEFAULT_INDEX }) => {
38
+ const [currentValue, setCurrentValue] = (0, react_1.useState)(value);
39
+ const [previousValue, setPreviousValue] = (0, react_1.useState)(value);
40
+ const [currentIndex, setCurrentIndex] = (0, react_1.useState)(initialIndex);
36
41
  const [isOpen, setIsOpen] = (0, react_1.useState)(!!value);
37
42
  const wrapperRef = (0, react_1.useRef)(null);
38
43
  const { formatMessage } = (0, react_intl_1.useIntl)();
39
44
  (0, react_1.useEffect)(() => {
40
45
  if (isOpen) {
41
46
  setTimeout(() => {
42
- wrapperRef.current.querySelector('input').focus();
47
+ wrapperRef.current?.querySelector("input")?.focus();
43
48
  }, 0);
44
49
  }
45
50
  }, [isOpen]);
51
+ (0, react_1.useEffect)(() => {
52
+ if (currentIndex && currentValue === previousValue) {
53
+ setValue({
54
+ value: currentValue,
55
+ index: currentIndex,
56
+ });
57
+ }
58
+ }, [currentIndex, currentValue, previousValue]);
59
+ (0, react_1.useEffect)(() => {
60
+ if (currentValue !== previousValue) {
61
+ setPreviousValue(currentValue);
62
+ setCurrentIndex(DEFAULT_INDEX);
63
+ setValue({
64
+ value: currentValue,
65
+ index: DEFAULT_INDEX,
66
+ });
67
+ }
68
+ }, [currentValue, previousValue]);
69
+ const onKeyDown = (0, react_1.useCallback)((e) => {
70
+ if (e.code.toLowerCase() === "enter") {
71
+ setCurrentIndex((current) => current + 1);
72
+ }
73
+ }, []);
74
+ const onChange = (0, react_1.useCallback)((e) => {
75
+ setCurrentValue(e.target.value);
76
+ }, [setCurrentValue]);
77
+ const onClear = (0, react_1.useCallback)(() => {
78
+ setCurrentValue("");
79
+ setIsOpen(false);
80
+ }, [setCurrentValue, setIsOpen]);
46
81
  if (isOpen) {
47
82
  return (react_1.default.createElement("div", { ref: wrapperRef },
48
- react_1.default.createElement(Searchbar_1.Searchbar, { name: "searchbar", onClear: () => { setValue(''); setIsOpen(false); }, value: value, size: "S", onChange: (e) => setValue(e.target.value), clearLabel: "Clearing the search", placeholder: formatMessage({
49
- id: (0, translations_1.getTradId)('pages.main.search.placeholder'),
50
- defaultMessage: 'Type to start searching...',
51
- }) }, "Search for navigation items")));
83
+ react_1.default.createElement(Searchbar_1.Searchbar, { name: "searchbar", onClear: onClear, value: value, size: "S", onChange: onChange, clearLabel: "Clearing the search", placeholder: formatMessage({
84
+ id: (0, translations_1.getTradId)("pages.main.search.placeholder"),
85
+ defaultMessage: "Type to start searching...",
86
+ }), onKeyDown: onKeyDown }, "Search for navigation items"),
87
+ react_1.default.createElement(Typography_1.Typography, { variant: "pi", fontColor: "neutral150", style: { margin: "3px 0 0", display: "inline-block" } }, formatMessage({
88
+ id: (0, translations_1.getTradId)("pages.main.search.subLabel"),
89
+ defaultMessage: "press ENTER to highlight next item",
90
+ }))));
52
91
  }
53
92
  else {
54
93
  return (react_1.default.createElement(IconButton_1.IconButton, { icon: react_1.default.createElement(Search_1.default, null), onClick: () => setIsOpen(!isOpen) }));
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Effect } from '../../../../types';
3
+ import { GenericInputProps } from "@strapi/helper-plugin";
3
4
  interface IProps {
4
5
  onChange: Effect<string[]>;
5
6
  initialValue?: string[];
@@ -7,7 +8,7 @@ interface IProps {
7
8
  name?: string;
8
9
  label?: string;
9
10
  disabled?: boolean;
10
- error?: string | string[];
11
+ error?: GenericInputProps["error"];
11
12
  }
12
13
  declare const TextArrayInput: React.FC<IProps>;
13
14
  export default TextArrayInput;
@@ -1,3 +1,3 @@
1
- declare const useAllContentTypes: () => Pick<any, "data" | "isLoading" | "error">;
1
+ declare const useAllContentTypes: () => Pick<import("react-query").UseQueryResult<any, unknown>, "data" | "isLoading" | "error">;
2
2
  export default useAllContentTypes;
3
3
  //# sourceMappingURL=useAllContentTypes.d.ts.map
@@ -1,8 +1,8 @@
1
1
  export default useNavigationConfig;
2
2
  declare function useNavigationConfig(): {
3
- data: any;
4
- isLoading: any;
5
- error: any;
3
+ data: unknown;
4
+ isLoading: boolean;
5
+ error: unknown;
6
6
  submitMutation: (...args: any[]) => Promise<void>;
7
7
  restoreMutation: (...args: any[]) => Promise<void>;
8
8
  restartMutation: (...args: any[]) => Promise<void>;
@@ -4,7 +4,9 @@ declare namespace _default {
4
4
  function registerTrads({ locales }: {
5
5
  locales?: any[] | undefined;
6
6
  }): {
7
- data: any;
7
+ data: {
8
+ [x: string]: string;
9
+ };
8
10
  locale: any;
9
11
  }[];
10
12
  }
@@ -0,0 +1,2 @@
1
+ export declare const customFieldsTypes: string[];
2
+ //# sourceMappingURL=const.d.ts.map
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.customFieldsTypes = void 0;
4
+ exports.customFieldsTypes = ["string", "boolean", "select", "media"];
5
+ //# sourceMappingURL=const.js.map
@@ -0,0 +1,2 @@
1
+ export * from "./const";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,18 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./const"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -37,21 +37,23 @@ const utils_1 = require("../../../../utils");
37
37
  const lodash_1 = require("lodash");
38
38
  const translations_1 = require("../../../../translations");
39
39
  const TextArrayInput_1 = __importDefault(require("../../../../components/TextArrayInput"));
40
+ const common_1 = require("../../common");
40
41
  const tradPrefix = 'pages.settings.form.customFields.popup.';
41
- const customFieldsTypes = ["string", "boolean", "select"];
42
42
  const prepareSelectOptions = (options) => options.map((option, index) => ({
43
- key: index,
43
+ key: `${option}-${index}`,
44
44
  metadatas: {
45
45
  intlLabel: {
46
46
  id: option,
47
47
  defaultMessage: option,
48
- }
48
+ },
49
+ hidden: false,
50
+ disabled: false,
49
51
  },
50
52
  value: option,
51
53
  label: option,
52
54
  }));
53
55
  const CustomFieldForm = ({ isEditForm, customField, onSubmit, onClose, usedCustomFieldNames }) => {
54
- const typeSelectOptions = prepareSelectOptions(customFieldsTypes);
56
+ const typeSelectOptions = prepareSelectOptions(common_1.customFieldsTypes);
55
57
  const initialValues = (0, react_1.useMemo)(() => {
56
58
  if ((0, lodash_1.isNil)(customField.type)) {
57
59
  return formDefinition.defaultValues;
@@ -85,18 +87,21 @@ const CustomFieldForm = ({ isEditForm, customField, onSubmit, onClose, usedCusto
85
87
  validationSchema: formDefinition.schemaFactory(usedCustomFieldNames),
86
88
  validateOnChange: false,
87
89
  });
88
- const defaultProps = (0, react_1.useCallback)((fieldName) => ({
89
- intlLabel: (0, translations_1.getTrad)(`${tradPrefix}${fieldName}.label`),
90
- onChange: handleChange,
91
- name: fieldName,
92
- value: values[fieldName],
93
- error: errors[fieldName],
94
- }), [values, errors, handleChange]);
90
+ const defaultProps = (0, react_1.useCallback)((fieldName) => {
91
+ const error = mapError(errors[fieldName]);
92
+ return {
93
+ intlLabel: (0, translations_1.getTrad)(`${tradPrefix}${fieldName}.label`),
94
+ onChange: handleChange,
95
+ name: fieldName,
96
+ value: values[fieldName],
97
+ error,
98
+ };
99
+ }, [values, errors, handleChange]);
95
100
  return (react_1.default.createElement("form", null,
96
101
  react_1.default.createElement(ModalLayout_1.ModalBody, null,
97
102
  react_1.default.createElement(Grid_1.Grid, { gap: 5 },
98
103
  react_1.default.createElement(Grid_1.GridItem, { key: "name", col: 12 },
99
- react_1.default.createElement(helper_plugin_1.GenericInput, { ...defaultProps("name"), autoFocused: true, placeholder: (0, translations_1.getTrad)(`${tradPrefix}name.placeholder`), description: (0, translations_1.getTrad)(`${tradPrefix}name.description`), type: "text", disabled: isEditForm })),
104
+ react_1.default.createElement(helper_plugin_1.GenericInput, { ...defaultProps("name"), placeholder: (0, translations_1.getTrad)(`${tradPrefix}name.placeholder`), description: (0, translations_1.getTrad)(`${tradPrefix}name.description`), type: "text", disabled: isEditForm })),
100
105
  react_1.default.createElement(Grid_1.GridItem, { key: "label", col: 12 },
101
106
  react_1.default.createElement(helper_plugin_1.GenericInput, { ...defaultProps("label"), placeholder: (0, translations_1.getTrad)(`${tradPrefix}label.placeholder`), description: (0, translations_1.getTrad)(`${tradPrefix}label.description`), type: "text" })),
102
107
  react_1.default.createElement(Grid_1.GridItem, { key: "type", col: 12 },
@@ -111,4 +116,14 @@ const CustomFieldForm = ({ isEditForm, customField, onSubmit, onClose, usedCusto
111
116
  react_1.default.createElement(ModalLayout_1.ModalFooter, { startActions: react_1.default.createElement(Button_1.Button, { onClick: onClose, variant: "tertiary" }, (0, utils_1.getMessage)('popup.item.form.button.cancel')), endActions: react_1.default.createElement(Button_1.Button, { onClick: handleSubmit, disabled: !(0, lodash_1.isEmpty)(errors) || isSubmitting }, (0, utils_1.getMessage)(`popup.item.form.button.save`)) })));
112
117
  };
113
118
  exports.default = CustomFieldForm;
119
+ const mapError = (err) => {
120
+ if (typeof err === "string") {
121
+ return err;
122
+ }
123
+ if (typeof err === "object" &&
124
+ err &&
125
+ (err.id || err.description || err.defaultMessage)) {
126
+ return err;
127
+ }
128
+ };
114
129
  //# sourceMappingURL=index.js.map
@@ -61,7 +61,7 @@ const CustomFieldTable = ({ data, onOpenModal, onRemoveCustomField, onToggleCust
61
61
  type: 'warning',
62
62
  message: {
63
63
  id: (0, translations_1.getTradId)(`${tradPrefix}confirmation.error`),
64
- default: 'Something went wrong',
64
+ defaultMessage: 'Something went wrong',
65
65
  }
66
66
  });
67
67
  }
@@ -1,5 +1,4 @@
1
- import React, { VFC } from "react";
2
- import { Form } from "@strapi/helper-plugin";
1
+ import React, { FC } from "react";
3
2
  interface Form {
4
3
  pruneNavigations: boolean;
5
4
  enabled: boolean;
@@ -14,7 +13,7 @@ interface Props {
14
13
  onSubmit: SubmitEffect;
15
14
  onCancel: CancelEffect;
16
15
  }
17
- export declare const DisableI18nModal: VFC<Props>;
16
+ export declare const DisableI18nModal: FC<Props>;
18
17
  export declare const useDisableI18nModal: (onSubmit: SubmitEffect) => {
19
18
  setDisableI18nModalOpened: React.Dispatch<React.SetStateAction<boolean>>;
20
19
  setI18nModalOnCancel: React.Dispatch<React.SetStateAction<() => void>>;