@griddo/ax 10.4.9 → 10.4.11

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.
@@ -25,7 +25,7 @@ const createConfig = ({ input, output }) => ({
25
25
  loader: "babel-loader",
26
26
  options: {
27
27
  presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"],
28
- plugins: ["@babel/plugin-proposal-optional-chaining"],
28
+ plugins: ["@babel/plugin-transform-optional-chaining"],
29
29
  },
30
30
  },
31
31
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "10.4.9",
4
+ "version": "10.4.11",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@atlaskit/tree": "^8.2.0",
43
- "@babel/plugin-proposal-optional-chaining": "^7.21.0",
43
+ "@babel/plugin-transform-optional-chaining": "7.24.5",
44
44
  "@babel/preset-react": "^7.14.5",
45
45
  "@pmmmwh/react-refresh-webpack-plugin": "0.5.0-rc.0",
46
46
  "@styled-system/prop-types": "5.1.2",
@@ -231,5 +231,5 @@
231
231
  "publishConfig": {
232
232
  "access": "public"
233
233
  },
234
- "gitHead": "798ce16f44f99f35c4eb372b04e54f8a1f433469"
234
+ "gitHead": "54e83b84007b55a6718857be0e37591b774448ff"
235
235
  }
@@ -128,7 +128,8 @@ describe("ComponentArraySelector component rendering", () => {
128
128
  mixableProps.field = {
129
129
  contentType: "modules",
130
130
  key: "mainContent",
131
- title: "", type: ""
131
+ title: "",
132
+ type: "",
132
133
  };
133
134
  mixableProps.objKey = "elements";
134
135
  mixableProps.disabled = false;
@@ -211,34 +212,36 @@ describe("ComponentArraySelector component rendering", () => {
211
212
  const actualDate = new Date();
212
213
  mixableProps.moduleCopy = {
213
214
  date: actualDate,
214
- elements: [{
215
- editorID: 1,
216
- component: "ArticlesDistributor",
217
- anchorID: null,
218
- verticalSpacing: "medium",
219
- hasDistributorData: true,
220
- data: {
221
- mode: "auto",
222
- source: ["ARTICLES"],
223
- order: "alpha",
224
- quantity: 2,
225
- },
226
- layout: "L001",
227
- title: {
228
- content: "Title",
229
- tag: "h2",
230
- },
231
- subtitle: "subtitle",
232
- detail: "detail",
233
- primaryLinkStyle: "primary",
234
- groupingLink: {
235
- component: "Link",
236
- text: "View all",
237
- style: "secondary",
238
- editorID: 40,
239
- parentEditorID: 39,
215
+ elements: [
216
+ {
217
+ editorID: 1,
218
+ component: "ArticlesDistributor",
219
+ anchorID: null,
220
+ verticalSpacing: "medium",
221
+ hasDistributorData: true,
222
+ data: {
223
+ mode: "auto",
224
+ source: ["ARTICLES"],
225
+ order: "alpha",
226
+ quantity: 2,
227
+ },
228
+ layout: "L001",
229
+ title: {
230
+ content: "Title",
231
+ tag: "h2",
232
+ },
233
+ subtitle: "subtitle",
234
+ detail: "detail",
235
+ primaryLinkStyle: "primary",
236
+ groupingLink: {
237
+ component: "Link",
238
+ text: "View all",
239
+ style: "secondary",
240
+ editorID: 40,
241
+ parentEditorID: 39,
242
+ },
240
243
  },
241
- }],
244
+ ],
242
245
  };
243
246
  mixableProps.mandatory = true;
244
247
 
@@ -26,6 +26,7 @@ const mockedFunction = defaultProps.pasteModule as jest.MockedFunction<
26
26
  >;
27
27
  const mockedNotification = defaultProps.setNotification as jest.MockedFunction<(notification: INotification) => void>;
28
28
  defaultProps.editorID = 1;
29
+ defaultProps.modulesToPaste = [{ editorID: 1, component: "BasicContent" }];
29
30
 
30
31
  describe("ComponentArraySelector component rendering", () => {
31
32
  test("should render the component", async () => {
@@ -92,7 +93,7 @@ describe("ComponentArraySelector trigger events", () => {
92
93
  });
93
94
  expect(mockedFunction).toHaveBeenCalled();
94
95
  await expect(mockedFunction(1, "", [{ editorID: 1, component: "BasicContent" }])).resolves.toEqual({});
95
- expect(screen.getByTestId("toast-message")).toHaveTextContent("Module pasted from clipboard");
96
+ expect(screen.getByTestId("toast-message")).toHaveTextContent("1 module pasted from clipboard");
96
97
  });
97
98
 
98
99
  test("should trigger the onClick and resolve promise without error", async () => {
@@ -110,7 +111,9 @@ describe("ComponentArraySelector trigger events", () => {
110
111
  fireEvent.click(iconButton);
111
112
  });
112
113
  expect(mockedFunction).toHaveBeenCalled();
113
- await expect(mockedFunction(1, "", [{ editorID: 1, component: "BasicContent" }])).resolves.toEqual({ error: undefined });
114
- expect(screen.getByTestId("toast-message")).toHaveTextContent("Module pasted from clipboard");
114
+ await expect(mockedFunction(1, "", [{ editorID: 1, component: "BasicContent" }])).resolves.toEqual({
115
+ error: undefined,
116
+ });
117
+ expect(screen.getByTestId("toast-message")).toHaveTextContent("1 module pasted from clipboard");
115
118
  });
116
119
  });
@@ -16,7 +16,7 @@ const PasteModuleButton = (props: IPasteModuleProps): JSX.Element => {
16
16
  slots,
17
17
  } = props;
18
18
 
19
- const { isVisible, toggleToast, setIsVisible } = useToast();
19
+ const { isVisible, toggleToast, setIsVisible, state: toastState } = useToast();
20
20
 
21
21
  const handlePasteModule = async () => {
22
22
  if (isModuleCopyUnavailable) {
@@ -41,7 +41,7 @@ const PasteModuleButton = (props: IPasteModuleProps): JSX.Element => {
41
41
  const { type, text } = pasteResult.error;
42
42
  setNotification && setNotification({ type, text });
43
43
  } else {
44
- toggleToast();
44
+ toggleToast(`${modulesToPaste.length} module${modulesToPaste.length > 1 ? "s" : ""} pasted from clipboard`);
45
45
  }
46
46
  }
47
47
  };
@@ -51,7 +51,7 @@ const PasteModuleButton = (props: IPasteModuleProps): JSX.Element => {
51
51
  <Tooltip content="Paste from clipboard">
52
52
  <IconAction icon="paste" onClick={handlePasteModule} />
53
53
  </Tooltip>
54
- {isVisible && <Toast message="Module pasted from clipboard" setIsVisible={setIsVisible} />}
54
+ {isVisible && <Toast message={`${toastState}`} setIsVisible={setIsVisible} />}
55
55
  </>
56
56
  );
57
57
  };
@@ -187,9 +187,9 @@ const MixableComponentArray = (props: IMixableComponentArrayProps): JSX.Element
187
187
  const onBeforeCapture = (start: BeforeCapture) => setDraggingId(parseInt(start.draggableId));
188
188
 
189
189
  const handleCopy = () => {
190
- const isCopied = copyModuleAction && copyModuleAction(selectedItems.all);
191
- isCopied &&
192
- toggleToast(`${selectedItems.all.length} module${selectedItems.all.length > 1 ? "s" : ""} copied to clipboard`);
190
+ const modulesCopied = copyModuleAction && copyModuleAction(selectedItems.all);
191
+ modulesCopied &&
192
+ toggleToast(`${modulesCopied} module${selectedItems.all.length > 1 ? "s" : ""} copied to clipboard`);
193
193
  };
194
194
 
195
195
  const handleDuplicate = () => {
@@ -318,7 +318,7 @@ export interface IMixableComponentArrayProps {
318
318
  addModuleAction: (moduleType: string, key: string, selectedID: number, isComponentModule?: boolean) => void;
319
319
  deleteModuleAction: (editorID: number[], key?: string) => void;
320
320
  duplicateModuleAction: (editorID: number[], key?: string) => number;
321
- copyModuleAction: (editorID: number[]) => boolean;
321
+ copyModuleAction: (editorID: number[]) => boolean | number;
322
322
  replaceElementsInCollectionAction: (newValue: string, reference?: string) => void;
323
323
  moveModuleAction: (moduleID: number[], selectedContent: any, newIndex: number, key: string) => void;
324
324
  pasteModuleAction: (editorID: number, key: string, modulesToPaste: IModule[]) => Promise<{ error?: INotification }>;
@@ -234,7 +234,7 @@ export interface IComponentContainerProps {
234
234
  addComponentAction: (componentType: any, key?: string) => void;
235
235
  deleteModuleAction: (editorID: number[], key?: string) => void;
236
236
  duplicateModuleAction: (editorID: number[], key?: string) => number;
237
- copyModuleAction: (editorID: number[]) => boolean;
237
+ copyModuleAction: (editorID: number[]) => boolean | number;
238
238
  replaceModuleAction: (module: any, parent: any, objKey: string) => void;
239
239
  };
240
240
  disabled?: boolean;
@@ -139,6 +139,7 @@ const Title = styled.span<{ disabled?: boolean }>`
139
139
  const HandleWrapper = styled.div<{ hidden?: boolean }>`
140
140
  display: ${(p) => (p.hidden ? "none" : "flex")};
141
141
  align-items: center;
142
+ z-index: 1;
142
143
  `;
143
144
 
144
145
  const IconHandleWrapper = styled.div`
@@ -1,8 +1,9 @@
1
- import React from "react";
1
+ import React, { useState } from "react";
2
2
  import { withRouter, RouteComponentProps } from "react-router-dom";
3
3
 
4
4
  import { IErrorItem } from "@ax/types";
5
5
  import { trimText } from "@ax/helpers";
6
+ import { useModal } from "@ax/hooks";
6
7
  import {
7
8
  Button,
8
9
  Icon,
@@ -14,6 +15,7 @@ import {
14
15
  Tooltip,
15
16
  ErrorCenter,
16
17
  SearchField,
18
+ Modal,
17
19
  } from "@ax/components";
18
20
 
19
21
  import { ActionMenu, ActionSimpleMenu, DownArrowButton } from "./atoms";
@@ -47,6 +49,7 @@ const AppBar = (props: IProps): JSX.Element => {
47
49
  searchFilters,
48
50
  hasAnimation,
49
51
  searchValue,
52
+ isDirty,
50
53
  } = props;
51
54
 
52
55
  const publishedTooltip: any = {
@@ -59,6 +62,9 @@ const AppBar = (props: IProps): JSX.Element => {
59
62
 
60
63
  const fixedClass = fixedAppBar ? "fixed" : "";
61
64
 
65
+ const { isOpen, toggleModal } = useModal();
66
+ const [langSelected, setLangSelected] = useState(language);
67
+
62
68
  const goToPages = () => {
63
69
  typeof backLink === "string" ? props.history.push(backLink, { isFromEditor }) : props.history.goBack();
64
70
  };
@@ -67,6 +73,11 @@ const AppBar = (props: IProps): JSX.Element => {
67
73
  pageLanguages && pageLanguages.find((pageLang: any) => pageLang.locale === lang);
68
74
 
69
75
  const handleLanguage = (item: any) => async () => {
76
+ if (langSelected && langSelected.locale !== item.locale && isDirty) {
77
+ setLangSelected(item);
78
+ toggleModal();
79
+ return;
80
+ }
70
81
  if (!languageActions || !languageActions.setLanguage) return;
71
82
 
72
83
  const lang = {
@@ -144,7 +155,7 @@ const AppBar = (props: IProps): JSX.Element => {
144
155
  };
145
156
 
146
157
  const PageStatus = () => {
147
- const offset = rightButton || rightLineButton ? -20 : -135;
158
+ const offset = rightButton || rightLineButton ? -20 : -135;
148
159
  return statusMenu.options && statusMenu.options.length > 0 ? (
149
160
  <FloatingMenu Button={StatusBtn} isInAppBar={true} position="left" offset={offset}>
150
161
  <ActionSimpleMenu menu={statusMenu} />
@@ -152,11 +163,40 @@ const AppBar = (props: IProps): JSX.Element => {
152
163
  ) : (
153
164
  <Icon name={pageStatus ?? "offline"} size="24" />
154
165
  );
155
- }
166
+ };
167
+
168
+ const modalText = <>If you change without saving it, it will be lost. Do you want to discard your changes?</>;
169
+
170
+ const mainAction = {
171
+ title: "Change language",
172
+ onClick: handleLanguage(langSelected),
173
+ };
174
+
175
+ const secondaryAction = {
176
+ title: "Cancel",
177
+ onClick: () => {
178
+ setLangSelected(language);
179
+ toggleModal();
180
+ },
181
+ };
182
+
183
+ const changeLanguageModalProps = {
184
+ isOpen,
185
+ hide: toggleModal,
186
+ title: "Change language",
187
+ mainAction,
188
+ secondaryAction,
189
+ };
190
+
191
+ const ChangeLanguageModal = () => (
192
+ <Modal {...changeLanguageModalProps}>
193
+ <S.ModalContent>{modalText}</S.ModalContent>
194
+ </Modal>
195
+ );
156
196
 
157
197
  const languageTooltip = typeof currentPageID === "number" && "Add language";
158
198
 
159
- const classes = hasAnimation ? `${fixedClass} ${additionalClass} animate` : `${fixedClass} ${additionalClass}`
199
+ const classes = hasAnimation ? `${fixedClass} ${additionalClass} animate` : `${fixedClass} ${additionalClass}`;
160
200
 
161
201
  return (
162
202
  <S.Header className={classes} inversed={inversed} data-testid="appbar-header">
@@ -204,14 +244,20 @@ const AppBar = (props: IProps): JSX.Element => {
204
244
  )}
205
245
  {language && (
206
246
  <>
247
+ <ChangeLanguageModal />
207
248
  <S.LanguageWrapper data-testid="language-wrapper">
208
249
  <Tooltip content={languageTooltip} hideOnClick bottom>
209
- <FloatingMenu Button={LanguageBtn} isInAppBar={true} position="left" offset={rightButton || rightLineButton ? 0 : -85}>
250
+ <FloatingMenu
251
+ Button={LanguageBtn}
252
+ isInAppBar={true}
253
+ position="left"
254
+ offset={rightButton || rightLineButton ? 0 : -85}
255
+ >
210
256
  {languageMenu}
211
257
  </FloatingMenu>
212
258
  </Tooltip>
213
259
  </S.LanguageWrapper>
214
- {(pageStatus || rightButton || rightLineButton ) && <S.Separator />}
260
+ {(pageStatus || rightButton || rightLineButton) && <S.Separator />}
215
261
  </>
216
262
  )}
217
263
  {pageStatus && (
@@ -221,7 +267,7 @@ const AppBar = (props: IProps): JSX.Element => {
221
267
  <PageStatus />
222
268
  </Tooltip>
223
269
  </S.IconStatusWrapper>
224
- {(rightButton || rightLineButton ) && <S.Separator />}
270
+ {(rightButton || rightLineButton) && <S.Separator />}
225
271
  </>
226
272
  )}
227
273
  {isFromEditor && errors && errors.length > 0 && (
@@ -297,6 +343,7 @@ export interface IAppBarProps {
297
343
  pageStatusActions?: any[];
298
344
  hasAnimation?: boolean;
299
345
  searchValue?: string;
346
+ isDirty?: boolean;
300
347
  }
301
348
 
302
349
  type IProps = IAppBarProps & RouteComponentProps;
@@ -208,6 +208,16 @@ const SearchWrapper = styled.div<{ length: number }>`
208
208
  justify-content: flex-end;
209
209
  `;
210
210
 
211
+ const ModalContent = styled.div`
212
+ padding: ${(p) => p.theme.spacing.m};
213
+ p {
214
+ margin-bottom: ${(p) => p.theme.spacing.m};
215
+ }
216
+ div {
217
+ margin-bottom: 0;
218
+ }
219
+ `;
220
+
211
221
  const StatusBtn = styled.span``;
212
222
 
213
223
  export {
@@ -233,4 +243,5 @@ export {
233
243
  TabsContent,
234
244
  StatusBtn,
235
245
  HelpText,
246
+ ModalContent,
236
247
  };
@@ -55,6 +55,7 @@ export interface IWrapperProps {
55
55
  pageStatusActions?: any[];
56
56
  hasAnimation?: boolean;
57
57
  searchValue?: string;
58
+ isDirty?: boolean;
58
59
  }
59
60
 
60
61
  export default MainWrapper;
@@ -6,7 +6,18 @@ import Button from "../Button";
6
6
  import * as S from "./style";
7
7
 
8
8
  const Modal = (props: IModalProps): JSX.Element | null => {
9
- const { isOpen, hide, children, title, size, mainAction, secondaryAction, height, overflow = "auto", isChild } = props;
9
+ const {
10
+ isOpen,
11
+ hide,
12
+ children,
13
+ title,
14
+ size,
15
+ mainAction,
16
+ secondaryAction,
17
+ height,
18
+ overflow = "auto",
19
+ isChild,
20
+ } = props;
10
21
 
11
22
  const titleContent = title ? <S.Title>{title}</S.Title> : "";
12
23
 
@@ -73,7 +84,7 @@ export interface IModalProps {
73
84
 
74
85
  interface IAction {
75
86
  title: string;
76
- onClick: () => void;
87
+ onClick: (item?: any) => void;
77
88
  disabled?: boolean;
78
89
  icon?: string;
79
90
  }
@@ -669,7 +669,7 @@ function moveModule(
669
669
  };
670
670
  }
671
671
 
672
- function copyModule(editorID: number[]): (dispatch: Dispatch, getState: any) => boolean {
672
+ function copyModule(editorID: number[]): (dispatch: Dispatch, getState: any) => boolean | number {
673
673
  return (dispatch, getState) => {
674
674
  const {
675
675
  navigation: { editorContent },
@@ -691,7 +691,7 @@ function copyModule(editorID: number[]): (dispatch: Dispatch, getState: any) =>
691
691
  elements: modulesToCopy,
692
692
  };
693
693
  dispatch(setCopyModule(payload));
694
- return true;
694
+ return payload.elements.length;
695
695
  } else {
696
696
  return false;
697
697
  }
@@ -878,7 +878,7 @@ function duplicateModule(editorID: number[], key?: string): (dispatch: Dispatch,
878
878
  };
879
879
  }
880
880
 
881
- function copyModule(editorID: number[]): (dispatch: Dispatch, getState: any) => boolean {
881
+ function copyModule(editorID: number[]): (dispatch: Dispatch, getState: any) => boolean | number {
882
882
  return (dispatch, getState) => {
883
883
  const { sections } = getStateValues(getState);
884
884
  const modulesToCopy: Record<string, unknown>[] = [];
@@ -899,7 +899,7 @@ function copyModule(editorID: number[]): (dispatch: Dispatch, getState: any) =>
899
899
 
900
900
  dispatch(setCopyModule(payload));
901
901
 
902
- return true;
902
+ return payload.elements.length;
903
903
  } else {
904
904
  return false;
905
905
  }
@@ -924,7 +924,7 @@ function pasteModule(
924
924
 
925
925
  let error: INotification | undefined;
926
926
 
927
- modulesToPaste.forEach(async (element) => {
927
+ for (const element of modulesToPaste) {
928
928
  const { isMaxModules, errorMessage } = checkMaxModules(editorContent, element.component);
929
929
  if (isMaxModules && errorMessage) {
930
930
  error = {
@@ -958,7 +958,7 @@ function pasteModule(
958
958
  }
959
959
 
960
960
  itemsArr.push(validatedModuleCopy);
961
- });
961
+ }
962
962
 
963
963
  const updatedPageContent = {
964
964
  ...editorContent,
@@ -95,7 +95,7 @@ interface IPageBrowserDispatchProps {
95
95
  moveElement(moduleID: number[], selectedContent: any, newIndex: number, key: string): void;
96
96
  replaceModule(module: any, parent: any, objKey: string): void;
97
97
  replaceElementsInCollection(newValue: string, reference: string): void;
98
- copyModule(editorID: number[]): boolean;
98
+ copyModule(editorID: number[]): boolean | number;
99
99
  pasteModule(editorID: number, key: string, modulesToPaste: IModule[]): Promise<{ error?: INotification }>;
100
100
  setNotification: (notification: INotification) => void;
101
101
  isGlobal: boolean;
@@ -346,11 +346,13 @@ const GlobalEditor = (props: IProps) => {
346
346
  }
347
347
  };
348
348
 
349
- const rightButtonProps = isAllowedToEditContentPage ? {
350
- label: isSaving ? "Saving" : getSaveLabel(),
351
- disabled: (!isDirty && pageID !== null && !isNewTranslation) || isSaving || isReadOnly,
352
- action: isPublished ? publishChanges : handleSavePage,
353
- } : undefined;
349
+ const rightButtonProps = isAllowedToEditContentPage
350
+ ? {
351
+ label: isSaving ? "Saving" : getSaveLabel(),
352
+ disabled: (!isDirty && pageID !== null && !isNewTranslation) || isSaving || isReadOnly,
353
+ action: isPublished ? publishChanges : handleSavePage,
354
+ }
355
+ : undefined;
354
356
 
355
357
  const goToLivePage = () => {
356
358
  const currentPageUrl = editorContent.fullUrl;
@@ -453,6 +455,7 @@ const GlobalEditor = (props: IProps) => {
453
455
  isFromEditor={true}
454
456
  pageStatusActions={pageStatusActions}
455
457
  tabs={tabsPreview}
458
+ isDirty={isDirty}
456
459
  >
457
460
  {selectedTab === "edit" ? (
458
461
  <>
@@ -463,12 +466,7 @@ const GlobalEditor = (props: IProps) => {
463
466
  )}
464
467
  {sitePageID && (
465
468
  <S.NotificationWrapper>
466
- <Notification
467
- type="info"
468
- text={globalNotificationText}
469
- btnText="Go Back"
470
- onClick={goBackToSite}
471
- />
469
+ <Notification type="info" text={globalNotificationText} btnText="Go Back" onClick={goBackToSite} />
472
470
  </S.NotificationWrapper>
473
471
  )}
474
472
  {notification && (
@@ -92,7 +92,7 @@ interface IPageBrowserDispatchProps {
92
92
  replaceModule(module: any, parent: any, objKey: string): void;
93
93
  replaceElementsInCollection(newValue: string, reference: string): void;
94
94
  moveModule(moduleID: number[], selectedContent: any, newIndex: number, key: string): void;
95
- copyModule(editorID: number[]): boolean;
95
+ copyModule(editorID: number[]): boolean | number;
96
96
  pasteModule(editorID: number, key: string, modulesToPaste: IModule[]): Promise<{ error?: INotification }>;
97
97
  setNotification: (notification: INotification) => void;
98
98
  browserRef?: React.RefObject<HTMLDivElement>;
@@ -133,7 +133,7 @@ interface IPageBrowserDispatchProps {
133
133
  setHistoryPush(path: string, isEditor: boolean): void;
134
134
  getGlobalFromLocalPage(): void;
135
135
  saveCurrentSiteInfo(): void;
136
- copyModule(editorID: number[]): boolean;
136
+ copyModule(editorID: number[]): boolean | number;
137
137
  pasteModule(editorID: number, key: string, modulesToPaste: IModule[]): Promise<{ error?: INotification }>;
138
138
  setNotification: (notification: INotification) => void;
139
139
  restorePageNavigation: (key: string) => void;
@@ -513,6 +513,7 @@ const PageEditor = (props: IProps) => {
513
513
  errorActions={{ goToError, goToPackage: handleClickNotification }}
514
514
  isFromEditor={true}
515
515
  tabs={tabsPreview}
516
+ isDirty={isDirty}
516
517
  >
517
518
  {selectedTab === "edit" ? (
518
519
  <>