@griddo/ax 1.66.13 → 1.67.2
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/__tests__/components/Fields/ConditionalField/ConditionalField.test.tsx +95 -0
- package/src/api/pages.tsx +15 -3
- package/src/api/redirects.tsx +4 -2
- package/src/api/sites.tsx +12 -4
- package/src/components/Browser/index.tsx +9 -22
- package/src/components/Browser/style.tsx +1 -6
- package/src/components/ErrorCenter/index.tsx +8 -5
- package/src/components/ErrorCenter/style.tsx +21 -8
- package/src/components/Fields/ComponentArray/MixableComponentArray/AddItemButton/index.tsx +3 -3
- package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +60 -25
- package/src/components/Fields/ComponentContainer/index.tsx +21 -7
- package/src/components/Fields/ConditionalField/index.tsx +1 -1
- package/src/components/Fields/LinkField/index.tsx +111 -0
- package/src/components/Fields/ReferenceField/ItemList/index.tsx +4 -0
- package/src/components/Fields/ReferenceField/ManualPanel/index.tsx +12 -2
- package/src/components/Fields/ReferenceField/index.tsx +24 -12
- package/src/components/Fields/ReferenceField/style.tsx +12 -1
- package/src/components/Fields/UrlField/index.tsx +13 -1
- package/src/components/Fields/VisualUniqueSelection/utils.tsx +1 -6
- package/src/components/Fields/index.tsx +2 -0
- package/src/components/FieldsBehavior/index.tsx +14 -1
- package/src/components/Icon/components/Copy.js +14 -0
- package/src/components/Icon/components/Copy2.js +14 -0
- package/src/components/Icon/components/Duplicate.js +3 -5
- package/src/components/Icon/components/Page.js +12 -0
- package/src/components/Icon/svgs/Copy.svg +3 -0
- package/src/components/Icon/svgs/Copy2.svg +3 -0
- package/src/components/Icon/svgs/Duplicate.svg +1 -1
- package/src/components/Icon/svgs/page.svg +3 -0
- package/src/components/MainWrapper/AppBar/index.tsx +21 -10
- package/src/components/MainWrapper/AppBar/style.tsx +11 -3
- package/src/components/MainWrapper/index.tsx +2 -0
- package/src/components/Notification/index.tsx +1 -3
- package/src/components/SearchField/index.tsx +37 -4
- package/src/components/SearchField/style.tsx +23 -10
- package/src/components/index.tsx +2 -0
- package/src/containers/Navigation/Defaults/actions.tsx +2 -0
- package/src/containers/PageEditor/actions.tsx +101 -19
- package/src/containers/PageEditor/utils.tsx +2 -1
- package/src/containers/Sites/actions.tsx +53 -24
- package/src/containers/Sites/constants.tsx +2 -0
- package/src/containers/Sites/interfaces.tsx +12 -5
- package/src/containers/Sites/reducer.tsx +8 -0
- package/src/containers/StructuredData/actions.tsx +5 -8
- package/src/forms/errors.tsx +1 -0
- package/src/forms/index.tsx +13 -1
- package/src/forms/validators.tsx +181 -13
- package/src/helpers/dataPacks.tsx +8 -1
- package/src/helpers/index.tsx +4 -1
- package/src/helpers/objects.tsx +10 -2
- package/src/modules/Categories/CategoriesList/CategoryItem/index.tsx +3 -1
- package/src/modules/Categories/CategoriesList/CategoryPanel/index.tsx +15 -9
- package/src/modules/Categories/CategoriesList/index.tsx +2 -1
- package/src/modules/Content/PageItem/index.tsx +52 -2
- package/src/modules/Content/atoms.tsx +41 -3
- package/src/modules/Content/index.tsx +44 -2
- package/src/modules/Content/style.tsx +8 -1
- package/src/modules/FramePreview/index.tsx +85 -0
- package/src/modules/FramePreview/style.tsx +18 -0
- package/src/modules/GlobalEditor/Editor/index.tsx +3 -1
- package/src/modules/GlobalEditor/PageBrowser/index.tsx +3 -0
- package/src/modules/GlobalEditor/index.tsx +22 -6
- package/src/modules/PageEditor/Editor/index.tsx +5 -1
- package/src/modules/PageEditor/PageBrowser/index.tsx +4 -5
- package/src/modules/PageEditor/index.tsx +40 -12
- package/src/modules/Redirects/index.tsx +40 -10
- package/src/modules/Settings/Globals/index.tsx +1 -1
- package/src/modules/Sites/index.tsx +2 -2
- package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +1 -1
- package/src/modules/StructuredData/StructuredDataList/index.tsx +19 -2
- package/src/modules/Users/Profile/index.tsx +3 -3
- package/src/routes/multisite.tsx +12 -4
- package/src/routes/site.tsx +1 -1
- package/src/types/index.tsx +13 -4
- package/tsconfig.paths.json +2 -1
package/src/forms/validators.tsx
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
dateToString,
|
|
3
|
+
getDeactivatedModules,
|
|
4
|
+
getDefaultSchema,
|
|
5
|
+
getSchema,
|
|
6
|
+
getTemplate,
|
|
7
|
+
hasProps,
|
|
8
|
+
isComponentEmpty,
|
|
9
|
+
isEmptyContainer,
|
|
10
|
+
isModuleDisabled,
|
|
11
|
+
} from "@ax/helpers";
|
|
12
|
+
import { findByEditorID } from "@ax/forms";
|
|
13
|
+
import { IErrorItem, ITemplate } from "@ax/types";
|
|
3
14
|
import { ERRORS } from "./errors";
|
|
4
15
|
|
|
5
16
|
const VALIDATORS = {
|
|
@@ -96,6 +107,13 @@ const VALIDATORS = {
|
|
|
96
107
|
return { isValid: true, errorCode: "" };
|
|
97
108
|
}
|
|
98
109
|
},
|
|
110
|
+
isMockup: (val: any, field: { type: string; defaultValue: any }): IError => {
|
|
111
|
+
const isValid = !checkMockupByType(field.type, val, field.defaultValue);
|
|
112
|
+
return { isValid, errorCode: "ERR016" };
|
|
113
|
+
},
|
|
114
|
+
apiValidator: (val: any, code: string): IError => {
|
|
115
|
+
return { isValid: false, errorCode: code };
|
|
116
|
+
},
|
|
99
117
|
isSamePass: (pass1: string, pass2: string): IError => {
|
|
100
118
|
const isValid = pass1 === pass2;
|
|
101
119
|
return { isValid, errorCode: "ERR041" };
|
|
@@ -156,11 +174,33 @@ const isEmptyField = (value: any, fieldType: string, multiple: boolean) => {
|
|
|
156
174
|
const { isEmpty } = isEmptyContainer(value, multiple);
|
|
157
175
|
return isEmpty;
|
|
158
176
|
}
|
|
177
|
+
case "NumberField":
|
|
178
|
+
return value === null || Number.isNaN(value);
|
|
159
179
|
default:
|
|
160
180
|
return typeof value === "string" && value.trim().length === 0;
|
|
161
181
|
}
|
|
162
182
|
};
|
|
163
183
|
|
|
184
|
+
const checkMockupByType = (type: string, value: any, defaultValue: any) => {
|
|
185
|
+
if (!value || !defaultValue) return false;
|
|
186
|
+
switch (type) {
|
|
187
|
+
case "HeadingField":
|
|
188
|
+
return value.content && defaultValue.content && value.content.trim() === defaultValue.content.trim();
|
|
189
|
+
case "ImageField":
|
|
190
|
+
return (
|
|
191
|
+
(hasProps(value, ["publicId"]) && value.publicId === defaultValue.publicId) ||
|
|
192
|
+
(hasProps(value, ["url"]) && value.url === defaultValue.url)
|
|
193
|
+
);
|
|
194
|
+
default:
|
|
195
|
+
return typeof value === "string" && typeof defaultValue === "string" && value.trim() === defaultValue.trim();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const checkMockupContent = (component: string, key: string, type: string, value: any) => {
|
|
200
|
+
const moduleDefault = getDefaultSchema(component);
|
|
201
|
+
return { isMockup: checkMockupByType(type, value, moduleDefault[key]), defaultValue: moduleDefault[key] };
|
|
202
|
+
};
|
|
203
|
+
|
|
164
204
|
const getValidationErrors = (
|
|
165
205
|
fields: Record<string, unknown>[],
|
|
166
206
|
current: any,
|
|
@@ -176,8 +216,8 @@ const getValidationErrors = (
|
|
|
176
216
|
const isEmpty = isEmptyField(current[field.key], field.type, hasMultipleOptions);
|
|
177
217
|
if (isEmpty) {
|
|
178
218
|
errors.push({
|
|
179
|
-
type: "
|
|
180
|
-
message: "
|
|
219
|
+
type: "error",
|
|
220
|
+
message: getErrorMessage("ERR015", null),
|
|
181
221
|
validator: { mandatory: true },
|
|
182
222
|
editorID: current.editorID ? current.editorID : null,
|
|
183
223
|
component: current.component ? current.component : null,
|
|
@@ -189,14 +229,42 @@ const getValidationErrors = (
|
|
|
189
229
|
}
|
|
190
230
|
}
|
|
191
231
|
|
|
192
|
-
if (
|
|
193
|
-
const {
|
|
232
|
+
if (current.component && field.isMockup) {
|
|
233
|
+
const { isMockup, defaultValue } = checkMockupContent(
|
|
234
|
+
current.component,
|
|
235
|
+
field.key,
|
|
236
|
+
field.type,
|
|
237
|
+
current[field.key]
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (isMockup) {
|
|
241
|
+
errors.push({
|
|
242
|
+
type: "error",
|
|
243
|
+
message: getErrorMessage("ERR016", null),
|
|
244
|
+
validator: { isMockup: { type: field.type, defaultValue } },
|
|
245
|
+
editorID: current.editorID ? current.editorID : null,
|
|
246
|
+
component: current.component ? current.component : null,
|
|
247
|
+
name: name ? name : field.title,
|
|
248
|
+
key: field.key,
|
|
249
|
+
tab,
|
|
250
|
+
template,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let fieldValidators: Record<string, unknown> = field.maxValue ? { maxValue: field.maxValue } : {};
|
|
256
|
+
fieldValidators = field.minValue ? { ...fieldValidators, minValue: field.minValue } : fieldValidators;
|
|
257
|
+
|
|
258
|
+
if (hasProps(field, ["validators"]) || Object.keys(fieldValidators).length) {
|
|
259
|
+
const allValidators = { ...field.validators, ...fieldValidators };
|
|
260
|
+
|
|
261
|
+
const { isValid, errorText } = getValidity(allValidators, current[field.key]);
|
|
194
262
|
|
|
195
263
|
if (!isValid) {
|
|
196
264
|
errors.push({
|
|
197
|
-
type: "
|
|
265
|
+
type: "error",
|
|
198
266
|
message: errorText,
|
|
199
|
-
validator:
|
|
267
|
+
validator: allValidators,
|
|
200
268
|
editorID: current.editorID ? current.editorID : null,
|
|
201
269
|
component: current.component ? current.component : null,
|
|
202
270
|
name: name ? name : field.title,
|
|
@@ -208,10 +276,13 @@ const getValidationErrors = (
|
|
|
208
276
|
}
|
|
209
277
|
|
|
210
278
|
if (Object.prototype.hasOwnProperty.call(field, "fields") && field.fields.length) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
279
|
+
let innerFields = field.fields;
|
|
280
|
+
|
|
281
|
+
if (field.type === "ConditionalField") {
|
|
282
|
+
innerFields = field.fields.filter((f: any) => f.condition === undefined || f.condition === current[field.key]);
|
|
283
|
+
const hiddenFields = field.fields.filter((f: any) => f.condition !== current[field.key]);
|
|
284
|
+
hiddenFields.forEach((field: any) => delete current[field.key]);
|
|
285
|
+
}
|
|
215
286
|
|
|
216
287
|
const innerErrors = getValidationErrors(innerFields, current, name, tab, template);
|
|
217
288
|
errors = [...errors, ...innerErrors];
|
|
@@ -221,6 +292,57 @@ const getValidationErrors = (
|
|
|
221
292
|
return errors;
|
|
222
293
|
};
|
|
223
294
|
|
|
295
|
+
const isTemplateActivated = (templates: ITemplate[], currentTemplateType: string): boolean =>
|
|
296
|
+
templates.find((temp: ITemplate) => temp.id === currentTemplateType) ? true : false;
|
|
297
|
+
|
|
298
|
+
const findPackagesActivationErrors = (
|
|
299
|
+
pageEditor: any,
|
|
300
|
+
modules: string[],
|
|
301
|
+
templates: ITemplate[]
|
|
302
|
+
): IErrorItem | null => {
|
|
303
|
+
const {
|
|
304
|
+
schema,
|
|
305
|
+
selectedContent: { component },
|
|
306
|
+
} = pageEditor;
|
|
307
|
+
|
|
308
|
+
let deactivatedModules: string[] = [];
|
|
309
|
+
let isCurrentTemplateActivated = true;
|
|
310
|
+
let hasDeactivatedModules = false;
|
|
311
|
+
|
|
312
|
+
const {
|
|
313
|
+
editorContent: { template },
|
|
314
|
+
} = pageEditor?.editorContent;
|
|
315
|
+
|
|
316
|
+
if (template) {
|
|
317
|
+
const mainContentModules = template?.mainContent?.modules;
|
|
318
|
+
|
|
319
|
+
if (mainContentModules) {
|
|
320
|
+
deactivatedModules = getDeactivatedModules(modules, mainContentModules);
|
|
321
|
+
hasDeactivatedModules = deactivatedModules.length > 0;
|
|
322
|
+
} else {
|
|
323
|
+
hasDeactivatedModules = isModuleDisabled(component, schema.schemaType, modules);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
isCurrentTemplateActivated = isTemplateActivated(templates, template.templateType);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!isCurrentTemplateActivated || hasDeactivatedModules) {
|
|
330
|
+
return {
|
|
331
|
+
type: "error",
|
|
332
|
+
message: getErrorMessage("ERR042", null),
|
|
333
|
+
validator: {},
|
|
334
|
+
editorID: null,
|
|
335
|
+
component: "",
|
|
336
|
+
name: "",
|
|
337
|
+
key: "",
|
|
338
|
+
tab: "",
|
|
339
|
+
template: false,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return null;
|
|
344
|
+
};
|
|
345
|
+
|
|
224
346
|
const findFieldsErrors = (content: any): IErrorItem[] => {
|
|
225
347
|
const queue: any[] = [content];
|
|
226
348
|
let errors: IErrorItem[] = [];
|
|
@@ -274,9 +396,55 @@ const findMandatoryStructuredDataErrors = (content: any, schema: any): IErrorIte
|
|
|
274
396
|
return errors;
|
|
275
397
|
};
|
|
276
398
|
|
|
399
|
+
const checkH1content = (content: any): IErrorItem | null => {
|
|
400
|
+
const h1s = content.getElementsByTagName("h1");
|
|
401
|
+
|
|
402
|
+
if (!h1s.length) {
|
|
403
|
+
return {
|
|
404
|
+
type: "warning",
|
|
405
|
+
message: getErrorMessage("ERR018", null),
|
|
406
|
+
validator: {},
|
|
407
|
+
editorID: null,
|
|
408
|
+
component: null,
|
|
409
|
+
name: "",
|
|
410
|
+
key: "",
|
|
411
|
+
tab: "",
|
|
412
|
+
template: false,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return null;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const parseValidationErrors = (errors: any[], content: any) => {
|
|
420
|
+
return errors.map((err: any) => {
|
|
421
|
+
const { element: module } = findByEditorID(content, err.editorID);
|
|
422
|
+
const schema = getSchema(module.component);
|
|
423
|
+
return {
|
|
424
|
+
type: "error",
|
|
425
|
+
message: getErrorMessage(err.error, null),
|
|
426
|
+
validator: { apiValidator: err.error },
|
|
427
|
+
editorID: err.editorID,
|
|
428
|
+
component: module.component,
|
|
429
|
+
name: schema.displayName,
|
|
430
|
+
key: err.key,
|
|
431
|
+
tab: "content",
|
|
432
|
+
template: false,
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
};
|
|
436
|
+
|
|
277
437
|
interface IError {
|
|
278
438
|
isValid: boolean;
|
|
279
439
|
errorCode: string;
|
|
280
440
|
}
|
|
281
441
|
|
|
282
|
-
export {
|
|
442
|
+
export {
|
|
443
|
+
getValidity,
|
|
444
|
+
isTemplateActivated,
|
|
445
|
+
findPackagesActivationErrors,
|
|
446
|
+
findFieldsErrors,
|
|
447
|
+
findMandatoryStructuredDataErrors,
|
|
448
|
+
checkH1content,
|
|
449
|
+
parseValidationErrors,
|
|
450
|
+
};
|
|
@@ -8,4 +8,11 @@ const getActivatedDataPacksIds = (activatedDataPacks: any) => {
|
|
|
8
8
|
const isModuleDisabled = (selectedComponent: string, type: string, activatedModules: string[]): boolean =>
|
|
9
9
|
type === "module" && !activatedModules.includes(selectedComponent);
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const getDeactivatedModules = (modules: any, currentModules: any) => {
|
|
12
|
+
const deactivatedModules = currentModules
|
|
13
|
+
.map((module: any) => (isModuleDisabled(module.component, "module", modules) ? module.component : null))
|
|
14
|
+
.filter((module: string | null) => !!module);
|
|
15
|
+
return deactivatedModules;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { getActivatedDataPacksIds, isModuleDisabled, getDeactivatedModules };
|
package/src/helpers/index.tsx
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
getNullValue,
|
|
35
35
|
removeEditorIds,
|
|
36
36
|
trimObject,
|
|
37
|
+
hasProps,
|
|
37
38
|
} from "./objects";
|
|
38
39
|
|
|
39
40
|
import {
|
|
@@ -91,7 +92,7 @@ import { imageResizeCropAndCompress, compressImage } from "./imageResize";
|
|
|
91
92
|
|
|
92
93
|
import { isEmptyArray, moveArrayElement } from "./arrays";
|
|
93
94
|
|
|
94
|
-
import { getActivatedDataPacksIds, isModuleDisabled } from "./dataPacks";
|
|
95
|
+
import { getActivatedDataPacksIds, isModuleDisabled, getDeactivatedModules } from "./dataPacks";
|
|
95
96
|
|
|
96
97
|
import { isDevelopment } from "./environment";
|
|
97
98
|
|
|
@@ -114,6 +115,7 @@ export {
|
|
|
114
115
|
getNullValue,
|
|
115
116
|
removeEditorIds,
|
|
116
117
|
trimObject,
|
|
118
|
+
hasProps,
|
|
117
119
|
filterDuplicatedValues,
|
|
118
120
|
areEquals,
|
|
119
121
|
removeMenuEditorIds,
|
|
@@ -153,6 +155,7 @@ export {
|
|
|
153
155
|
handleRequest,
|
|
154
156
|
getActivatedDataPacksIds,
|
|
155
157
|
isModuleDisabled,
|
|
158
|
+
getDeactivatedModules,
|
|
156
159
|
getInitials,
|
|
157
160
|
getSchemaType,
|
|
158
161
|
getModuleCategories,
|
package/src/helpers/objects.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IMenuItem } from "@ax/types";
|
|
2
2
|
|
|
3
|
-
const isEmptyObj = (obj: any) => {
|
|
3
|
+
const isEmptyObj = (obj: any): boolean => {
|
|
4
4
|
for (const key in obj) {
|
|
5
5
|
if (Object.prototype.hasOwnProperty.call(obj, key)) return false;
|
|
6
6
|
}
|
|
@@ -9,7 +9,7 @@ const isEmptyObj = (obj: any) => {
|
|
|
9
9
|
|
|
10
10
|
const deepClone = (obj: any) => JSON.parse(JSON.stringify(obj));
|
|
11
11
|
|
|
12
|
-
const isSelectedEditorID = (element: any, id: number) => element.editorID === id;
|
|
12
|
+
const isSelectedEditorID = (element: any, id: number): boolean => element.editorID === id;
|
|
13
13
|
|
|
14
14
|
const resetMultipleValues = (containerValue: any) => {
|
|
15
15
|
const { id } = containerValue;
|
|
@@ -103,6 +103,13 @@ const trimObject = (obj: any) => {
|
|
|
103
103
|
return obj;
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
+
const hasProps = (obj: Record<string, unknown>, props: string[]): boolean => {
|
|
107
|
+
return (
|
|
108
|
+
!!props &&
|
|
109
|
+
props.map((prop) => Object.prototype.hasOwnProperty.call(obj, prop)).filter(Boolean).length === props.length
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
106
113
|
export {
|
|
107
114
|
isEmptyObj,
|
|
108
115
|
deepClone,
|
|
@@ -115,4 +122,5 @@ export {
|
|
|
115
122
|
getNullValue,
|
|
116
123
|
removeEditorIds,
|
|
117
124
|
trimObject,
|
|
125
|
+
hasProps,
|
|
118
126
|
};
|
|
@@ -24,6 +24,7 @@ const CategoryItem = (props: ICategoryItemProps): JSX.Element => {
|
|
|
24
24
|
onChange,
|
|
25
25
|
toggleToast,
|
|
26
26
|
setDeletedItem,
|
|
27
|
+
getContents,
|
|
27
28
|
} = props;
|
|
28
29
|
|
|
29
30
|
const { isOpen, toggleModal } = useModal();
|
|
@@ -140,7 +141,7 @@ const CategoryItem = (props: ICategoryItemProps): JSX.Element => {
|
|
|
140
141
|
<S.StyledActionMenu icon="more" options={menuOptions} tooltip="Actions" />
|
|
141
142
|
</S.ActionsCell>
|
|
142
143
|
</S.CategoryRow>
|
|
143
|
-
<CategoryPanel isOpen={isOpen} toggleModal={toggleModal} item={category} />
|
|
144
|
+
<CategoryPanel isOpen={isOpen} toggleModal={toggleModal} item={category} getContents={getContents}/>
|
|
144
145
|
</>
|
|
145
146
|
);
|
|
146
147
|
};
|
|
@@ -154,6 +155,7 @@ interface IProps {
|
|
|
154
155
|
onChange: (e: any) => void;
|
|
155
156
|
toggleToast(): void;
|
|
156
157
|
setDeletedItem(item: number): void;
|
|
158
|
+
getContents(dataId: string): void;
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
interface IDispatchProps {
|
|
@@ -16,6 +16,7 @@ const CategoryPanel = (props: IProps): JSX.Element => {
|
|
|
16
16
|
item,
|
|
17
17
|
createStructuredDataContent,
|
|
18
18
|
updateStructuredDataContent,
|
|
19
|
+
getContents,
|
|
19
20
|
currentStructuredData,
|
|
20
21
|
category,
|
|
21
22
|
entity,
|
|
@@ -41,18 +42,22 @@ const CategoryPanel = (props: IProps): JSX.Element => {
|
|
|
41
42
|
newCategory = { ...newCategory, id: category.isTranslation ? category.id : item.id };
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
const addItemAction = () => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
const addItemAction = async () => {
|
|
46
|
+
const langID = lang && category.isTranslation ? lang.id : undefined;
|
|
47
|
+
const isCreated = await createStructuredDataContent(newCategory, langID);
|
|
48
|
+
|
|
49
|
+
if (isCreated && currentStructuredData) {
|
|
50
|
+
getContents(currentStructuredData.id);
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
toggleModal();
|
|
52
54
|
};
|
|
53
55
|
|
|
54
|
-
const editItemAction = () => {
|
|
55
|
-
updateStructuredDataContent(newCategory);
|
|
56
|
+
const editItemAction = async () => {
|
|
57
|
+
const isUpdated = await updateStructuredDataContent(newCategory);
|
|
58
|
+
if (isUpdated && currentStructuredData) {
|
|
59
|
+
getContents(currentStructuredData.id);
|
|
60
|
+
}
|
|
56
61
|
toggleModal();
|
|
57
62
|
};
|
|
58
63
|
|
|
@@ -122,11 +127,12 @@ interface ICategoryPanelProps {
|
|
|
122
127
|
item?: IStructuredDataContent;
|
|
123
128
|
isOpen: boolean;
|
|
124
129
|
toggleModal(): any;
|
|
130
|
+
getContents(dataId: string): void;
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
interface IDispatchProps {
|
|
128
|
-
createStructuredDataContent(category: IStructuredDataContent, langId?: number | null)
|
|
129
|
-
updateStructuredDataContent(category: IStructuredDataContent)
|
|
134
|
+
createStructuredDataContent: (category: IStructuredDataContent, langId?: number | null) => Promise<boolean>;
|
|
135
|
+
updateStructuredDataContent: (category: IStructuredDataContent) => Promise<boolean>;
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
type IProps = IDispatchProps & ICategoryPanelProps & IStateProps;
|
|
@@ -209,6 +209,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
|
|
|
209
209
|
onChange={addToBulkSelection}
|
|
210
210
|
toggleToast={toggleToast}
|
|
211
211
|
setDeletedItem={setDeletedItem}
|
|
212
|
+
getContents={getContents}
|
|
212
213
|
/>
|
|
213
214
|
);
|
|
214
215
|
})
|
|
@@ -216,7 +217,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
|
|
|
216
217
|
</TableList>
|
|
217
218
|
</S.TableWrapper>
|
|
218
219
|
</S.CategoryListWrapper>
|
|
219
|
-
<CategoryPanel isOpen={isOpen} toggleModal={toggleModal} />
|
|
220
|
+
<CategoryPanel isOpen={isOpen} toggleModal={toggleModal} getContents={getContents}/>
|
|
220
221
|
{isVisible && <Toast {...toastProps} />}
|
|
221
222
|
</MainWrapper>
|
|
222
223
|
);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React, { memo, useState } from "react";
|
|
2
2
|
|
|
3
|
+
import { schemas } from "components";
|
|
4
|
+
|
|
3
5
|
import { useModal } from "@ax/hooks";
|
|
4
6
|
import { getHumanLastModifiedDate, getTemplateDisplayName, slugify } from "@ax/helpers";
|
|
5
7
|
import { IPage, ISite, ISavePageParams, ICheck, IColumn, IPageLanguage, IDataPack } from "@ax/types";
|
|
@@ -18,7 +20,7 @@ import {
|
|
|
18
20
|
CategoryCell,
|
|
19
21
|
} from "@ax/components";
|
|
20
22
|
|
|
21
|
-
import { DeleteModal } from "../atoms";
|
|
23
|
+
import { DeleteModal, CopyModal } from "../atoms";
|
|
22
24
|
|
|
23
25
|
import * as S from "./style";
|
|
24
26
|
|
|
@@ -34,6 +36,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
|
|
|
34
36
|
categoryColors,
|
|
35
37
|
addCategoryColors,
|
|
36
38
|
dataPacks,
|
|
39
|
+
sites,
|
|
37
40
|
} = props;
|
|
38
41
|
const { isSelected, siteLanguages, page, lang, isDuplicable } = item;
|
|
39
42
|
const {
|
|
@@ -50,6 +53,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
|
|
|
50
53
|
deleteBulk,
|
|
51
54
|
getDataPack,
|
|
52
55
|
setTemplateInstanceError,
|
|
56
|
+
toggleCopiedToast,
|
|
53
57
|
} = functions;
|
|
54
58
|
const { locale } = lang;
|
|
55
59
|
const {
|
|
@@ -65,13 +69,18 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
|
|
|
65
69
|
const displayName = getTemplateDisplayName(templateId);
|
|
66
70
|
|
|
67
71
|
const initValue = { title: "", slug: "" };
|
|
72
|
+
const [site, setSite] = useState(null);
|
|
68
73
|
const [modalState, setModalState] = useState(initValue);
|
|
69
74
|
const [deleteAllVersions, setDeleteAllVersions] = useState(false);
|
|
70
75
|
const { isOpen, toggleModal } = useModal();
|
|
71
76
|
const { isOpen: isRemoveOpen, toggleModal: toggleRemoveModal } = useModal();
|
|
72
77
|
const { isOpen: isUnpublishOpen, toggleModal: toggleUnpublishModal } = useModal();
|
|
73
78
|
const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
|
|
79
|
+
const { isOpen: isCopyOpen, toggleModal: toggleCopyModal } = useModal();
|
|
80
|
+
|
|
81
|
+
const currentTemplateDataPacks = schemas.templates[templateId].dataPacks;
|
|
74
82
|
|
|
83
|
+
const isCopyable = !currentTemplateDataPacks;
|
|
75
84
|
const isGlobal = origin === "GLOBAL";
|
|
76
85
|
const isTranslated = pageLanguages.length > 1;
|
|
77
86
|
const activeColumns = Object.keys(columns).filter((col: string) => columns[col].show);
|
|
@@ -311,6 +320,13 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
|
|
|
311
320
|
action: toggleModal,
|
|
312
321
|
};
|
|
313
322
|
|
|
323
|
+
const copyOption = {
|
|
324
|
+
label: "Copy page in another site",
|
|
325
|
+
icon: "copy",
|
|
326
|
+
action: toggleCopyModal,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
if (isCopyable) menuOptions.unshift(copyOption);
|
|
314
330
|
if (!isGlobal) menuOptions.unshift(duplicateOption);
|
|
315
331
|
|
|
316
332
|
const getPublishItem = (status: string, canBeUnpublished: boolean) => {
|
|
@@ -393,6 +409,26 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
|
|
|
393
409
|
|
|
394
410
|
const secondaryRemoveModalAction = { title: "Cancel", onClick: toggleRemoveModal };
|
|
395
411
|
|
|
412
|
+
const copyToOtherSite = () => {
|
|
413
|
+
if (site) {
|
|
414
|
+
const siteID = parseInt(site);
|
|
415
|
+
|
|
416
|
+
duplicatePage(page.id, null, siteID).then((successEvent: boolean) => {
|
|
417
|
+
if (successEvent === true) {
|
|
418
|
+
toggleCopiedToast();
|
|
419
|
+
}
|
|
420
|
+
toggleCopyModal();
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const secondaryCopyModalAction = {
|
|
426
|
+
title: "Cancel",
|
|
427
|
+
onClick: toggleCopyModal,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const mainCopyModalAction = { title: "Copy page", onClick: copyToOtherSite, disabled: !site };
|
|
431
|
+
|
|
396
432
|
const getLiveStatus = () => (page.haveDraftPage ? "modified" : page.liveStatus.status);
|
|
397
433
|
|
|
398
434
|
const mainUnpublishAction = { title: "Ok", onClick: toggleUnpublishModal };
|
|
@@ -471,6 +507,18 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
|
|
|
471
507
|
<S.StyledActionMenu icon="more" options={menuOptions} tooltip="Page actions" />
|
|
472
508
|
</S.ActionsCell>
|
|
473
509
|
</S.PageRow>
|
|
510
|
+
<CopyModal
|
|
511
|
+
isOpen={isCopyOpen}
|
|
512
|
+
toggleModal={() => {
|
|
513
|
+
setSite(null);
|
|
514
|
+
toggleCopyModal();
|
|
515
|
+
}}
|
|
516
|
+
mainModalAction={mainCopyModalAction}
|
|
517
|
+
secondaryModalAction={secondaryCopyModalAction}
|
|
518
|
+
sites={sites}
|
|
519
|
+
site={site}
|
|
520
|
+
setSite={setSite}
|
|
521
|
+
/>
|
|
474
522
|
<Modal
|
|
475
523
|
isOpen={isOpen}
|
|
476
524
|
hide={toggleModal}
|
|
@@ -576,11 +624,12 @@ interface IPageItemProps {
|
|
|
576
624
|
setHistoryPush(path: string, isEditor: boolean): void;
|
|
577
625
|
updatePageStatus(ids: number[], status: string, updatedFromList: boolean): Promise<boolean>;
|
|
578
626
|
setCurrentPageID(currentPageID: number | null): ISetCurrentPageIDAction;
|
|
579
|
-
duplicatePage(pageID: number, data: any): Promise<
|
|
627
|
+
duplicatePage(pageID: number, data: any, siteID?: number): Promise<boolean>;
|
|
580
628
|
removePageFromSite(pageID: number): Promise<boolean>;
|
|
581
629
|
deleteBulk(ids: number[]): void;
|
|
582
630
|
setTemplateInstanceError(error: any): void;
|
|
583
631
|
getDataPack: (id: string) => Promise<void>;
|
|
632
|
+
toggleCopiedToast(): void;
|
|
584
633
|
};
|
|
585
634
|
activatedTemplates: any[];
|
|
586
635
|
toggleToast(): void;
|
|
@@ -590,6 +639,7 @@ interface IPageItemProps {
|
|
|
590
639
|
categoryColors: any;
|
|
591
640
|
addCategoryColors(cats: string[]): void;
|
|
592
641
|
dataPacks: IDataPack[];
|
|
642
|
+
sites: ISite[];
|
|
593
643
|
}
|
|
594
644
|
|
|
595
645
|
export default memo(PageItem);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import { IModal } from "@ax/types";
|
|
4
|
-
import { Modal, FieldsBehavior, Button } from "@ax/components";
|
|
3
|
+
import { IModal, ISite } from "@ax/types";
|
|
4
|
+
import { Modal, FieldsBehavior, Select, Button } from "@ax/components";
|
|
5
5
|
|
|
6
6
|
import * as S from "./style";
|
|
7
7
|
|
|
@@ -80,6 +80,38 @@ const SecondaryActionButton = (props: IActionButton): JSX.Element => (
|
|
|
80
80
|
</Button>
|
|
81
81
|
);
|
|
82
82
|
|
|
83
|
+
const CopyModal = (props: ICopyModal): JSX.Element => {
|
|
84
|
+
const { isOpen, toggleModal, mainModalAction, secondaryModalAction, setSite, sites, site } = props;
|
|
85
|
+
const sitesOptions = sites.map((site: ISite) => ({ label: site.name, value: site.id.toString() }));
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Modal
|
|
89
|
+
isOpen={isOpen}
|
|
90
|
+
hide={toggleModal}
|
|
91
|
+
size="S"
|
|
92
|
+
title="Copy page in another site"
|
|
93
|
+
mainAction={mainModalAction}
|
|
94
|
+
secondaryAction={secondaryModalAction}
|
|
95
|
+
>
|
|
96
|
+
<S.ModalContent>
|
|
97
|
+
<p>
|
|
98
|
+
<strong>Select a site to copy this page. </strong>
|
|
99
|
+
You can only select sites with the same language as this page.
|
|
100
|
+
</p>
|
|
101
|
+
<S.SelectWrapper>
|
|
102
|
+
<Select
|
|
103
|
+
name="select"
|
|
104
|
+
options={sitesOptions}
|
|
105
|
+
onChange={(value: string) => setSite(value)}
|
|
106
|
+
value={site?.toString() || ""}
|
|
107
|
+
mandatory={true}
|
|
108
|
+
/>
|
|
109
|
+
</S.SelectWrapper>
|
|
110
|
+
</S.ModalContent>
|
|
111
|
+
</Modal>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
83
115
|
interface IDeleteModal extends IModal {
|
|
84
116
|
isTranslated: boolean;
|
|
85
117
|
deleteAllVersions: boolean;
|
|
@@ -92,4 +124,10 @@ interface IActionButton {
|
|
|
92
124
|
title: string;
|
|
93
125
|
}
|
|
94
126
|
|
|
95
|
-
|
|
127
|
+
interface ICopyModal extends IModal {
|
|
128
|
+
setSite: React.Dispatch<React.SetStateAction<any>>;
|
|
129
|
+
sites: ISite[];
|
|
130
|
+
site: string | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { DeleteModal, MainActionButton, SecondaryActionButton, CopyModal };
|