@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.
- package/package.json +2 -2
- package/src/GlobalStore.tsx +3 -1
- package/src/__tests__/components/ConfigPanel/GlobalPageForm/GlobalPageForm.test.tsx +7 -3
- package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.style.test.tsx +44 -0
- package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.test.tsx +109 -0
- package/src/__tests__/components/Nav/Nav.test.tsx +75 -2
- package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +50 -21
- package/src/__tests__/components/Toast/Toast.test.tsx +37 -1
- package/src/__tests__/hooks/broadcast.test.tsx +263 -0
- package/src/api/utils.tsx +7 -6
- package/src/components/Browser/index.tsx +10 -2
- package/src/components/ConfigPanel/GlobalPageForm/index.tsx +3 -4
- package/src/components/Fields/Wysiwyg/atoms.tsx +1 -1
- package/src/components/Fields/Wysiwyg/index.tsx +28 -0
- package/src/components/Fields/Wysiwyg/style.tsx +41 -1
- package/src/components/MainWrapper/index.tsx +14 -2
- package/src/components/Nav/index.tsx +21 -6
- package/src/components/ResizePanel/index.tsx +8 -3
- package/src/components/Toast/index.tsx +4 -1
- package/src/containers/ActivityLog/actions.tsx +4 -7
- package/src/containers/PageEditor/actions.tsx +14 -3
- package/src/helpers/containerEvaluations.tsx +8 -0
- package/src/hooks/broadcast.ts +160 -0
- package/src/hooks/index.tsx +5 -0
- package/src/modules/ActivityLog/index.tsx +1 -0
- package/src/modules/Analytics/index.tsx +1 -1
- package/src/modules/App/index.tsx +21 -6
- package/src/modules/Content/PageItem/index.tsx +36 -19
- package/src/modules/Content/index.tsx +5 -7
- package/src/modules/FileDrive/FileModal/DetailPanel/UsageContent/index.tsx +5 -14
- package/src/modules/FileDrive/index.tsx +1 -1
- package/src/modules/Forms/FormUseModal/index.tsx +3 -8
- package/src/modules/GlobalEditor/atoms.tsx +35 -1
- package/src/modules/GlobalEditor/index.tsx +169 -51
- package/src/modules/GlobalSettings/Robots/index.tsx +1 -1
- package/src/modules/MediaGallery/ImageModal/DetailPanel/UsageContent/index.tsx +6 -15
- package/src/modules/MediaGallery/index.tsx +1 -1
- package/src/modules/PageEditor/atoms.tsx +35 -1
- package/src/modules/PageEditor/index.tsx +168 -48
- package/src/modules/Redirects/index.tsx +1 -0
- package/src/modules/Settings/Languages/index.tsx +1 -1
- package/src/modules/Settings/SeoAnalyticsSettings/Analytics/index.tsx +1 -0
- package/src/modules/Settings/Social/index.tsx +1 -1
- package/src/modules/StructuredData/Form/index.tsx +108 -10
- package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +31 -11
- package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +17 -3
- package/src/modules/StructuredData/StructuredDataList/index.tsx +6 -4
- package/src/modules/StructuredData/atoms.tsx +35 -1
- package/src/routes/multisite.tsx +34 -18
- package/src/routes/site.tsx +40 -21
- package/src/storeRegistry.ts +5 -0
- 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 };
|
package/src/hooks/index.tsx
CHANGED
|
@@ -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,
|
|
@@ -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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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 ?
|
|
269
|
-
|
|
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
|
|
290
|
-
if (
|
|
296
|
+
const duplicatedID = await duplicatePage(page.id, modalState);
|
|
297
|
+
if (duplicatedID) {
|
|
291
298
|
toggleModal("duplicate");
|
|
292
|
-
setRoute(
|
|
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
|
|
458
|
-
if (successEvent
|
|
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
|
-
|
|
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}/
|
|
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
|
|
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
|
-
|
|
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}/
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}/
|
|
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}/
|
|
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 {
|
|
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
|
-
|
|
153
|
-
setHistoryPush("/sites/pages/editor", true);
|
|
151
|
+
setHistoryPush(`/sites/pages/editor/${pageID}`, true);
|
|
154
152
|
} else {
|
|
155
|
-
|
|
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
|
-
|
|
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 };
|