@griddo/ax 1.68.7 → 1.69.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 (55) hide show
  1. package/config/webpack.config.js +24 -0
  2. package/package.json +6 -2
  3. package/src/__mocks__/axios/ReferenceField.ts +471 -0
  4. package/src/{__tests__/components/Fields/UrlField → __mocks__}/mockedAxios.ts +0 -0
  5. package/src/__mocks__/reducers/structuredData.tsx +10 -0
  6. package/src/__mocks__/store/ReferenceField.ts +1671 -0
  7. package/src/__tests__/components/Fields/AnalyticsField/AnalyticsField.test.tsx +20 -28
  8. package/src/__tests__/components/Fields/CheckGroup/CheckGroup.test.tsx +16 -28
  9. package/src/__tests__/components/Fields/MultiCheckSelectGroup/MultiCheckSelectGroup.test.tsx +120 -0
  10. package/src/__tests__/components/Fields/ReferenceField/ReferenceField.test.tsx +532 -0
  11. package/src/__tests__/components/Fields/TextField/TextField.test.tsx +0 -1
  12. package/src/__tests__/components/Fields/UrlField/UrlField.test.tsx +14 -13
  13. package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.test.tsx +121 -0
  14. package/src/components/Fields/ComponentArray/MixableComponentArray/PasteModuleButton/index.tsx +4 -3
  15. package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +69 -34
  16. package/src/components/Fields/ComponentArray/SameComponentArray/index.tsx +59 -26
  17. package/src/components/Fields/ComponentContainer/atoms.tsx +2 -8
  18. package/src/components/Fields/ComponentContainer/index.tsx +21 -21
  19. package/src/components/Fields/ComponentContainer/style.tsx +49 -28
  20. package/src/components/Fields/MultiCheckSelectGroup/index.tsx +2 -2
  21. package/src/components/Fields/MultiCheckSelectGroup/style.tsx +4 -4
  22. package/src/components/Fields/ReferenceField/AutoPanel/AutoItem/index.tsx +1 -1
  23. package/src/components/Fields/ReferenceField/AutoPanel/index.tsx +2 -4
  24. package/src/components/Fields/ReferenceField/AutoPanel/style.tsx +3 -0
  25. package/src/components/Fields/ReferenceField/Context/index.tsx +3 -3
  26. package/src/components/Fields/ReferenceField/ItemList/Item/index.tsx +15 -24
  27. package/src/components/Fields/ReferenceField/ItemList/Item/style.tsx +22 -15
  28. package/src/components/Fields/ReferenceField/ItemList/index.tsx +42 -11
  29. package/src/components/Fields/ReferenceField/ManualPanel/Item/index.tsx +1 -1
  30. package/src/components/Fields/ReferenceField/index.tsx +5 -6
  31. package/src/components/Fields/Wysiwyg/index.tsx +4 -4
  32. package/src/components/Gallery/GalleryPanel/DetailPanel/index.tsx +12 -4
  33. package/src/components/Gallery/GalleryPanel/index.tsx +10 -2
  34. package/src/components/Gallery/index.tsx +5 -1
  35. package/src/components/MainWrapper/AppBar/index.tsx +1 -1
  36. package/src/containers/App/reducer.tsx +1 -0
  37. package/src/containers/Gallery/actions.tsx +11 -5
  38. package/src/containers/Navigation/Defaults/actions.tsx +2 -2
  39. package/src/containers/PageEditor/actions.tsx +18 -7
  40. package/src/forms/elements.tsx +5 -6
  41. package/src/helpers/arrays.tsx +1 -2
  42. package/src/modules/Content/PageItem/index.tsx +13 -2
  43. package/src/modules/Content/index.tsx +18 -1
  44. package/src/modules/GlobalEditor/Editor/index.tsx +2 -2
  45. package/src/modules/GlobalEditor/index.tsx +9 -7
  46. package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/index.tsx +1 -1
  47. package/src/modules/PageEditor/Editor/index.tsx +2 -2
  48. package/src/modules/PageEditor/index.tsx +8 -6
  49. package/src/modules/StructuredData/Form/index.tsx +7 -4
  50. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +25 -3
  51. package/src/modules/StructuredData/StructuredDataList/index.tsx +4 -0
  52. package/src/__mocks__/reducers/analyticsState.tsx +0 -14
  53. package/src/__mocks__/reducers/app.tsx +0 -10
  54. package/src/__mocks__/reducers/pageEditor.tsx +0 -30
  55. package/src/__mocks__/reducers/sites.tsx +0 -10
@@ -1,13 +1,15 @@
1
1
  import React from "react";
2
+ import { DraggableProvided } from "react-beautiful-dnd";
3
+
2
4
  import { IDataSource, IStructuredDataContent } from "@ax/types";
3
5
  import { getFormattedDateWithTimezone } from "@ax/helpers";
4
6
 
5
- import { FloatingMenu, Icon, IconAction, ReorderArrows } from "@ax/components";
7
+ import { FloatingMenu, Icon, IconAction } from "@ax/components";
6
8
 
7
9
  import * as S from "./style";
8
10
 
9
11
  const Item = (props: IProps) => {
10
- const { item, handleDelete, handleReorder, index, listLength, source } = props;
12
+ const { item, handleDelete, listLength, source, innerRef, provided } = props;
11
13
 
12
14
  const removeItem = () => {
13
15
  handleDelete(item);
@@ -20,22 +22,12 @@ const Item = (props: IProps) => {
20
22
  );
21
23
 
22
24
  const actionMenuItem = (item: any) => (
23
- <S.ActionItem key={item.icon} onClick={item.action}>
25
+ <S.ActionItem key={item.icon} onClick={item.action} data-testid="action-menu-item">
24
26
  <Icon name={item.icon} />
25
27
  <S.ActionText>{item.label}</S.ActionText>
26
28
  </S.ActionItem>
27
29
  );
28
30
 
29
- const moveModule = (e: any, isPush: boolean) => {
30
- e.preventDefault();
31
- e.stopPropagation();
32
- handleReorder(item, isPush);
33
- };
34
-
35
- const handleUpClick = (e: any) => moveModule(e, false);
36
-
37
- const handleDownClick = (e: any) => moveModule(e, true);
38
-
39
31
  const editMenu = {
40
32
  options: [
41
33
  {
@@ -53,19 +45,18 @@ const Item = (props: IProps) => {
53
45
  );
54
46
 
55
47
  return (
56
- <S.Item>
48
+ <S.Item ref={innerRef} data-testid="reference-field-item" {...provided?.draggableProps}>
49
+ {listLength > 1 && (
50
+ <S.HandleWrapper {...provided?.dragHandleProps}>
51
+ <S.IconHandleWrapper>
52
+ <Icon name="drag" size="16" />
53
+ </S.IconHandleWrapper>
54
+ </S.HandleWrapper>
55
+ )}
57
56
  <S.TextWrapper>
58
57
  <S.Header>
59
58
  <S.Type>{source?.title.toUpperCase()}</S.Type>
60
59
  {item.modified && <S.Date>{getFormattedDateWithTimezone(item.modified, "d MMM Y")}</S.Date>}
61
- <S.ReorderArrowsWrapper>
62
- <ReorderArrows
63
- handleDownClick={handleDownClick}
64
- handleUpClick={handleUpClick}
65
- listLength={listLength}
66
- index={index}
67
- />
68
- </S.ReorderArrowsWrapper>
69
60
  </S.Header>
70
61
  <S.Title>{item.content.title}</S.Title>
71
62
  </S.TextWrapper>
@@ -76,11 +67,11 @@ const Item = (props: IProps) => {
76
67
 
77
68
  interface IProps {
78
69
  item: IStructuredDataContent;
79
- index: number;
80
70
  listLength: number;
81
71
  source: IDataSource;
82
72
  handleDelete(item: IStructuredDataContent): void;
83
- handleReorder(item: any, isPush: boolean): void;
73
+ innerRef: any;
74
+ provided: DraggableProvided;
84
75
  }
85
76
 
86
77
  export default Item;
@@ -20,10 +20,11 @@ const IconActionWrapper = styled.div`
20
20
  const Item = styled.li`
21
21
  position: relative;
22
22
  display: flex;
23
+ align-items: stretch;
23
24
  background-color: ${(p) => p.theme.color.uiBarBackground};
24
25
  border: ${(p) => `1px solid ${p.theme.color.uiLine}`};
25
26
  border-radius: ${(p) => p.theme.radii.s};
26
- padding: ${(p) => p.theme.spacing.s};
27
+ padding: ${(p) => `${p.theme.spacing.s} ${p.theme.spacing.s} ${p.theme.spacing.s} ${p.theme.spacing.xs}`};
27
28
  margin-bottom: ${(p) => p.theme.spacing.xxs};
28
29
  cursor: pointer;
29
30
  &:hover {
@@ -49,7 +50,7 @@ const Header = styled.div`
49
50
  display: flex;
50
51
  justify-content: space-between;
51
52
  align-items: center;
52
- padding-right: ${p => p.theme.spacing.l};
53
+ padding-right: ${(p) => p.theme.spacing.l};
53
54
  margin-bottom: ${(p) => p.theme.spacing.xxs};
54
55
  `;
55
56
 
@@ -66,7 +67,6 @@ const Type = styled.div`
66
67
  const Title = styled.div`
67
68
  ${(p) => p.theme.textStyle.fieldContent};
68
69
  color: ${(p) => p.theme.color.textHighEmphasis};
69
- max-width: 75%;
70
70
  `;
71
71
 
72
72
  const ActionMenu = styled.ul`
@@ -104,19 +104,25 @@ const ActionItem = styled.li`
104
104
  const ButtonsWrapper = styled.span`
105
105
  display: flex;
106
106
  position: absolute;
107
- bottom: ${p => p.theme.spacing.xs};
108
- right: ${p => p.theme.spacing.xs};
107
+ bottom: ${(p) => p.theme.spacing.xs};
108
+ right: ${(p) => p.theme.spacing.xs};
109
109
  `;
110
110
 
111
- const ReorderArrowsWrapper = styled.div`
112
- position: absolute;
113
- right: 0;
114
- bottom: -${p => p.theme.spacing.xxs};
115
- white-space: nowrap;
111
+ const HandleWrapper = styled.div`
116
112
  display: flex;
117
- justify-content: flex-end;
118
- min-width: ${p => p.theme.spacing.l};
119
- `
113
+ align-items: center;
114
+ padding-right: ${(p) => p.theme.spacing.xxs};
115
+ `;
116
+
117
+ const IconHandleWrapper = styled.div`
118
+ width: ${(p) => p.theme.spacing.s};
119
+ height: ${(p) => p.theme.spacing.s};
120
+ svg {
121
+ path {
122
+ fill: ${(p) => p.theme.color.textLowEmphasis};
123
+ }
124
+ }
125
+ `;
120
126
 
121
127
  export {
122
128
  IconActionWrapper,
@@ -130,5 +136,6 @@ export {
130
136
  ActionText,
131
137
  ActionItem,
132
138
  ButtonsWrapper,
133
- ReorderArrowsWrapper
134
- }
139
+ HandleWrapper,
140
+ IconHandleWrapper,
141
+ };
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect } from "react";
2
2
  import { connect } from "react-redux";
3
+ import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";
3
4
 
4
5
  import { structuredData } from "@ax/api";
5
6
  import { IDataSource, IRootState, IStructuredDataContent } from "@ax/types";
@@ -58,23 +59,53 @@ const ItemList = (props: IProps) => {
58
59
  }
59
60
  };
60
61
 
61
- return (
62
- <S.ItemList>
63
- {selectedItems &&
64
- selectedItems.map((item: IStructuredDataContent, index: number) => {
65
- const source = sourceTitles.find((el: IDataSource) => el.id === item.structuredData);
66
- return (
62
+ const onDragEnd = (result: DropResult) => {
63
+ if (!result.destination) {
64
+ return;
65
+ }
66
+
67
+ if (result.destination.index === result.source.index) {
68
+ return;
69
+ }
70
+
71
+ const item = selectedItems.find((elem: IStructuredDataContent) => elem.id === parseInt(result.draggableId));
72
+ setReorderElements(item, result.destination.index);
73
+ };
74
+
75
+ const ComponentList = React.memo(function ComponentList({ components }: any) {
76
+ return components.map((item: IStructuredDataContent, index: number) => {
77
+ const source = sourceTitles.find((el: IDataSource) => el.id === item.structuredData);
78
+ return (
79
+ <Draggable draggableId={`${item.id}`} index={index} key={item.id}>
80
+ {(provided) => (
67
81
  <Item
68
- key={index}
69
82
  item={item}
70
83
  handleDelete={handleDelete}
71
- handleReorder={setReorderElements}
72
- index={index}
73
84
  listLength={selectedItems.length}
74
85
  source={source}
86
+ innerRef={provided.innerRef}
87
+ provided={provided}
75
88
  />
76
- );
77
- })}
89
+ )}
90
+ </Draggable>
91
+ );
92
+ });
93
+ });
94
+
95
+ return (
96
+ <S.ItemList>
97
+ {selectedItems && (
98
+ <DragDropContext onDragEnd={onDragEnd}>
99
+ <Droppable droppableId="referenceList">
100
+ {(provided) => (
101
+ <div ref={provided.innerRef} {...provided.droppableProps}>
102
+ <ComponentList components={selectedItems} />
103
+ {provided.placeholder}
104
+ </div>
105
+ )}
106
+ </Droppable>
107
+ </DragDropContext>
108
+ )}
78
109
  </S.ItemList>
79
110
  );
80
111
  };
@@ -15,7 +15,7 @@ const Item = (props: IProps) => {
15
15
  };
16
16
 
17
17
  return (
18
- <S.Item onClick={handleItemClick}>
18
+ <S.Item onClick={handleItemClick} data-testid="manual-panel-item">
19
19
  <CheckField name="check" value={item.id} checked={isChecked} disabled={disabled} />
20
20
  <S.TextWrapper>
21
21
  <S.Header>
@@ -13,13 +13,13 @@ import { useReference, ReferenceProvider } from "./Context";
13
13
 
14
14
  import * as S from "./style";
15
15
 
16
- const ReferenceFieldWithProvider = (props: IProps) => (
16
+ const ReferenceFieldWithProvider = (props: IReferenceFieldProps) => (
17
17
  <ReferenceProvider modes={props.selectionType}>
18
18
  <ReferenceField {...props} />
19
19
  </ReferenceProvider>
20
20
  );
21
21
 
22
- const ReferenceField = (props: IProps) => {
22
+ const ReferenceField = (props: IReferenceFieldProps) => {
23
23
  const {
24
24
  source,
25
25
  value,
@@ -139,9 +139,8 @@ const ReferenceField = (props: IProps) => {
139
139
  const directionLabel = state.orderDirection === "ASC" ? "(ascendent)" : "(descendent)";
140
140
  return `${orderLabel} ${directionLabel}`;
141
141
  };
142
-
143
142
  const autoData = (
144
- <S.DataWrapper>
143
+ <S.DataWrapper data-testid="auto-data-wrapper">
145
144
  <S.SourcesWrapper>
146
145
  {value &&
147
146
  value.source &&
@@ -182,7 +181,7 @@ const ReferenceField = (props: IProps) => {
182
181
  const Asterisk = () => (mandatory ? <S.Asterisk>*</S.Asterisk> : null);
183
182
 
184
183
  return (
185
- <S.Wrapper>
184
+ <S.Wrapper data-testid="reference-field-wrapper">
186
185
  <S.Label>
187
186
  Elements
188
187
  <Asterisk />
@@ -218,7 +217,7 @@ const ReferenceField = (props: IProps) => {
218
217
  );
219
218
  };
220
219
 
221
- interface IProps {
220
+ export interface IReferenceFieldProps {
222
221
  source: string[];
223
222
  value: any;
224
223
  onChange: (value: any) => void;
@@ -9,7 +9,7 @@ import { wysiwygConfig, buttons, buttonsFull } from "./config";
9
9
  import "./vendors";
10
10
  import * as S from "./style";
11
11
 
12
- const Wysiwyg = (props: IProps): JSX.Element => {
12
+ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
13
13
  const {
14
14
  value,
15
15
  error,
@@ -64,13 +64,13 @@ const Wysiwyg = (props: IProps): JSX.Element => {
64
64
  const config = { ...wysiwygConfig, ...dynamicConfig };
65
65
 
66
66
  return (
67
- <S.EditorWrapper error={error} disabled={disabled}>
67
+ <S.EditorWrapper error={error} disabled={disabled} data-testid="wysiwyg-wrapper">
68
68
  <FroalaEditor tag="textarea" model={value} config={config} onModelChange={handleChange} />
69
69
  </S.EditorWrapper>
70
70
  );
71
71
  };
72
72
 
73
- interface IWysiwygProps {
73
+ interface IProps {
74
74
  name: string;
75
75
  value: string;
76
76
  title: string;
@@ -97,6 +97,6 @@ const mapDispatchToProps = {
97
97
  uploadImage: galleryActions.uploadImage,
98
98
  };
99
99
 
100
- type IProps = IWysiwygProps & IDispatchProps;
100
+ export type IWysiwygProps = IProps & IDispatchProps;
101
101
 
102
102
  export default connect(mapStateToProps, mapDispatchToProps)(Wysiwyg);
@@ -145,10 +145,18 @@ const GalleryDetailPanel = (props: IProps) => {
145
145
  <IconAction icon="OpenOutside" size="m" onClick={handleOpenUrl} />
146
146
  </S.ImageWrapper>
147
147
  <S.ImageName>{imageSelected.name}</S.ImageName>
148
- <S.Date><strong>Uploaded:</strong> {getFormattedDateWithTimezone(imageSelected.published, "d MMM Y")}</S.Date>
149
- <S.Type><strong>Type:</strong> {getFileExtension(imageSelected.name)}</S.Type>
150
- <S.ImageSize><strong>Size:</strong> {formatBytes(imageSelected.size)}</S.ImageSize>
151
- <S.ImageDimensions><strong>Resolution:</strong> {dimensions}</S.ImageDimensions>
148
+ <S.Date>
149
+ <strong>Uploaded:</strong> {getFormattedDateWithTimezone(imageSelected.published, "d MMM Y")}
150
+ </S.Date>
151
+ <S.Type>
152
+ <strong>Type:</strong> {getFileExtension(imageSelected.name)}
153
+ </S.Type>
154
+ <S.ImageSize>
155
+ <strong>Size:</strong> {formatBytes(imageSelected.size)}
156
+ </S.ImageSize>
157
+ <S.ImageDimensions>
158
+ <strong>Resolution:</strong> {dimensions}
159
+ </S.ImageDimensions>
152
160
  </S.ImageInfoWrapper>
153
161
  <S.FormWrapper>
154
162
  <FieldsBehavior
@@ -7,8 +7,16 @@ import DetailPanel from "./DetailPanel";
7
7
  import * as S from "./style";
8
8
 
9
9
  const GalleryPanel = (props: IGalleryPanelProps) => {
10
- const { isImageSelected, imageSelected, validFormats, setImage, isGlobalTab, site, selectedTab, refreshImages } =
11
- props;
10
+ const {
11
+ isImageSelected,
12
+ imageSelected,
13
+ validFormats,
14
+ setImage,
15
+ isGlobalTab,
16
+ site,
17
+ selectedTab,
18
+ refreshImages,
19
+ } = props;
12
20
  const allowUpload = !isGlobalTab || !selectedTab;
13
21
 
14
22
  return (
@@ -56,6 +56,10 @@ const Gallery = (props: IProps): JSX.Element => {
56
56
  // eslint-disable-next-line react-hooks/exhaustive-deps
57
57
  }, [galleryScope]);
58
58
 
59
+ useEffect(() => {
60
+ setImageSelectedData(data);
61
+ }, [data]);
62
+
59
63
  useEffect(() => {
60
64
  const handleScroll = () => {
61
65
  const loadMore =
@@ -172,7 +176,7 @@ const Gallery = (props: IProps): JSX.Element => {
172
176
  <S.GridItem key={item.name + index} orientation={item.orientation}>
173
177
  <S.ImageWrapper
174
178
  onClick={handleClick(item)}
175
- selected={isSelected || selectedImage === item.id}
179
+ selected={isSelected}
176
180
  orientation={item.orientation}
177
181
  >
178
182
  <img src={item.thumb} alt={item.alt} />
@@ -208,7 +208,7 @@ const AppBar = (props: IProps): JSX.Element => {
208
208
  <S.Separator />
209
209
  </>
210
210
  )}
211
- {errors && errors.length > 0 && (
211
+ {isFromEditor && errors && errors.length > 0 && (
212
212
  <>
213
213
  <S.IconStatusWrapper>
214
214
  <FloatingMenu Button={ErrorCenterBtn} isInAppBar={true} position="left" offset={-20}>
@@ -44,6 +44,7 @@ export interface IGlobalSettings {
44
44
  siteLogoMini: string;
45
45
  welcomeText1: string;
46
46
  welcomeText2: string;
47
+ skipReviewOnPublish?: boolean;
47
48
  }
48
49
 
49
50
  export const initialState = {
@@ -67,8 +67,8 @@ function getSiteImages(props: IGetSiteImages): (dispatch: Dispatch, getState: ()
67
67
  setData({
68
68
  items: images,
69
69
  page,
70
- isImageSelected: false,
71
- imageSelected: null,
70
+ isImageSelected: data.isImageSelected,
71
+ imageSelected: data.imageSelected,
72
72
  isFinished: result.data.items.length < items,
73
73
  })
74
74
  );
@@ -86,7 +86,7 @@ function selectImage(item: IImage): (dispatch: Dispatch, getState: () => IRootSt
86
86
  const {
87
87
  gallery: { data },
88
88
  } = getState();
89
- if (data.imageSelected === item) {
89
+ if (data?.imageSelected?.id === item.id) {
90
90
  dispatch(setData({ ...data, imageSelected: null, isImageSelected: false }));
91
91
  } else {
92
92
  dispatch(setData({ ...data, imageSelected: item, isImageSelected: true }));
@@ -147,10 +147,16 @@ function updateImage(
147
147
  }
148
148
 
149
149
  function deleteImage(imageID: number): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
150
- return async (dispatch) => {
150
+ return async (dispatch, getState) => {
151
151
  try {
152
+ const {
153
+ gallery: { data },
154
+ } = getState();
152
155
  const responseActions = {
153
- handleSuccess: () => true,
156
+ handleSuccess: () => {
157
+ dispatch(setData({ ...data, isImageSelected: false, imageSelected: null }));
158
+ return true;
159
+ },
154
160
  handleError: (response: any) => {
155
161
  appActions.handleError(response)(dispatch);
156
162
  return false;
@@ -573,7 +573,7 @@ function duplicateModule(editorID: number, key?: string): (dispatch: Dispatch, g
573
573
  function moveModule(
574
574
  elementID: number,
575
575
  content: any,
576
- isPush: boolean,
576
+ newIndex: number,
577
577
  key: string
578
578
  ): (dispatch: Dispatch, getState: any) => void {
579
579
  return async (dispatch, getState) => {
@@ -584,7 +584,7 @@ function moveModule(
584
584
 
585
585
  const contentElements = [...selectedContent[key]];
586
586
  const { element: selectedModule } = findByEditorID(editorContent, selectedContent.editorID);
587
- selectedModule[key] = moveElement(elementID, contentElements, isPush);
587
+ selectedModule[key] = moveElement(elementID, contentElements, newIndex);
588
588
 
589
589
  generateContent(editorContent, dispatch, getState);
590
590
  } catch {
@@ -525,6 +525,9 @@ function updatePageStatus(
525
525
  try {
526
526
  dispatch(setIsSaving(true));
527
527
  const {
528
+ app: {
529
+ globalSettings: { skipReviewOnPublish },
530
+ },
528
531
  pageEditor: {
529
532
  editorContent: { header, footer },
530
533
  },
@@ -532,7 +535,7 @@ function updatePageStatus(
532
535
  } = getState();
533
536
 
534
537
  const pagesWithErrors: number[] = [];
535
- if (status === pageStatus.UPLOAD_PENDING && updatedFromList) {
538
+ if (status === pageStatus.UPLOAD_PENDING && updatedFromList && !skipReviewOnPublish && ids.length > 1) {
536
539
  const getPagesParams = {
537
540
  siteID: currentSiteInfo?.id,
538
541
  filterPages: ids,
@@ -829,7 +832,10 @@ function copyModule(editorID: number): (dispatch: Dispatch, getState: any) => bo
829
832
  };
830
833
  }
831
834
 
832
- function pasteModule(editorID: number): (dispatch: Dispatch, getState: any) => Promise<{ error?: INotification }> {
835
+ function pasteModule(
836
+ editorID: number,
837
+ key: string
838
+ ): (dispatch: Dispatch, getState: any) => Promise<{ error?: INotification }> {
833
839
  return async (dispatch, getState) => {
834
840
  const {
835
841
  pageEditor: { moduleCopy },
@@ -873,7 +879,7 @@ function pasteModule(editorID: number): (dispatch: Dispatch, getState: any) => P
873
879
  }
874
880
  }
875
881
 
876
- const itemsArr = originalElement.modules;
882
+ const itemsArr = originalElement[key];
877
883
  itemsArr.push(validatedModuleCopy);
878
884
 
879
885
  const updatedPageContent = {
@@ -884,7 +890,7 @@ function pasteModule(editorID: number): (dispatch: Dispatch, getState: any) => P
884
890
 
885
891
  const { sections: generatedSections } = getStateValues(getState);
886
892
  const { element: generatedElement } = findByEditorID(generatedSections, editorID);
887
- const pastedEditorID = generatedElement.modules[itemsArr.length - 1].editorID;
893
+ const pastedEditorID = generatedElement[key][itemsArr.length - 1].editorID;
888
894
 
889
895
  localStorage.setItem("selectedID", `${pastedEditorID}`);
890
896
  return { error };
@@ -1059,7 +1065,7 @@ function resetPageEditor(): (dispatch: Dispatch) => Promise<void> {
1059
1065
  function moveElement(
1060
1066
  elementID: number,
1061
1067
  content: any,
1062
- isPush: boolean,
1068
+ newIndex: number,
1063
1069
  key: string
1064
1070
  ): (dispatch: Dispatch, getState: any) => void {
1065
1071
  return async (dispatch, getState) => {
@@ -1069,7 +1075,7 @@ function moveElement(
1069
1075
  elementID,
1070
1076
  content,
1071
1077
  selectedContent,
1072
- isPush,
1078
+ newIndex,
1073
1079
  page: editorContent.editorContent,
1074
1080
  key,
1075
1081
  });
@@ -1106,6 +1112,8 @@ function validatePage(publish?: boolean): (dispatch: Dispatch, getState: any) =>
1106
1112
  return async (dispatch, getState) => {
1107
1113
  try {
1108
1114
  const { editorContent } = getStateValues(getState);
1115
+ const { component } = editorContent;
1116
+ const isGlobalPage = component === "GlobalPage";
1109
1117
  const content = deepClone(editorContent);
1110
1118
 
1111
1119
  const {
@@ -1133,7 +1141,10 @@ function validatePage(publish?: boolean): (dispatch: Dispatch, getState: any) =>
1133
1141
  const fieldErrors = findFieldsErrors(content);
1134
1142
 
1135
1143
  errors = [...errors, ...fieldErrors];
1136
- const packagesActivationErrors = findPackagesActivationErrors(pageEditor, modules, templates);
1144
+ let packagesActivationErrors;
1145
+ if (!isGlobalPage) {
1146
+ packagesActivationErrors = findPackagesActivationErrors(pageEditor, modules, templates);
1147
+ }
1137
1148
  errors = packagesActivationErrors ? [...errors, packagesActivationErrors] : errors;
1138
1149
 
1139
1150
  let warnings: IErrorItem[] = [];
@@ -150,11 +150,10 @@ const updateByEditorID = (content: any, editorID: number, contentKey: string, va
150
150
  return content;
151
151
  };
152
152
 
153
- const moveElement = (elementID: number, arr: any[], isPush: boolean, idKey = "editorID") => {
153
+ const moveElement = (elementID: number, arr: any[], newIndex: number, idKey = "editorID") => {
154
154
  const arrCopy = [...arr];
155
155
  if (arrCopy.length <= 1) return arrCopy;
156
156
  const elementIndex = arrCopy.findIndex((el: any) => el[idKey] === elementID);
157
- const newIndex = isPush ? elementIndex + 1 : elementIndex - 1;
158
157
  const element = { ...arrCopy[elementIndex] };
159
158
  arrCopy.splice(elementIndex, 1);
160
159
  arrCopy.splice(newIndex, 0, element);
@@ -163,12 +162,12 @@ const moveElement = (elementID: number, arr: any[], isPush: boolean, idKey = "ed
163
162
  };
164
163
 
165
164
  const moveModule = (params: IMoveElementParams) => {
166
- const { elementID, content, selectedContent, isPush, page, key } = params;
165
+ const { elementID, content, selectedContent, newIndex, page, key } = params;
167
166
  const isPage = ["Page", "GlobalPage"].includes(selectedContent.component);
168
167
  let newContent;
169
168
  if (isPage) {
170
169
  const { modules } = content;
171
- const newModules = moveElement(elementID, modules, isPush);
170
+ const newModules = moveElement(elementID, modules, newIndex);
172
171
  const { template } = selectedContent;
173
172
  const selectedSection = Object.keys(template).find(
174
173
  (key: string) => template[key] && template[key].editorID === content.editorID
@@ -189,7 +188,7 @@ const moveModule = (params: IMoveElementParams) => {
189
188
  const contentElements = content[key];
190
189
  const { template } = page;
191
190
  const { element: selectedModule } = findByEditorID(page, selectedContent.editorID);
192
- selectedModule[key] = moveElement(elementID, contentElements, isPush);
191
+ selectedModule[key] = moveElement(elementID, contentElements, newIndex);
193
192
 
194
193
  newContent = {
195
194
  ...page,
@@ -216,7 +215,7 @@ function replaceElements(elements: any, elemType: string): any {
216
215
  interface IMoveElementParams {
217
216
  elementID: number;
218
217
  content: any;
219
- isPush: boolean;
218
+ newIndex: number;
220
219
  selectedContent: any;
221
220
  page: any;
222
221
  key: string;
@@ -1,10 +1,9 @@
1
1
  const isEmptyArray = (arr: any[]) => arr && arr.length === 0;
2
2
 
3
- const moveArrayElement = (element: unknown, arr: unknown[], isPush: boolean): Array<unknown> => {
3
+ const moveArrayElement = (element: unknown, arr: unknown[], newIndex: number): Array<unknown> => {
4
4
  const arrCopy = [...arr];
5
5
  if (arrCopy.length <= 1) return arrCopy;
6
6
  const elementIndex = arrCopy.findIndex((el: unknown) => el === element);
7
- const newIndex = isPush ? elementIndex + 1 : elementIndex - 1;
8
7
  const el = arrCopy[elementIndex];
9
8
  arrCopy.splice(elementIndex, 1);
10
9
  arrCopy.splice(newIndex, 0, el);
@@ -37,6 +37,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
37
37
  addCategoryColors,
38
38
  dataPacks,
39
39
  sites,
40
+ skipReview,
40
41
  } = props;
41
42
  const { isSelected, siteLanguages, page, lang, isDuplicable } = item;
42
43
  const {
@@ -49,6 +50,8 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
49
50
  setCurrentPageID,
50
51
  languageActions,
51
52
  duplicatePage,
53
+ validatePage,
54
+ getPage,
52
55
  removePageFromSite,
53
56
  deleteBulk,
54
57
  getDataPack,
@@ -181,8 +184,13 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
181
184
  };
182
185
 
183
186
  const publishPage = async () => {
184
- await updatePageStatus([page.id], pageStatus.UPLOAD_PENDING, true);
185
- await getSiteContent();
187
+ await getPage(page.id);
188
+ const isValidated = skipReview ? true : await validatePage(true);
189
+
190
+ if (isValidated) {
191
+ await updatePageStatus([page.id], pageStatus.UPLOAD_PENDING, true);
192
+ await getSiteContent();
193
+ }
186
194
  };
187
195
 
188
196
  const unpublishPage = async () => {
@@ -630,6 +638,8 @@ interface IPageItemProps {
630
638
  setTemplateInstanceError(error: any): void;
631
639
  getDataPack: (id: string) => Promise<void>;
632
640
  toggleCopiedToast(): void;
641
+ getPage(pageID?: number, global?: boolean): Promise<void>;
642
+ validatePage(publish?: boolean, browserRef?: any, currentPage?: IPage): Promise<boolean>;
633
643
  };
634
644
  activatedTemplates: any[];
635
645
  toggleToast(): void;
@@ -640,6 +650,7 @@ interface IPageItemProps {
640
650
  addCategoryColors(cats: string[]): void;
641
651
  dataPacks: IDataPack[];
642
652
  sites: ISite[];
653
+ skipReview?: boolean;
643
654
  }
644
655
 
645
656
  export default memo(PageItem);