@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
|
@@ -26,7 +26,15 @@ import { structuredDataActions } from "@ax/containers/StructuredData";
|
|
|
26
26
|
import { usersActions } from "@ax/containers/Users";
|
|
27
27
|
import { RouteLeavingGuard } from "@ax/guards";
|
|
28
28
|
import { dateToString, getDefaultTheme } from "@ax/helpers";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
useBroadcastClipboard,
|
|
31
|
+
useBroadcastContentUpdate,
|
|
32
|
+
useEditingHeartbeat,
|
|
33
|
+
useIsDirty,
|
|
34
|
+
useModals,
|
|
35
|
+
usePermissionsForPage,
|
|
36
|
+
} from "@ax/hooks";
|
|
37
|
+
import history from "@ax/routes/history";
|
|
30
38
|
import type {
|
|
31
39
|
HeadingFilter,
|
|
32
40
|
IErrorItem,
|
|
@@ -40,7 +48,7 @@ import type {
|
|
|
40
48
|
PageAccessGrantsSummary,
|
|
41
49
|
} from "@ax/types";
|
|
42
50
|
|
|
43
|
-
import { DeleteModal } from "./atoms";
|
|
51
|
+
import { ConflictSaveModal, DeleteModal } from "./atoms";
|
|
44
52
|
import Editor from "./Editor";
|
|
45
53
|
import Preview from "./Preview";
|
|
46
54
|
|
|
@@ -78,6 +86,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
78
86
|
updatePageAccessGrants,
|
|
79
87
|
accessGrants,
|
|
80
88
|
sendPagePing,
|
|
89
|
+
match,
|
|
81
90
|
} = props;
|
|
82
91
|
|
|
83
92
|
const isAllowedTo = usePermissionsForPage(
|
|
@@ -109,9 +118,14 @@ const GlobalEditor = (props: IProps) => {
|
|
|
109
118
|
time: "12:00 am",
|
|
110
119
|
});
|
|
111
120
|
const [pendingNavigateCallback, setPendingNavigateCallback] = useState<(() => void) | null>(null);
|
|
121
|
+
const [showPageUpdateNotification, setShowPageUpdateNotification] = useState(false);
|
|
122
|
+
const [pendingConflictAction, setPendingConflictAction] = useState<(() => Promise<void>) | null>(null);
|
|
112
123
|
const browserRef = useRef<HTMLDivElement>(null);
|
|
113
124
|
|
|
114
|
-
const {
|
|
125
|
+
const { broadcastPageSave, remotePageUpdate, clearRemoteUpdate } = useBroadcastContentUpdate(pageID);
|
|
126
|
+
useBroadcastClipboard();
|
|
127
|
+
|
|
128
|
+
const { isOpen, toggleModal, closeModal } = useModals([
|
|
115
129
|
"userEditing",
|
|
116
130
|
"unpublish",
|
|
117
131
|
"schedule",
|
|
@@ -122,6 +136,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
122
136
|
"dirtyNavigate",
|
|
123
137
|
"userAccess",
|
|
124
138
|
"delete",
|
|
139
|
+
"conflictSave",
|
|
125
140
|
]);
|
|
126
141
|
|
|
127
142
|
const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent, isNewTranslation);
|
|
@@ -155,19 +170,34 @@ const GlobalEditor = (props: IProps) => {
|
|
|
155
170
|
// biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
|
|
156
171
|
useEffect(() => {
|
|
157
172
|
const { setStructuredDataFilter } = props;
|
|
173
|
+
const urlPageID = match?.params?.pageID ? parseInt(match.params.pageID, 10) : undefined;
|
|
174
|
+
if (urlPageID) setCurrentPageID(urlPageID);
|
|
175
|
+
const resolvedPageID = urlPageID ?? pageID;
|
|
158
176
|
|
|
159
177
|
editorContent?.structuredData && setStructuredDataFilter(editorContent.structuredData);
|
|
160
|
-
const handleGetPage = async () => await getPage(
|
|
178
|
+
const handleGetPage = async () => await getPage(resolvedPageID, true);
|
|
161
179
|
setTab(defaultContentTab);
|
|
162
180
|
setToastMsg(null);
|
|
163
181
|
handleGetPage();
|
|
164
|
-
if (!
|
|
182
|
+
if (!resolvedPageID) {
|
|
165
183
|
setIsDirty(false);
|
|
166
184
|
}
|
|
167
185
|
}, []);
|
|
168
186
|
|
|
169
187
|
useEditingHeartbeat(pageID, sendPagePing);
|
|
170
188
|
|
|
189
|
+
// sc-111850: the route carries the page id and the mount effect above seeds
|
|
190
|
+
// currentPageID from it, so a URL still pointing at the previous page is not cosmetic —
|
|
191
|
+
// it re-enters on the next mount and drags the editor back to that page. The global
|
|
192
|
+
// editor lives under /data, not /sites: this module is the one routes/multisite.tsx
|
|
193
|
+
// mounts, and routes/site.tsx mounts PageEditor instead.
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
const urlPageID = match?.params?.pageID ? parseInt(match.params.pageID, 10) : undefined;
|
|
196
|
+
if (pageID && urlPageID && pageID !== urlPageID) {
|
|
197
|
+
history.replace(`/data/pages/editor/${pageID}`);
|
|
198
|
+
}
|
|
199
|
+
}, [pageID, match]);
|
|
200
|
+
|
|
171
201
|
useEffect(() => {
|
|
172
202
|
setSelectedTab(defaultTab);
|
|
173
203
|
}, [defaultTab]);
|
|
@@ -200,6 +230,12 @@ const GlobalEditor = (props: IProps) => {
|
|
|
200
230
|
}
|
|
201
231
|
}, [editorContent]);
|
|
202
232
|
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (remotePageUpdate) {
|
|
235
|
+
setShowPageUpdateNotification(true);
|
|
236
|
+
}
|
|
237
|
+
}, [remotePageUpdate]);
|
|
238
|
+
|
|
203
239
|
const setRoute = (path: string) => props.setHistoryPush(path, true);
|
|
204
240
|
|
|
205
241
|
const removePage = async () => {
|
|
@@ -217,31 +253,52 @@ const GlobalEditor = (props: IProps) => {
|
|
|
217
253
|
}
|
|
218
254
|
};
|
|
219
255
|
|
|
220
|
-
|
|
221
|
-
|
|
256
|
+
// A warned tab must ask before ANY write, not only before the ones that go through
|
|
257
|
+
// savePage. Two traps here: these actions are wired straight to onClick, so their first
|
|
258
|
+
// argument can be a click event and only an explicit `true` may count as "already
|
|
259
|
+
// confirmed"; and the retry is stored as-is, because `() => retry()` would make React
|
|
260
|
+
// read it as a state updater and run the write before the author decides.
|
|
261
|
+
const blockedByConflict = (retry: () => Promise<void>, skipConflictCheck?: boolean) => {
|
|
262
|
+
if (skipConflictCheck === true || !isDirty || !remotePageUpdate) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
setPendingConflictAction(() => retry);
|
|
266
|
+
toggleModal("conflictSave");
|
|
267
|
+
return true;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const publishPage = async (skipConflictCheck?: boolean) => {
|
|
271
|
+
const { updatePageStatus, pageID, validatePage, skipReviewOnPublish } = props;
|
|
222
272
|
|
|
223
273
|
const validated = skipReviewOnPublish ? true : await validatePage(true);
|
|
224
274
|
|
|
225
275
|
if (validated) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
pageID
|
|
232
|
-
|
|
233
|
-
|
|
276
|
+
if (pageID && !isNewTranslation) {
|
|
277
|
+
if (blockedByConflict(() => publishPage(true), skipConflictCheck)) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const isSaved = await updatePageStatus([pageID], pageStatus.UPLOAD_PENDING);
|
|
282
|
+
if (isSaved) {
|
|
283
|
+
resetDirty();
|
|
284
|
+
broadcastPageSave(pageID);
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
const publishPageData = {
|
|
288
|
+
status: pageStatus.UPLOAD_PENDING,
|
|
289
|
+
};
|
|
234
290
|
|
|
235
|
-
|
|
236
|
-
|
|
291
|
+
await handleSavePageCommon({ publishPage: publishPageData }, undefined, skipConflictCheck, () =>
|
|
292
|
+
publishPage(true),
|
|
293
|
+
);
|
|
237
294
|
}
|
|
238
295
|
} else {
|
|
239
296
|
setNotification({ text: errorNotificationText, type: "error" });
|
|
240
297
|
}
|
|
241
298
|
};
|
|
242
299
|
|
|
243
|
-
const publishChanges = async () => {
|
|
244
|
-
const {
|
|
300
|
+
const publishChanges = async (skipConflictCheck?: boolean) => {
|
|
301
|
+
const { validatePage, skipReviewOnPublish } = props;
|
|
245
302
|
|
|
246
303
|
const validated = skipReviewOnPublish ? true : await validatePage(true);
|
|
247
304
|
|
|
@@ -250,21 +307,23 @@ const GlobalEditor = (props: IProps) => {
|
|
|
250
307
|
status: pageStatus.UPLOAD_PENDING,
|
|
251
308
|
};
|
|
252
309
|
|
|
253
|
-
|
|
254
|
-
if (isSaved) {
|
|
255
|
-
resetDirty();
|
|
256
|
-
}
|
|
310
|
+
await handleSavePageCommon({ publishPage }, undefined, skipConflictCheck, () => publishChanges(true));
|
|
257
311
|
} else {
|
|
258
312
|
setNotification({ text: errorNotificationText, type: "error" });
|
|
259
313
|
}
|
|
260
314
|
};
|
|
261
315
|
|
|
262
|
-
const unpublishPage = async () => {
|
|
316
|
+
const unpublishPage = async (skipConflictCheck?: boolean) => {
|
|
263
317
|
const { updatePageStatus, pageID } = props;
|
|
264
318
|
|
|
319
|
+
if (blockedByConflict(() => unpublishPage(true), skipConflictCheck)) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
265
323
|
const isSaved = await updatePageStatus([pageID], pageStatus.OFFLINE_PENDING);
|
|
266
324
|
if (isSaved) {
|
|
267
325
|
resetDirty();
|
|
326
|
+
broadcastPageSave(pageID);
|
|
268
327
|
}
|
|
269
328
|
};
|
|
270
329
|
|
|
@@ -278,14 +337,13 @@ const GlobalEditor = (props: IProps) => {
|
|
|
278
337
|
}
|
|
279
338
|
};
|
|
280
339
|
|
|
281
|
-
const handlePublishDraft = async () => {
|
|
282
|
-
const {
|
|
340
|
+
const handlePublishDraft = async (skipConflictCheck?: boolean) => {
|
|
341
|
+
const { validatePage, skipReviewOnPublish } = props;
|
|
283
342
|
|
|
284
343
|
const validated = skipReviewOnPublish ? true : await validatePage(true);
|
|
285
344
|
|
|
286
345
|
if (validated) {
|
|
287
|
-
|
|
288
|
-
if (isSaved) resetDirty();
|
|
346
|
+
await handleSavePageCommon({ publishDraft: true }, undefined, skipConflictCheck, () => handlePublishDraft(true));
|
|
289
347
|
} else {
|
|
290
348
|
setNotification({ text: errorNotificationText, type: "error" });
|
|
291
349
|
}
|
|
@@ -303,29 +361,38 @@ const GlobalEditor = (props: IProps) => {
|
|
|
303
361
|
getPage(pageID, true);
|
|
304
362
|
};
|
|
305
363
|
|
|
306
|
-
const handleCreateDraft = async () => {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const isSaved = await savePage({ createDraft: true });
|
|
310
|
-
if (isSaved) resetDirty();
|
|
364
|
+
const handleCreateDraft = async (skipConflictCheck?: boolean) => {
|
|
365
|
+
await handleSavePageCommon({ createDraft: true }, undefined, skipConflictCheck, () => handleCreateDraft(true));
|
|
311
366
|
};
|
|
312
367
|
|
|
313
|
-
const handleSchedulePublication = async () => {
|
|
368
|
+
const handleSchedulePublication = async (skipConflictCheck?: boolean) => {
|
|
369
|
+
if (blockedByConflict(() => handleSchedulePublication(true), skipConflictCheck)) {
|
|
370
|
+
closeModal("schedule");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
314
374
|
const date = new Date(`${scheduleDate.date} ${scheduleDate.time}`);
|
|
315
375
|
const dateString = dateToString(date, "dd/MM/yyyy HH:mm:ss");
|
|
316
376
|
const saved = await schedulePublication(dateString, isDraft);
|
|
317
377
|
if (saved) {
|
|
318
378
|
resetDirty();
|
|
319
|
-
|
|
379
|
+
broadcastPageSave(pageID);
|
|
380
|
+
closeModal("schedule");
|
|
320
381
|
}
|
|
321
382
|
};
|
|
322
383
|
|
|
323
|
-
const handleCancelSchedulePublication = async () => {
|
|
384
|
+
const handleCancelSchedulePublication = async (skipConflictCheck?: boolean) => {
|
|
385
|
+
if (blockedByConflict(() => handleCancelSchedulePublication(true), skipConflictCheck)) {
|
|
386
|
+
closeModal("cancelSchedule");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
324
390
|
const saved = await schedulePublication(null, isDraft);
|
|
325
391
|
if (saved) {
|
|
326
392
|
setScheduleDate({ date: "", time: "12:00 am" });
|
|
327
393
|
resetDirty();
|
|
328
|
-
|
|
394
|
+
broadcastPageSave(pageID);
|
|
395
|
+
closeModal("cancelSchedule");
|
|
329
396
|
}
|
|
330
397
|
};
|
|
331
398
|
|
|
@@ -433,16 +500,38 @@ const GlobalEditor = (props: IProps) => {
|
|
|
433
500
|
const isEditable = editorContent?.editable;
|
|
434
501
|
const isRestricted = !!(accessGrants.users.length || accessGrants.roles.length);
|
|
435
502
|
|
|
436
|
-
const
|
|
503
|
+
const handleSavePageCommon = async (
|
|
504
|
+
saveOptions: SavePageOptions,
|
|
505
|
+
onSuccess?: () => void,
|
|
506
|
+
skipConflictCheck?: boolean,
|
|
507
|
+
recursiveCall: () => Promise<void> = () => Promise.resolve(),
|
|
508
|
+
) => {
|
|
509
|
+
if (blockedByConflict(recursiveCall, skipConflictCheck)) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
437
513
|
const { savePage } = props;
|
|
438
514
|
|
|
439
|
-
const isSaved = await savePage(
|
|
515
|
+
const isSaved = await savePage(saveOptions);
|
|
440
516
|
if (isSaved) {
|
|
441
517
|
resetDirty();
|
|
442
|
-
|
|
518
|
+
clearRemoteUpdate();
|
|
519
|
+
if (pageID) {
|
|
520
|
+
broadcastPageSave(pageID);
|
|
521
|
+
}
|
|
522
|
+
onSuccess?.();
|
|
443
523
|
}
|
|
444
524
|
};
|
|
445
525
|
|
|
526
|
+
const handleSavePage = async (setToast?: (msg: string) => void, skipConflictCheck?: boolean) => {
|
|
527
|
+
await handleSavePageCommon(
|
|
528
|
+
{ setToast },
|
|
529
|
+
() => setToastMsg(null),
|
|
530
|
+
skipConflictCheck,
|
|
531
|
+
() => handleSavePage(setToast, true),
|
|
532
|
+
);
|
|
533
|
+
};
|
|
534
|
+
|
|
446
535
|
const goToPages = (path: string) => setRoute(path);
|
|
447
536
|
|
|
448
537
|
const goToError = async (editorID: number | null, tab: string, template: boolean, parentEditorID?: number) => {
|
|
@@ -523,6 +612,12 @@ const GlobalEditor = (props: IProps) => {
|
|
|
523
612
|
setPendingNavigateCallback(null);
|
|
524
613
|
};
|
|
525
614
|
|
|
615
|
+
const handleConfirmConflictSave = async () => {
|
|
616
|
+
toggleModal("conflictSave");
|
|
617
|
+
await pendingConflictAction?.();
|
|
618
|
+
setPendingConflictAction(null);
|
|
619
|
+
};
|
|
620
|
+
|
|
526
621
|
let pageStatusActions =
|
|
527
622
|
props.pageStatus === pageStatus.PUBLISHED || isDraft
|
|
528
623
|
? [
|
|
@@ -549,10 +644,8 @@ const GlobalEditor = (props: IProps) => {
|
|
|
549
644
|
const goBackToSite = () => {
|
|
550
645
|
if (sitePageID) {
|
|
551
646
|
setCurrentSiteInfo(savedSiteInfo);
|
|
552
|
-
setCurrentPageID(sitePageID);
|
|
553
647
|
getUserCurrentPermissions();
|
|
554
|
-
|
|
555
|
-
setHistoryPush(path, true);
|
|
648
|
+
setHistoryPush(`/sites/pages/editor/${sitePageID}`, true);
|
|
556
649
|
}
|
|
557
650
|
};
|
|
558
651
|
|
|
@@ -656,7 +749,9 @@ const GlobalEditor = (props: IProps) => {
|
|
|
656
749
|
}
|
|
657
750
|
};
|
|
658
751
|
|
|
659
|
-
|
|
752
|
+
const isContentReady = !!(editorContent as any)?.template;
|
|
753
|
+
|
|
754
|
+
return isLoading || !isContentReady ? (
|
|
660
755
|
<Loading />
|
|
661
756
|
) : (
|
|
662
757
|
<>
|
|
@@ -687,6 +782,22 @@ const GlobalEditor = (props: IProps) => {
|
|
|
687
782
|
>
|
|
688
783
|
{selectedTab === "edit" ? (
|
|
689
784
|
<>
|
|
785
|
+
{showPageUpdateNotification && (
|
|
786
|
+
<S.NotificationWrapper data-testid="page-update-notification">
|
|
787
|
+
<Notification
|
|
788
|
+
type="info"
|
|
789
|
+
text="This page was edited in another tab and you're viewing an older version. Reload to see the latest changes. Any unsaved edits here will be lost."
|
|
790
|
+
btnText="Reload Page"
|
|
791
|
+
onClick={() => {
|
|
792
|
+
window.location.reload();
|
|
793
|
+
}}
|
|
794
|
+
resetError={() => {
|
|
795
|
+
setShowPageUpdateNotification(false);
|
|
796
|
+
clearRemoteUpdate();
|
|
797
|
+
}}
|
|
798
|
+
/>
|
|
799
|
+
</S.NotificationWrapper>
|
|
800
|
+
)}
|
|
690
801
|
{isLivePageChanged && (
|
|
691
802
|
<S.NotificationWrapper>
|
|
692
803
|
<Notification type="warning" text={modifiedNotificationText} />
|
|
@@ -844,6 +955,11 @@ const GlobalEditor = (props: IProps) => {
|
|
|
844
955
|
you want to discard your changes?
|
|
845
956
|
</S.ModalContent>
|
|
846
957
|
</Modal>
|
|
958
|
+
<ConflictSaveModal
|
|
959
|
+
isOpen={isOpen("conflictSave")}
|
|
960
|
+
toggleModal={() => toggleModal("conflictSave")}
|
|
961
|
+
onSaveAnyway={handleConfirmConflictSave}
|
|
962
|
+
/>
|
|
847
963
|
</MainWrapper>
|
|
848
964
|
</>
|
|
849
965
|
);
|
|
@@ -917,14 +1033,16 @@ const mapDispatchToProps = {
|
|
|
917
1033
|
updatePageAccessGrants: pageEditorActions.updatePageAccessGrants,
|
|
918
1034
|
};
|
|
919
1035
|
|
|
1036
|
+
type SavePageOptions = {
|
|
1037
|
+
createDraft?: boolean;
|
|
1038
|
+
publishPage?: { status: string };
|
|
1039
|
+
publishDraft?: boolean;
|
|
1040
|
+
setToast?: (msg: string) => void;
|
|
1041
|
+
};
|
|
1042
|
+
|
|
920
1043
|
interface IPageEditorDispatchProps {
|
|
921
1044
|
getPage(pageID?: number, global?: boolean, preferDraft?: boolean): Promise<void>;
|
|
922
|
-
savePage(data?:
|
|
923
|
-
createDraft?: boolean;
|
|
924
|
-
publishPage?: { status: string };
|
|
925
|
-
publishDraft?: boolean;
|
|
926
|
-
setToast?: (msg: string) => void;
|
|
927
|
-
}): Promise<boolean>;
|
|
1045
|
+
savePage(data?: SavePageOptions): Promise<boolean>;
|
|
928
1046
|
deletePage(params?: ISavePageParams): Promise<boolean>;
|
|
929
1047
|
validatePage(publish?: boolean): Promise<boolean>;
|
|
930
1048
|
updatePageStatus(id: number[], status: string): Promise<boolean>;
|
|
@@ -950,7 +1068,7 @@ interface IPageEditorDispatchProps {
|
|
|
950
1068
|
}): Promise<boolean>;
|
|
951
1069
|
}
|
|
952
1070
|
|
|
953
|
-
type IProps = IPageEditorStateProps & IPageEditorDispatchProps & RouteComponentProps
|
|
1071
|
+
type IProps = IPageEditorStateProps & IPageEditorDispatchProps & RouteComponentProps<{ pageID?: string }>;
|
|
954
1072
|
|
|
955
1073
|
const GlobalEditorWithErrorBoundary = withErrorBoundary(GlobalEditor, {
|
|
956
1074
|
fallback: <ErrorPage type="wrongEditor" />,
|
|
@@ -76,7 +76,7 @@ const Robots = (props: IProps): JSX.Element => {
|
|
|
76
76
|
return (
|
|
77
77
|
<>
|
|
78
78
|
<RouteLeavingGuard when={isDirty} action={setRoute} text={modalText} />
|
|
79
|
-
<MainWrapper backLink={false} title="SEO Settings" rightButton={rightButtonProps}>
|
|
79
|
+
<MainWrapper backLink={false} title="SEO Settings" rightButton={rightButtonProps} screen="robots">
|
|
80
80
|
<S.Wrapper>
|
|
81
81
|
<Nav current={currentNavItem} items={navItems} onClick={handleMenuClick} />
|
|
82
82
|
<S.ContentWrapper>
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import React, { useEffect, useState } 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
6
|
import { usersActions } from "@ax/containers/Users";
|
|
8
|
-
import type { ISetCurrentPageIDAction } from "@ax/containers/PageEditor/interfaces";
|
|
9
7
|
import type { IImage, IRootState, ISite, IUser } from "@ax/types";
|
|
10
8
|
import { images } from "@ax/api";
|
|
11
9
|
import { isReqOk } from "@ax/helpers";
|
|
@@ -23,7 +21,6 @@ const UsageContent = (props: IProps) => {
|
|
|
23
21
|
selectedTab,
|
|
24
22
|
currentUser,
|
|
25
23
|
setHistoryPush,
|
|
26
|
-
setCurrentPageID,
|
|
27
24
|
getSite,
|
|
28
25
|
setCurrentDataID,
|
|
29
26
|
setSelectedStructuredData,
|
|
@@ -71,8 +68,7 @@ const UsageContent = (props: IProps) => {
|
|
|
71
68
|
const data = items.sites.find((site) => site.id === currentSiteID)?.simpleContent || [];
|
|
72
69
|
const pageItems = pages.map((item: IImageUseItem) => {
|
|
73
70
|
const handleClick = () => {
|
|
74
|
-
|
|
75
|
-
setHistoryPush("/sites/pages/editor", true);
|
|
71
|
+
setHistoryPush(`/sites/pages/editor/${item.id}`, true);
|
|
76
72
|
};
|
|
77
73
|
|
|
78
74
|
return <Item title={item.title} type="Page" date={item.date} key={item.id} onClick={handleClick} />;
|
|
@@ -84,7 +80,7 @@ const UsageContent = (props: IProps) => {
|
|
|
84
80
|
setSelectedStructuredData(item.structuredData.id, "site");
|
|
85
81
|
setCurrentDataID(item.id);
|
|
86
82
|
await getDataContent(item.id);
|
|
87
|
-
setHistoryPush(`/sites/data/${item.structuredData.id}/
|
|
83
|
+
setHistoryPush(`/sites/data/${item.structuredData.id}/content`);
|
|
88
84
|
};
|
|
89
85
|
|
|
90
86
|
return (
|
|
@@ -104,13 +100,12 @@ const UsageContent = (props: IProps) => {
|
|
|
104
100
|
const handleClick = async (page: IImageUseItem) => {
|
|
105
101
|
await getSite(item.id);
|
|
106
102
|
if (!page.structuredData) {
|
|
107
|
-
|
|
108
|
-
setHistoryPush("/sites/pages/editor", true);
|
|
103
|
+
setHistoryPush(`/sites/pages/editor/${page.id}`, true);
|
|
109
104
|
} else {
|
|
110
105
|
setSelectedStructuredData(page.structuredData.id, "site");
|
|
111
106
|
setCurrentDataID(page.id);
|
|
112
107
|
await getDataContent(page.id);
|
|
113
|
-
setHistoryPush(`/sites/data/${page.structuredData.id}/
|
|
108
|
+
setHistoryPush(`/sites/data/${page.structuredData.id}/content`);
|
|
114
109
|
}
|
|
115
110
|
};
|
|
116
111
|
return (
|
|
@@ -132,13 +127,11 @@ const UsageContent = (props: IProps) => {
|
|
|
132
127
|
}
|
|
133
128
|
const hasGlobal = currentUser?.isSuperAdmin || !!currentUser?.roles.find((rol) => rol.siteId === "global");
|
|
134
129
|
const handleClick = (item: IImageUseItem) => {
|
|
135
|
-
setCurrentPageID(item.id);
|
|
136
130
|
if (isSiteView) {
|
|
137
131
|
setCurrentSiteInfo(null);
|
|
138
132
|
getUserCurrentPermissions();
|
|
139
133
|
}
|
|
140
|
-
|
|
141
|
-
setHistoryPush(path, true);
|
|
134
|
+
setHistoryPush(`/data/pages/editor/${item.id}`, true);
|
|
142
135
|
};
|
|
143
136
|
return <ItemGroup title="Global Pages" items={items.globalPages} onClick={handleClick} disabled={!hasGlobal} />;
|
|
144
137
|
};
|
|
@@ -157,7 +150,7 @@ const UsageContent = (props: IProps) => {
|
|
|
157
150
|
setCurrentSiteInfo(null);
|
|
158
151
|
getUserCurrentPermissions();
|
|
159
152
|
}
|
|
160
|
-
setHistoryPush(`/data/${item.structuredData.id}/
|
|
153
|
+
setHistoryPush(`/data/${item.structuredData.id}/content`);
|
|
161
154
|
};
|
|
162
155
|
return (
|
|
163
156
|
<ItemGroup title="Global Simple Content" items={items.simpleData} onClick={handleClick} disabled={!hasGlobal} />
|
|
@@ -196,7 +189,6 @@ interface IProps {
|
|
|
196
189
|
selectedTab: "site" | "global";
|
|
197
190
|
currentUser: IUser | null;
|
|
198
191
|
setHistoryPush(page: string, isEditor?: boolean): Promise<void>;
|
|
199
|
-
setCurrentPageID(currentPageID: number | null): ISetCurrentPageIDAction;
|
|
200
192
|
getSite(siteID: number): Promise<void>;
|
|
201
193
|
setCurrentDataID(id: number | null): void;
|
|
202
194
|
setSelectedStructuredData(id: string, scope: string): void;
|
|
@@ -220,7 +212,6 @@ export interface IImageUseItem {
|
|
|
220
212
|
|
|
221
213
|
const mapDispatchToProps = {
|
|
222
214
|
setHistoryPush: appActions.setHistoryPush,
|
|
223
|
-
setCurrentPageID: pageEditorActions.setCurrentPageID,
|
|
224
215
|
getSite: sitesActions.getSite,
|
|
225
216
|
setCurrentSiteInfo: sitesActions.setCurrentSiteInfo,
|
|
226
217
|
setCurrentDataID: structuredDataActions.setCurrentDataID,
|
|
@@ -623,7 +623,7 @@ const MediaGallery = (props: IProps) => {
|
|
|
623
623
|
);
|
|
624
624
|
|
|
625
625
|
return (
|
|
626
|
-
<MainWrapper backLink={false} title="Media Gallery" rightButton={rightButtonProps}>
|
|
626
|
+
<MainWrapper backLink={false} title="Media Gallery" rightButton={rightButtonProps} screen="gallery">
|
|
627
627
|
<S.Wrapper ref={wrapperRef}>
|
|
628
628
|
<S.FolderPanel
|
|
629
629
|
isOpen={isPanelOpen}
|
|
@@ -154,6 +154,40 @@ interface IUserEditingModal {
|
|
|
154
154
|
onBack: () => void;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
const ConflictSaveModal = (props: IConflictSaveModal): JSX.Element => {
|
|
158
|
+
const { isOpen, toggleModal, onSaveAnyway } = props;
|
|
159
|
+
|
|
160
|
+
const mainAction = {
|
|
161
|
+
title: "Continue anyway",
|
|
162
|
+
onClick: onSaveAnyway,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const secondaryAction = { title: "Cancel", onClick: toggleModal };
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Modal
|
|
169
|
+
isOpen={isOpen}
|
|
170
|
+
hide={toggleModal}
|
|
171
|
+
size="S"
|
|
172
|
+
height="auto"
|
|
173
|
+
title="Continue with an outdated version?"
|
|
174
|
+
mainAction={mainAction}
|
|
175
|
+
secondaryAction={secondaryAction}
|
|
176
|
+
>
|
|
177
|
+
<S.ModalContent>
|
|
178
|
+
<p>
|
|
179
|
+
This page <strong>was updated in another tab</strong> after you opened it, so what you see here{" "}
|
|
180
|
+
<strong>is out of date</strong>. Reload to get the latest version first.
|
|
181
|
+
</p>
|
|
182
|
+
</S.ModalContent>
|
|
183
|
+
</Modal>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
interface IConflictSaveModal extends IModal {
|
|
188
|
+
onSaveAnyway: () => void;
|
|
189
|
+
}
|
|
190
|
+
|
|
157
191
|
const UnpublishModal = (props: IUnpublishModal): JSX.Element => {
|
|
158
192
|
const { isOpen, toggleModal } = props;
|
|
159
193
|
|
|
@@ -181,4 +215,4 @@ interface IUnpublishModal {
|
|
|
181
215
|
toggleModal: () => void;
|
|
182
216
|
}
|
|
183
217
|
|
|
184
|
-
export { UnpublishModal, UserEditingModal, DeleteModal, DirtyNavigateModal };
|
|
218
|
+
export { UnpublishModal, UserEditingModal, DeleteModal, DirtyNavigateModal, ConflictSaveModal };
|