@griddo/ax 12.7.0 → 12.8.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 (52) hide show
  1. package/package.json +2 -2
  2. package/src/GlobalStore.tsx +3 -1
  3. package/src/__tests__/components/ConfigPanel/GlobalPageForm/GlobalPageForm.test.tsx +7 -3
  4. package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.style.test.tsx +44 -0
  5. package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.test.tsx +109 -0
  6. package/src/__tests__/components/Nav/Nav.test.tsx +75 -2
  7. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +50 -21
  8. package/src/__tests__/components/Toast/Toast.test.tsx +37 -1
  9. package/src/__tests__/hooks/broadcast.test.tsx +263 -0
  10. package/src/api/utils.tsx +7 -6
  11. package/src/components/Browser/index.tsx +10 -2
  12. package/src/components/ConfigPanel/GlobalPageForm/index.tsx +3 -4
  13. package/src/components/Fields/Wysiwyg/atoms.tsx +1 -1
  14. package/src/components/Fields/Wysiwyg/index.tsx +28 -0
  15. package/src/components/Fields/Wysiwyg/style.tsx +41 -1
  16. package/src/components/MainWrapper/index.tsx +14 -2
  17. package/src/components/Nav/index.tsx +21 -6
  18. package/src/components/ResizePanel/index.tsx +8 -3
  19. package/src/components/Toast/index.tsx +4 -1
  20. package/src/containers/ActivityLog/actions.tsx +4 -7
  21. package/src/containers/PageEditor/actions.tsx +14 -3
  22. package/src/helpers/containerEvaluations.tsx +8 -0
  23. package/src/hooks/broadcast.ts +160 -0
  24. package/src/hooks/index.tsx +5 -0
  25. package/src/modules/ActivityLog/index.tsx +1 -0
  26. package/src/modules/Analytics/index.tsx +1 -1
  27. package/src/modules/App/index.tsx +21 -6
  28. package/src/modules/Content/PageItem/index.tsx +36 -19
  29. package/src/modules/Content/index.tsx +5 -7
  30. package/src/modules/FileDrive/FileModal/DetailPanel/UsageContent/index.tsx +5 -14
  31. package/src/modules/FileDrive/index.tsx +1 -1
  32. package/src/modules/Forms/FormUseModal/index.tsx +3 -8
  33. package/src/modules/GlobalEditor/atoms.tsx +35 -1
  34. package/src/modules/GlobalEditor/index.tsx +169 -51
  35. package/src/modules/GlobalSettings/Robots/index.tsx +1 -1
  36. package/src/modules/MediaGallery/ImageModal/DetailPanel/UsageContent/index.tsx +6 -15
  37. package/src/modules/MediaGallery/index.tsx +1 -1
  38. package/src/modules/PageEditor/atoms.tsx +35 -1
  39. package/src/modules/PageEditor/index.tsx +168 -48
  40. package/src/modules/Redirects/index.tsx +1 -0
  41. package/src/modules/Settings/Languages/index.tsx +1 -1
  42. package/src/modules/Settings/SeoAnalyticsSettings/Analytics/index.tsx +1 -0
  43. package/src/modules/Settings/Social/index.tsx +1 -1
  44. package/src/modules/StructuredData/Form/index.tsx +108 -10
  45. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +31 -11
  46. package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +17 -3
  47. package/src/modules/StructuredData/StructuredDataList/index.tsx +6 -4
  48. package/src/modules/StructuredData/atoms.tsx +35 -1
  49. package/src/routes/multisite.tsx +34 -18
  50. package/src/routes/site.tsx +40 -21
  51. package/src/storeRegistry.ts +5 -0
  52. package/src/modules/StructuredData/index.tsx +0 -18
@@ -1,4 +1,5 @@
1
1
  import type { IComponent, IContainedComponent, IContainerEvaluation } from "@ax/types";
2
+
2
3
  import { getDisplayName } from "./schemas";
3
4
 
4
5
  const isComponentEmpty = (component: IComponent): boolean => {
@@ -58,6 +59,13 @@ const areEqual = (prevProps: any, newProps: any): boolean => {
58
59
  return false;
59
60
  }
60
61
 
62
+ // Re-render si moduleCopy cambia (e.g. cross-tab clipboard broadcast)
63
+ const prevModuleCopyDate = prevProps.moduleCopy?.date?.getTime?.();
64
+ const newModuleCopyDate = newProps.moduleCopy?.date?.getTime?.();
65
+ if (prevModuleCopyDate !== newModuleCopyDate) {
66
+ return false;
67
+ }
68
+
61
69
  const {
62
70
  selectedContent: { type },
63
71
  } = prevProps;
@@ -0,0 +1,160 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+
4
+ import { SET_COPY_MODULE } from "@ax/containers/PageEditor/constants";
5
+ import type { IRootState } from "@ax/types";
6
+
7
+ // Types
8
+ interface BroadcastMessage {
9
+ type: string;
10
+ payload?: any;
11
+ timestamp?: number;
12
+ senderId?: string;
13
+ }
14
+
15
+ interface ContentUpdateMessage {
16
+ contentID: number;
17
+ timestamp: number;
18
+ }
19
+
20
+ // Unique ID for this tab instance to filter own broadcasts
21
+ const TAB_ID = Math.random().toString(36).substring(2, 11);
22
+
23
+ // Generic Broadcast Channel wrapper
24
+ const useBroadcastChannel = (channelName: string, onMessage: (message: BroadcastMessage) => void) => {
25
+ const channelRef = useRef<BroadcastChannel | null>(null);
26
+ const onMessageRef = useRef(onMessage);
27
+
28
+ useEffect(() => {
29
+ onMessageRef.current = onMessage;
30
+ }, [onMessage]);
31
+
32
+ useEffect(() => {
33
+ const channel = new BroadcastChannel(channelName);
34
+ channelRef.current = channel;
35
+
36
+ const handleMessage = (event: MessageEvent<BroadcastMessage>) => {
37
+ onMessageRef.current(event.data);
38
+ };
39
+
40
+ channel.addEventListener("message", handleMessage);
41
+
42
+ return () => {
43
+ channel.removeEventListener("message", handleMessage);
44
+ channel.close();
45
+ };
46
+ }, [channelName]);
47
+
48
+ const postMessage = useCallback((message: BroadcastMessage) => {
49
+ if (channelRef.current) {
50
+ channelRef.current.postMessage({
51
+ ...message,
52
+ timestamp: Date.now(),
53
+ });
54
+ }
55
+ }, []);
56
+
57
+ return { postMessage };
58
+ };
59
+
60
+ // Broadcast clipboard (module copy) across tabs
61
+ const useBroadcastClipboard = () => {
62
+ const dispatch = useDispatch();
63
+ const channelName = "griddo-clipboard";
64
+ const moduleCopy = useSelector((state: IRootState) => state.pageEditor.moduleCopy);
65
+ const isFromBroadcastRef = useRef(false);
66
+
67
+ const handleMessage = useCallback(
68
+ (message: any) => {
69
+ if (message.senderId === TAB_ID) {
70
+ return;
71
+ }
72
+ if (message.type === "module-copied" && message.payload) {
73
+ isFromBroadcastRef.current = true;
74
+ const moduleDataWithDate = {
75
+ ...message.payload,
76
+ date: new Date(message.payload.date),
77
+ };
78
+ dispatch({
79
+ type: SET_COPY_MODULE,
80
+ payload: { moduleCopy: moduleDataWithDate },
81
+ });
82
+ }
83
+ },
84
+ [dispatch],
85
+ );
86
+
87
+ const { postMessage } = useBroadcastChannel(channelName, handleMessage);
88
+
89
+ useEffect(() => {
90
+ if (moduleCopy) {
91
+ if (isFromBroadcastRef.current) {
92
+ isFromBroadcastRef.current = false;
93
+ return;
94
+ }
95
+ postMessage({
96
+ type: "module-copied",
97
+ payload: moduleCopy,
98
+ senderId: TAB_ID,
99
+ });
100
+ }
101
+ }, [moduleCopy, postMessage]);
102
+
103
+ return {};
104
+ };
105
+
106
+ // Broadcast content updates (page/data saves) across tabs
107
+ const useBroadcastContentUpdate = (contentID: number | null) => {
108
+ const [remotePageUpdate, setRemotePageUpdate] = useState<ContentUpdateMessage | null>(null);
109
+
110
+ const handleMessage = useCallback(
111
+ (message: any) => {
112
+ if (message.type === "page-saved" && message.payload?.contentID === contentID) {
113
+ setRemotePageUpdate(message.payload);
114
+ }
115
+ },
116
+ [contentID],
117
+ );
118
+
119
+ const { postMessage } = useBroadcastChannel("griddo-page-updates", handleMessage);
120
+
121
+ const broadcastPageSave = useCallback(
122
+ (id: number) => {
123
+ postMessage({
124
+ type: "page-saved",
125
+ payload: { contentID: id },
126
+ });
127
+ },
128
+ [postMessage],
129
+ );
130
+
131
+ return { broadcastPageSave, remotePageUpdate, clearRemoteUpdate: () => setRemotePageUpdate(null) };
132
+ };
133
+
134
+ // Broadcast logout across tabs.
135
+ // The logout action is injected by the consumer so this generic hook does not depend on
136
+ // the App container (which would create a load-time circular dependency: hooks → App → … → hooks).
137
+ const useBroadcastLogout = (onRemoteLogout?: () => void) => {
138
+ const channelName = "griddo-logout";
139
+
140
+ const handleMessage = useCallback(
141
+ (message: any) => {
142
+ if (message.type === "user-logout") {
143
+ onRemoteLogout?.();
144
+ }
145
+ },
146
+ [onRemoteLogout],
147
+ );
148
+
149
+ const { postMessage } = useBroadcastChannel(channelName, handleMessage);
150
+
151
+ const broadcastLogout = useCallback(() => {
152
+ postMessage({
153
+ type: "user-logout",
154
+ });
155
+ }, [postMessage]);
156
+
157
+ return { broadcastLogout };
158
+ };
159
+
160
+ export { useBroadcastChannel, useBroadcastClipboard, useBroadcastContentUpdate, useBroadcastLogout };
@@ -1,3 +1,4 @@
1
+ import { useBroadcastChannel, useBroadcastClipboard, useBroadcastContentUpdate, useBroadcastLogout } from "./broadcast";
1
2
  import { type IBulkSelectedItems, useBulkSelection } from "./bulk";
2
3
  import { useAdaptiveText, useCategoryColors, useEmptyState } from "./content";
3
4
  import {
@@ -25,6 +26,10 @@ import { useFirefoxScrollLock, useWindowSize } from "./window";
25
26
 
26
27
  export {
27
28
  useAdaptiveText,
29
+ useBroadcastChannel,
30
+ useBroadcastClipboard,
31
+ useBroadcastLogout,
32
+ useBroadcastContentUpdate,
28
33
  useBulkSelection,
29
34
  useCategoryColors,
30
35
  useContextMenu,
@@ -211,6 +211,7 @@ const ActivityLog = (props: IActivityLogProps) => {
211
211
  return (
212
212
  <MainWrapper
213
213
  title="Logs"
214
+ screen="activity-log"
214
215
  rightButton={rightButtonProps}
215
216
  rightLineButton={rightLineButtonProps}
216
217
  searchAction={handleSearch}
@@ -123,7 +123,7 @@ const Analytics = (props: IProps): JSX.Element => {
123
123
  return (
124
124
  <>
125
125
  <RouteLeavingGuard when={isDirty} action={setRoute} text={modalText} />
126
- <MainWrapper backLink={false} title="Analytics Settings" rightButton={rightButtonProps}>
126
+ <MainWrapper backLink={false} title="Analytics Settings" rightButton={rightButtonProps} screen="analytics-global">
127
127
  <ErrorToast />
128
128
  <S.Wrapper>
129
129
  <S.FormWrapper>
@@ -1,15 +1,25 @@
1
- import React from "react";
2
- import { connect } from "react-redux";
1
+ import { useEffect } from "react";
3
2
  import { ErrorBoundary } from "react-error-boundary";
3
+ import { connect, useSelector } from "react-redux";
4
4
 
5
- import type { IRootState } from "@ax/types";
6
5
  import { ErrorPage, Loading } from "@ax/components";
6
+ import { appActions } from "@ax/containers/App";
7
+ import { useBroadcastLogout } from "@ax/hooks";
8
+ import type { IRootState } from "@ax/types";
7
9
 
8
- import Routing from "./Routing";
9
10
  import Style from "../../Style";
11
+ import Routing from "./Routing";
10
12
 
11
13
  const App = (props: IProps) => {
12
- const { isRehydrated } = props;
14
+ const { isRehydrated, logoutAndNavigate } = props;
15
+ const token = useSelector((state: IRootState) => state.app.token);
16
+ const { broadcastLogout } = useBroadcastLogout(logoutAndNavigate);
17
+
18
+ useEffect(() => {
19
+ if (token === "") {
20
+ broadcastLogout();
21
+ }
22
+ }, [token, broadcastLogout]);
13
23
 
14
24
  return (
15
25
  <Style>
@@ -32,8 +42,13 @@ function mapStateToProps(state: IRootState) {
32
42
  };
33
43
  }
34
44
 
45
+ const mapDispatchToProps = {
46
+ logoutAndNavigate: appActions.logoutAndNavigate,
47
+ };
48
+
35
49
  interface IProps {
36
50
  isRehydrated: boolean;
51
+ logoutAndNavigate: () => void;
37
52
  }
38
53
 
39
- export default connect(mapStateToProps)(App);
54
+ export default connect(mapStateToProps, mapDispatchToProps)(App);
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { type MouseEvent as ReactMouseEvent, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import {
@@ -14,7 +14,7 @@ import {
14
14
  } from "@ax/components";
15
15
  import { appActions } from "@ax/containers/App";
16
16
  import { pageEditorActions } from "@ax/containers/PageEditor";
17
- import { type ISetCurrentPageIDAction, pageStatus } from "@ax/containers/PageEditor/interfaces";
17
+ import { pageStatus } from "@ax/containers/PageEditor/interfaces";
18
18
  import {
19
19
  buildCategoryColumns,
20
20
  getHumanLastModifiedDate,
@@ -71,7 +71,6 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
71
71
  getSiteContent,
72
72
  setHistoryPush,
73
73
  updatePageStatus,
74
- setCurrentPageID,
75
74
  duplicatePage,
76
75
  validatePage,
77
76
  getPage,
@@ -147,20 +146,27 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
147
146
 
148
147
  const setRoute = (path: string) => setHistoryPush(path, true);
149
148
 
150
- const goToPage = async () => {
149
+ const goToPage = async (e?: ReactMouseEvent) => {
151
150
  const pageID = page.haveDraftPage ? page.haveDraftPage : page.id;
152
- setCurrentPageID(pageID);
153
151
 
154
152
  const { templateId } = item.page;
155
153
  const dataPack = dataPacks.find((pack) => pack.templates.some((template) => template.id === templateId));
156
154
  dataPack && (await getDataPack(dataPack.id));
157
155
 
158
- setRoute("pages/editor");
156
+ if (e?.ctrlKey || e?.metaKey) {
157
+ window.open(`/sites/pages/editor/${pageID}`, "_blank");
158
+ return;
159
+ }
160
+
161
+ setRoute(`/sites/pages/editor/${pageID}`);
159
162
  };
160
163
 
161
- const editLivePage = () => {
162
- setCurrentPageID(page.id);
163
- setRoute("pages/editor");
164
+ const editLivePage = (e?: ReactMouseEvent) => {
165
+ if (e?.ctrlKey || e?.metaKey) {
166
+ window.open(`/sites/pages/editor/${page.id}`, "_blank");
167
+ return;
168
+ }
169
+ setRoute(`/sites/pages/editor/${page.id}`);
164
170
  };
165
171
 
166
172
  const handleOnChange = (value: ICheck) => onCheck(value);
@@ -265,8 +271,9 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
265
271
  });
266
272
 
267
273
  const selectedPageLanguage = getSelectedPageLanguage(language);
268
- selectedPageLanguage ? setCurrentPageID(selectedPageLanguage.pageId) : createNewTranslation(true);
269
- setHistoryPush("pages/editor", true);
274
+ const targetPageID = selectedPageLanguage ? selectedPageLanguage.pageId : page.id;
275
+ if (!selectedPageLanguage) createNewTranslation(true);
276
+ setHistoryPush(`pages/editor/${targetPageID}`, true);
270
277
  };
271
278
 
272
279
  const getCurrentPageLanguages = () => {
@@ -286,10 +293,10 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
286
293
  const handleDuplicatePage = async () => {
287
294
  setTemplateInstanceError({ error: false, templateName: "" });
288
295
  if (isDuplicable) {
289
- const isDuplicated = await duplicatePage(page.id, modalState);
290
- if (isDuplicated) {
296
+ const duplicatedID = await duplicatePage(page.id, modalState);
297
+ if (duplicatedID) {
291
298
  toggleModal("duplicate");
292
- setRoute("pages/editor");
299
+ setRoute(`pages/editor/${duplicatedID}`);
293
300
  }
294
301
  } else {
295
302
  toggleModal("duplicate");
@@ -303,6 +310,11 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
303
310
  setModalState({ title: "", slug: "" });
304
311
  };
305
312
 
313
+ const handleOpenInNewTab = () => {
314
+ const pageID = page.haveDraftPage ? page.haveDraftPage : page.id;
315
+ window.open(`/sites/pages/editor/${pageID}`, "_blank");
316
+ };
317
+
306
318
  const currentLanguages = getCurrentPageLanguages();
307
319
 
308
320
  const isTemplateActivated = activatedTemplates.some((temp: any) => temp.id === templateId);
@@ -418,6 +430,12 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
418
430
  action: viewPage,
419
431
  };
420
432
 
433
+ const newTabOption = {
434
+ label: "Open in new tab",
435
+ icon: "openOutside",
436
+ action: handleOpenInNewTab,
437
+ };
438
+
421
439
  const publishAction = getPublishItem(page.liveStatus?.status, canBeUnpublished);
422
440
 
423
441
  menuOptions = publishAction && isAllowedTo.publishUnpublishPages ? [publishAction, ...menuOptions] : menuOptions;
@@ -442,7 +460,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
442
460
 
443
461
  menuOptions = page.haveDraftPage ? [...editOptions, ...menuOptions] : menuOptions;
444
462
 
445
- menuOptions = page.liveStatus?.status === pageStatus.PUBLISHED ? [viewOption, ...menuOptions] : menuOptions;
463
+ menuOptions = page.liveStatus?.status === pageStatus.PUBLISHED ? [viewOption, newTabOption, ...menuOptions] : [newTabOption, ...menuOptions];
446
464
 
447
465
  const GlobalMark = () => (
448
466
  <Tooltip content="Global Page">
@@ -454,8 +472,8 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
454
472
  if (site) {
455
473
  const siteID = parseInt(site);
456
474
 
457
- duplicatePage(page.id, null, siteID).then((successEvent: boolean) => {
458
- if (successEvent === true) {
475
+ duplicatePage(page.id, null, siteID).then((successEvent) => {
476
+ if (successEvent) {
459
477
  toggleCopiedToast();
460
478
  }
461
479
  toggleModal("copy");
@@ -596,8 +614,7 @@ interface IPageItemProps {
596
614
  getSiteContent(): any;
597
615
  setHistoryPush(path: string, isEditor: boolean): void;
598
616
  updatePageStatus(ids: number[], status: string, updatedFromList: boolean): Promise<boolean>;
599
- setCurrentPageID(currentPageID: number | null): ISetCurrentPageIDAction;
600
- duplicatePage(pageID: number, data: any, siteID?: number): Promise<boolean>;
617
+ duplicatePage(pageID: number, data: any, siteID?: number): Promise<number | false>;
601
618
  removePageFromSite(pageID: number): Promise<boolean>;
602
619
  deleteBulk(ids: number[]): Promise<boolean>;
603
620
  setTemplateInstanceError(error: any): void;
@@ -414,7 +414,7 @@ const Content = (props: IProps): JSX.Element => {
414
414
 
415
415
  const addNewData = () => {
416
416
  resetForm(true);
417
- const path = `/sites/data/${currentStructuredData?.id}/editor`;
417
+ const path = `/sites/data/${currentStructuredData?.id}/content`;
418
418
  setHistoryPush(path, false);
419
419
  };
420
420
 
@@ -431,7 +431,7 @@ const Content = (props: IProps): JSX.Element => {
431
431
  setCurrentPageID(null);
432
432
  setCurrentPageStatus("offline");
433
433
  setCurrentPageName("New Page");
434
- const path = "/sites/pages/editor/new";
434
+ const path = "/sites/pages/editor";
435
435
  setHistoryPush(path, true);
436
436
  }
437
437
  };
@@ -625,11 +625,10 @@ const Content = (props: IProps): JSX.Element => {
625
625
  let path: string;
626
626
 
627
627
  if (isFromPage) {
628
- setCurrentPageID(item.relatedPage?.pageId || null);
629
- path = "/sites/pages/editor";
628
+ path = `/sites/pages/editor/${item.relatedPage?.pageId}`;
630
629
  } else {
631
630
  setSelectedStructuredData(item.structuredData, "site");
632
- path = `/sites/data/${item.structuredData}/editor`;
631
+ path = `/sites/data/${item.structuredData}/content`;
633
632
  }
634
633
 
635
634
  setHistoryPush(path, isFromPage);
@@ -714,7 +713,6 @@ const Content = (props: IProps): JSX.Element => {
714
713
  deletePage: deleteCurrentPage,
715
714
  getSiteContent,
716
715
  setHistoryPush,
717
- setCurrentPageID,
718
716
  duplicatePage,
719
717
  removePageFromSite,
720
718
  deleteBulk: deleteCurrentPageBulk,
@@ -1027,7 +1025,7 @@ interface IDispatchProps {
1027
1025
  getStructuredDataContents(params: any, siteID: number): Promise<void>;
1028
1026
  resetForm(setDefault?: boolean): void;
1029
1027
  deleteBulk(ids: any): Promise<boolean>;
1030
- duplicatePage(pageID: number, data?: any, siteID?: number): Promise<boolean>;
1028
+ duplicatePage(pageID: number, data?: any, siteID?: number): Promise<number | false>;
1031
1029
  getPage(pageID?: number, global?: boolean): Promise<void>;
1032
1030
  validatePage(publish?: boolean, browserRef?: any, currentPage?: IPage): Promise<boolean>;
1033
1031
  deleteDataContent(dataID: number[]): Promise<boolean>;
@@ -1,10 +1,8 @@
1
1
  import React from "react";
2
2
  import { connect } from "react-redux";
3
3
  import { appActions } from "@ax/containers/App";
4
- import { pageEditorActions } from "@ax/containers/PageEditor";
5
4
  import { sitesActions } from "@ax/containers/Sites";
6
5
  import { structuredDataActions } from "@ax/containers/StructuredData";
7
- import type { ISetCurrentPageIDAction } from "@ax/containers/PageEditor/interfaces";
8
6
  import type { IFile, IFileUseItem, IFileUsePages, IRootState } from "@ax/types";
9
7
  import Item from "./Item";
10
8
  import ItemGroup from "./ItemGroup";
@@ -17,7 +15,6 @@ const UsageContent = (props: IProps) => {
17
15
  currentSiteID,
18
16
  selectedTab,
19
17
  setHistoryPush,
20
- setCurrentPageID,
21
18
  getSite,
22
19
  setCurrentDataID,
23
20
  setSelectedStructuredData,
@@ -34,8 +31,7 @@ const UsageContent = (props: IProps) => {
34
31
  const pageItems = pages[0] ? pages[0].pages : [];
35
32
  return pageItems.map((item: IFileUseItem) => {
36
33
  const handleClick = () => {
37
- setCurrentPageID(item.id);
38
- setHistoryPush("pages/editor", true);
34
+ setHistoryPush(`pages/editor/${item.id}`, true);
39
35
  };
40
36
 
41
37
  return <Item title={item.title} date={item.published} key={item.id} onClick={handleClick} />;
@@ -44,8 +40,7 @@ const UsageContent = (props: IProps) => {
44
40
  return pages.map((item: IFileUsePages) => {
45
41
  const handleClick = async (page: IFileUseItem) => {
46
42
  await getSite(item.siteId);
47
- setCurrentPageID(page.id);
48
- setHistoryPush("/sites/pages/editor", true);
43
+ setHistoryPush(`/sites/pages/editor/${page.id}`, true);
49
44
  };
50
45
  return <ItemGroup key={item.siteId} title={item.siteName} items={item.pages} onClick={handleClick} />;
51
46
  });
@@ -57,9 +52,7 @@ const UsageContent = (props: IProps) => {
57
52
  return <></>;
58
53
  } else {
59
54
  const handleClick = (item: IFileUseItem) => {
60
- setCurrentPageID(item.id);
61
- const path = isSiteView ? "/data/pages/editor" : "data/pages/editor";
62
- setHistoryPush(path, true);
55
+ setHistoryPush(`${isSiteView ? "/data" : "data"}/pages/editor/${item.id}`, true);
63
56
  };
64
57
  return <ItemGroup title="Global Pages" items={globalPages} onClick={handleClick} />;
65
58
  }
@@ -75,7 +68,7 @@ const UsageContent = (props: IProps) => {
75
68
  item.structuredDataId && setSelectedStructuredData(item.structuredDataId, "site");
76
69
  setCurrentDataID(item.id);
77
70
  await getDataContent(item.id);
78
- setHistoryPush(`data/${item.structuredDataId}/editor`, true);
71
+ setHistoryPush(`data/${item.structuredDataId}/content`, true);
79
72
  };
80
73
 
81
74
  return (
@@ -93,7 +86,7 @@ const UsageContent = (props: IProps) => {
93
86
  item.structuredDataId && setSelectedStructuredData(item.structuredDataId, "global");
94
87
  setCurrentDataID(item.id);
95
88
  await getDataContent(item.id);
96
- setHistoryPush(`/data/${item.structuredDataId}/editor`, true);
89
+ setHistoryPush(`/data/${item.structuredDataId}/content`, true);
97
90
  };
98
91
  return <ItemGroup title="Global Simple Content" items={simpleStructuredData} onClick={handleClick} />;
99
92
  }
@@ -119,7 +112,6 @@ interface IProps {
119
112
  currentSiteID: number | null;
120
113
  selectedTab: "site" | "global";
121
114
  setHistoryPush(page: string, isEditor: boolean): Promise<void>;
122
- setCurrentPageID(currentPageID: number | null): ISetCurrentPageIDAction;
123
115
  getSite(siteID: number): Promise<void>;
124
116
  setCurrentDataID(id: number | null): void;
125
117
  setSelectedStructuredData(id: string, scope: string): void;
@@ -128,7 +120,6 @@ interface IProps {
128
120
 
129
121
  const mapDispatchToProps = {
130
122
  setHistoryPush: appActions.setHistoryPush,
131
- setCurrentPageID: pageEditorActions.setCurrentPageID,
132
123
  getSite: sitesActions.getSite,
133
124
  setCurrentDataID: structuredDataActions.setCurrentDataID,
134
125
  setSelectedStructuredData: structuredDataActions.setSelectedStructuredData,
@@ -605,7 +605,7 @@ const FileDrive = (props: IProps) => {
605
605
  );
606
606
 
607
607
  return (
608
- <MainWrapper backLink={false} title="File Drive Manager" rightButton={rightButtonProps}>
608
+ <MainWrapper backLink={false} title="File Drive Manager" rightButton={rightButtonProps} screen="file-drive">
609
609
  <S.Wrapper ref={wrapperRef}>
610
610
  <S.FolderPanel
611
611
  isOpen={isPanelOpen}
@@ -15,7 +15,6 @@ import { CheckGroupFilter, Loading, Modal } from "@ax/components";
15
15
  import { getStructuredDataTitle } from "@ax/helpers";
16
16
  import { useCategoryColors } from "@ax/hooks";
17
17
  import { appActions } from "@ax/containers/App";
18
- import { pageEditorActions } from "@ax/containers/PageEditor";
19
18
  import { navigationActions } from "@ax/containers/Navigation";
20
19
  import { sitesActions } from "@ax/containers/Sites";
21
20
  import {
@@ -143,17 +142,15 @@ const FormUseModal = (props: IFormUseModalModal): JSX.Element => {
143
142
  };
144
143
 
145
144
  const goToPage = async (pageID: number, siteID: number | null) => {
146
- const { setCurrentPageID, setSiteInfo, setHistoryPush } = props;
145
+ const { setSiteInfo, setHistoryPush } = props;
147
146
  if (!isSiteView && siteID) {
148
147
  const siteInfo = await getSite(siteID);
149
148
  if (siteInfo) {
150
149
  await setSiteInfo(siteInfo);
151
150
  }
152
- setCurrentPageID(pageID);
153
- setHistoryPush("/sites/pages/editor", true);
151
+ setHistoryPush(`/sites/pages/editor/${pageID}`, true);
154
152
  } else {
155
- setCurrentPageID(pageID);
156
- setHistoryPush(isSiteView ? "/sites/pages/editor" : "/data/pages/editor", true);
153
+ setHistoryPush(isSiteView ? `/sites/pages/editor/${pageID}` : `/data/pages/editor/${pageID}`, true);
157
154
  }
158
155
  };
159
156
 
@@ -271,7 +268,6 @@ interface IFormUseModalModal extends IModal {
271
268
  formInUse: { page?: number[]; header?: number[]; footer?: number[] } | null;
272
269
  isSiteView: boolean;
273
270
  setHistoryPush: (path: string, isEditor: boolean) => void;
274
- setCurrentPageID: (currentPageID: number | null) => void;
275
271
  setSiteInfo(currentSiteInfo: ISite): Promise<void>;
276
272
  setSelectedDefault(selectedDefault: string): void;
277
273
  setHeader(id: number | null): void;
@@ -280,7 +276,6 @@ interface IFormUseModalModal extends IModal {
280
276
 
281
277
  const mapDispatchToProps = {
282
278
  setHistoryPush: appActions.setHistoryPush,
283
- setCurrentPageID: pageEditorActions.setCurrentPageID,
284
279
  setSiteInfo: sitesActions.setSiteInfo,
285
280
  setSelectedDefault: navigationActions.setSelectedDefault,
286
281
  setHeader: navigationActions.setHeader,
@@ -74,4 +74,38 @@ interface IDeleteModal extends IModal {
74
74
  isDeleting?: boolean;
75
75
  }
76
76
 
77
- export { DeleteModal };
77
+ const ConflictSaveModal = (props: IConflictSaveModal): JSX.Element => {
78
+ const { isOpen, toggleModal, onSaveAnyway } = props;
79
+
80
+ const mainAction = {
81
+ title: "Continue anyway",
82
+ onClick: onSaveAnyway,
83
+ };
84
+
85
+ const secondaryAction = { title: "Cancel", onClick: toggleModal };
86
+
87
+ return (
88
+ <Modal
89
+ isOpen={isOpen}
90
+ hide={toggleModal}
91
+ size="S"
92
+ height="auto"
93
+ title="Continue with an outdated version?"
94
+ mainAction={mainAction}
95
+ secondaryAction={secondaryAction}
96
+ >
97
+ <S.ModalContent>
98
+ <p>
99
+ This page <strong>was updated in another tab</strong> after you opened it, so what you see here{" "}
100
+ <strong>is out of date</strong>. Reload to get the latest version first.
101
+ </p>
102
+ </S.ModalContent>
103
+ </Modal>
104
+ );
105
+ };
106
+
107
+ interface IConflictSaveModal extends IModal {
108
+ onSaveAnyway: () => void;
109
+ }
110
+
111
+ export { DeleteModal, ConflictSaveModal };