@griddo/ax 1.75.254 → 1.75.256
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/SummaryButton/SummaryButton.test.tsx +54 -0
- package/src/__tests__/components/Fields/TagField/TagField.test.tsx +34 -34
- package/src/__tests__/components/Fields/TranslateButton/TranslateButton.test.tsx +118 -0
- package/src/__tests__/components/Gallery/GalleryPanel/DetailPanel/DetailPanel.test.tsx +4 -4
- package/src/api/pages.tsx +23 -0
- package/src/components/Button/index.tsx +5 -7
- package/src/components/Button/style.tsx +3 -0
- package/src/components/Fields/ReferenceField/ManualPanel/index.tsx +2 -2
- package/src/components/Fields/SummaryButton/index.tsx +76 -0
- package/src/components/Fields/SummaryButton/style.tsx +33 -0
- package/src/components/Fields/{TagField → TagsField}/index.tsx +2 -2
- package/src/components/Fields/TranslateButton/index.tsx +108 -0
- package/src/components/Fields/TranslateButton/style.tsx +48 -0
- package/src/components/Fields/index.tsx +6 -2
- package/src/components/Gallery/GalleryPanel/DetailPanel/index.tsx +1 -1
- package/src/components/Icon/components/Ia.js +12 -0
- package/src/components/Icon/svgs/IA.svg +3 -0
- package/src/components/MainWrapper/AppBar/atoms.tsx +2 -1
- package/src/components/MainWrapper/AppBar/style.tsx +9 -0
- package/src/components/Modal/index.tsx +2 -1
- package/src/components/index.tsx +4 -0
- package/src/containers/App/reducer.tsx +4 -0
- package/src/containers/PageEditor/actions.tsx +79 -1
- package/src/containers/PageEditor/constants.tsx +1 -0
- package/src/containers/PageEditor/interfaces.tsx +6 -0
- package/src/containers/PageEditor/reducer.tsx +4 -0
- package/src/modules/PageEditor/index.tsx +5 -0
- package/src/schemas/pages/GlobalPage.tsx +15 -0
- package/src/schemas/pages/Page.tsx +15 -0
- package/src/types/index.tsx +3 -0
- /package/src/components/Fields/{TagField → TagsField}/style.tsx +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griddo/ax",
|
|
3
3
|
"description": "Griddo Author Experience",
|
|
4
|
-
"version": "1.75.
|
|
4
|
+
"version": "1.75.256",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
|
|
7
7
|
"Carlos Torres <carlos.torres@secuoyas.com>",
|
|
@@ -230,5 +230,5 @@
|
|
|
230
230
|
"publishConfig": {
|
|
231
231
|
"access": "public"
|
|
232
232
|
},
|
|
233
|
-
"gitHead": "
|
|
233
|
+
"gitHead": "5a8f1a970ca0b3eab2407b462004a3476c9f8abb"
|
|
234
234
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ThemeProvider } from "styled-components";
|
|
3
|
+
import { parseTheme } from "@ax/helpers";
|
|
4
|
+
import globalTheme from "@ax/themes/theme.json";
|
|
5
|
+
import { render, cleanup, screen } from "../../../../../config/jest/test-utils";
|
|
6
|
+
import { mock } from "jest-mock-extended";
|
|
7
|
+
import configureStore from "redux-mock-store";
|
|
8
|
+
import thunk from "redux-thunk";
|
|
9
|
+
import SummaryButton, { ISummaryButtonProps } from "@ax/components/Fields/SummaryButton";
|
|
10
|
+
|
|
11
|
+
afterEach(cleanup);
|
|
12
|
+
|
|
13
|
+
const middlewares: any = [thunk];
|
|
14
|
+
const mockStore = configureStore(middlewares);
|
|
15
|
+
|
|
16
|
+
const defaultProps = mock<ISummaryButtonProps>();
|
|
17
|
+
|
|
18
|
+
describe("Summary Button component rendering", () => {
|
|
19
|
+
it("should render the component", () => {
|
|
20
|
+
const initialStore = {
|
|
21
|
+
app: { globalSettings: { autoSummary: true } },
|
|
22
|
+
};
|
|
23
|
+
const store = mockStore(initialStore);
|
|
24
|
+
|
|
25
|
+
render(
|
|
26
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
27
|
+
<SummaryButton {...defaultProps} />
|
|
28
|
+
</ThemeProvider>,
|
|
29
|
+
{ store }
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const summaryButtonWrapper = screen.getByTestId("summary-button-wrapper");
|
|
33
|
+
|
|
34
|
+
expect(summaryButtonWrapper).toBeTruthy();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should not render the component", () => {
|
|
38
|
+
const initialStore = {
|
|
39
|
+
app: { globalSettings: { autoSummary: false } },
|
|
40
|
+
};
|
|
41
|
+
const store = mockStore(initialStore);
|
|
42
|
+
|
|
43
|
+
render(
|
|
44
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
45
|
+
<SummaryButton {...defaultProps} />
|
|
46
|
+
</ThemeProvider>,
|
|
47
|
+
{ store }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const summaryButtonWrapper = screen.queryByTestId("summary-button-wrapper");
|
|
51
|
+
|
|
52
|
+
expect(summaryButtonWrapper).toBeFalsy();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import
|
|
2
|
+
import TagsField from "@ax/components/Fields/TagsField";
|
|
3
3
|
import { ThemeProvider } from "styled-components";
|
|
4
4
|
import { parseTheme } from "@ax/helpers";
|
|
5
5
|
import globalTheme from "@ax/themes/theme.json";
|
|
@@ -12,21 +12,21 @@ const props = {
|
|
|
12
12
|
onChange: jest.fn(),
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
describe("
|
|
15
|
+
describe("TagsField component", () => {
|
|
16
16
|
test("should render the component", () => {
|
|
17
17
|
render(
|
|
18
18
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
19
|
-
<
|
|
19
|
+
<TagsField {...props} />
|
|
20
20
|
</ThemeProvider>
|
|
21
21
|
);
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
expect(
|
|
25
|
-
expect(
|
|
23
|
+
const tagsFieldWrapper = screen.getAllByTestId("tag-field-wrapper");
|
|
24
|
+
expect(tagsFieldWrapper).toBeTruthy();
|
|
25
|
+
expect(tagsFieldWrapper).toHaveLength(1);
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
expect(
|
|
29
|
-
expect(
|
|
27
|
+
const tagsFieldInput = screen.getAllByTestId("tag-field-input");
|
|
28
|
+
expect(tagsFieldInput).toBeTruthy();
|
|
29
|
+
expect(tagsFieldInput).toHaveLength(1);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
test("should render the tags if have values", () => {
|
|
@@ -37,17 +37,17 @@ describe("TagField component", () => {
|
|
|
37
37
|
|
|
38
38
|
render(
|
|
39
39
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
40
|
-
<
|
|
40
|
+
<TagsField {...defaultProps} />
|
|
41
41
|
</ThemeProvider>
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
-
const
|
|
45
|
-
expect(
|
|
44
|
+
const tagsFieldWrapper = screen.getByTestId("tag-field-wrapper");
|
|
45
|
+
expect(tagsFieldWrapper).toBeTruthy();
|
|
46
46
|
|
|
47
|
-
const
|
|
48
|
-
expect(
|
|
47
|
+
const tagsFieldInput = screen.getByTestId("tag-field-input");
|
|
48
|
+
expect(tagsFieldInput).toBeTruthy();
|
|
49
49
|
|
|
50
|
-
const tagRendered = within(
|
|
50
|
+
const tagRendered = within(tagsFieldWrapper).getAllByTestId("delete-icon-wrapper");
|
|
51
51
|
expect(tagRendered).toHaveLength(2);
|
|
52
52
|
|
|
53
53
|
const tagComponent = screen.getByTestId("tag-field-wrapper");
|
|
@@ -55,17 +55,17 @@ describe("TagField component", () => {
|
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
describe("
|
|
58
|
+
describe("TagsField events", () => {
|
|
59
59
|
test("should call the value when call onchange", () => {
|
|
60
60
|
render(
|
|
61
61
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
62
|
-
<
|
|
62
|
+
<TagsField {...props} />
|
|
63
63
|
</ThemeProvider>
|
|
64
64
|
);
|
|
65
|
-
const
|
|
65
|
+
const TagsFieldInput = screen.getByTestId<HTMLInputElement>("tag-field-input");
|
|
66
66
|
|
|
67
|
-
fireEvent.change(
|
|
68
|
-
expect(
|
|
67
|
+
fireEvent.change(TagsFieldInput, { target: { value: "test" } });
|
|
68
|
+
expect(TagsFieldInput.value).toBe("test");
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
test("should add new value on keyDown if it's not in array", () => {
|
|
@@ -76,13 +76,13 @@ describe("TagField events", () => {
|
|
|
76
76
|
};
|
|
77
77
|
render(
|
|
78
78
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
79
|
-
<
|
|
79
|
+
<TagsField {...defaultProps} />
|
|
80
80
|
</ThemeProvider>
|
|
81
81
|
);
|
|
82
82
|
|
|
83
|
-
const
|
|
84
|
-
fireEvent.input(
|
|
85
|
-
fireEvent.keyDown(
|
|
83
|
+
const tagsFieldInput = screen.getByTestId<HTMLInputElement>("tag-field-input");
|
|
84
|
+
fireEvent.input(tagsFieldInput, { target: { value: "test" } });
|
|
85
|
+
fireEvent.keyDown(tagsFieldInput, { key: "Enter", code: "Enter", charCode: 13 });
|
|
86
86
|
expect(onChange).toHaveBeenCalledWith(["test"]);
|
|
87
87
|
});
|
|
88
88
|
test("shouldn't add new value on keyDown if it's in array", () => {
|
|
@@ -93,13 +93,13 @@ describe("TagField events", () => {
|
|
|
93
93
|
};
|
|
94
94
|
render(
|
|
95
95
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
96
|
-
<
|
|
96
|
+
<TagsField {...defaultProps} />
|
|
97
97
|
</ThemeProvider>
|
|
98
98
|
);
|
|
99
99
|
|
|
100
|
-
const
|
|
101
|
-
fireEvent.input(
|
|
102
|
-
fireEvent.keyDown(
|
|
100
|
+
const tagsFieldInput = screen.getByTestId<HTMLInputElement>("tag-field-input");
|
|
101
|
+
fireEvent.input(tagsFieldInput, { target: { value: "test" } });
|
|
102
|
+
fireEvent.keyDown(tagsFieldInput, { key: "Enter", code: "Enter", charCode: 13 });
|
|
103
103
|
expect(onChange).toBeCalledTimes(0);
|
|
104
104
|
});
|
|
105
105
|
test("should delete value from array", () => {
|
|
@@ -111,12 +111,12 @@ describe("TagField events", () => {
|
|
|
111
111
|
|
|
112
112
|
render(
|
|
113
113
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
114
|
-
<
|
|
114
|
+
<TagsField {...defaultProps} />
|
|
115
115
|
</ThemeProvider>
|
|
116
116
|
);
|
|
117
117
|
|
|
118
|
-
const
|
|
119
|
-
const tagRendered = within(
|
|
118
|
+
const tagsFieldWrapper = screen.getByTestId("tag-field-wrapper");
|
|
119
|
+
const tagRendered = within(tagsFieldWrapper).getAllByTestId("delete-icon-wrapper");
|
|
120
120
|
expect(tagRendered).toHaveLength(2);
|
|
121
121
|
fireEvent.click(tagRendered[0], 0);
|
|
122
122
|
expect(onChange).toBeCalledTimes(1);
|
|
@@ -126,11 +126,11 @@ describe("TagField events", () => {
|
|
|
126
126
|
const useRefSpy = jest.spyOn(React, "useRef").mockReturnValueOnce({ current: { focus: jest.fn() } });
|
|
127
127
|
render(
|
|
128
128
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
129
|
-
<
|
|
129
|
+
<TagsField {...props} />
|
|
130
130
|
</ThemeProvider>
|
|
131
131
|
);
|
|
132
|
-
const
|
|
133
|
-
fireEvent.click(
|
|
132
|
+
const tagsFieldWrapper = screen.getByTestId("tag-field-wrapper");
|
|
133
|
+
fireEvent.click(tagsFieldWrapper);
|
|
134
134
|
expect(useRefSpy).toBeCalledTimes(1);
|
|
135
135
|
});
|
|
136
136
|
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ThemeProvider } from "styled-components";
|
|
3
|
+
import { parseTheme } from "@ax/helpers";
|
|
4
|
+
import globalTheme from "@ax/themes/theme.json";
|
|
5
|
+
import { render, cleanup, screen } from "../../../../../config/jest/test-utils";
|
|
6
|
+
import { mock } from "jest-mock-extended";
|
|
7
|
+
import configureStore from "redux-mock-store";
|
|
8
|
+
import thunk from "redux-thunk";
|
|
9
|
+
import TranslateButton, { ITranslateButtonProps } from "@ax/components/Fields/TranslateButton";
|
|
10
|
+
|
|
11
|
+
afterEach(cleanup);
|
|
12
|
+
|
|
13
|
+
const middlewares: any = [thunk];
|
|
14
|
+
const mockStore = configureStore(middlewares);
|
|
15
|
+
|
|
16
|
+
const defaultProps = mock<ITranslateButtonProps>();
|
|
17
|
+
|
|
18
|
+
describe("Translate Button component rendering", () => {
|
|
19
|
+
it("should render the component", () => {
|
|
20
|
+
const initialStore = {
|
|
21
|
+
app: { globalSettings: { autoTranslation: true } },
|
|
22
|
+
pageEditor: { editorContent: { canBeTranslated: true }, isIATranslated: false },
|
|
23
|
+
};
|
|
24
|
+
const store = mockStore(initialStore);
|
|
25
|
+
|
|
26
|
+
render(
|
|
27
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
28
|
+
<TranslateButton {...defaultProps} />
|
|
29
|
+
</ThemeProvider>,
|
|
30
|
+
{ store }
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const translateButtonWrapper = screen.getByTestId("translate-button-wrapper");
|
|
34
|
+
expect(translateButtonWrapper).toBeTruthy();
|
|
35
|
+
const translatedNotificationWrapper = screen.queryByTestId("translated-notification-wrapper");
|
|
36
|
+
expect(translatedNotificationWrapper).toBeFalsy();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should not render the component if setting is off", () => {
|
|
40
|
+
const initialStore = {
|
|
41
|
+
app: { globalSettings: { autoTranslation: false } },
|
|
42
|
+
pageEditor: { editorContent: { canBeTranslated: true }, isIATranslated: false },
|
|
43
|
+
};
|
|
44
|
+
const store = mockStore(initialStore);
|
|
45
|
+
|
|
46
|
+
render(
|
|
47
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
48
|
+
<TranslateButton {...defaultProps} />
|
|
49
|
+
</ThemeProvider>,
|
|
50
|
+
{ store }
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const translateButtonWrapper = screen.queryByTestId("translate-button-wrapper");
|
|
54
|
+
const translatedNotificationWrapper = screen.queryByTestId("translated-notification-wrapper");
|
|
55
|
+
expect(translatedNotificationWrapper).toBeFalsy();
|
|
56
|
+
expect(translateButtonWrapper).toBeFalsy();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should not render the component if can't be translated", () => {
|
|
60
|
+
const initialStore = {
|
|
61
|
+
app: { globalSettings: { autoTranslation: true } },
|
|
62
|
+
pageEditor: { editorContent: { canBeTranslated: false }, isIATranslated: false },
|
|
63
|
+
};
|
|
64
|
+
const store = mockStore(initialStore);
|
|
65
|
+
|
|
66
|
+
render(
|
|
67
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
68
|
+
<TranslateButton {...defaultProps} />
|
|
69
|
+
</ThemeProvider>,
|
|
70
|
+
{ store }
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const translateButtonWrapper = screen.queryByTestId("translate-button-wrapper");
|
|
74
|
+
const translatedNotificationWrapper = screen.queryByTestId("translated-notification-wrapper");
|
|
75
|
+
expect(translatedNotificationWrapper).toBeFalsy();
|
|
76
|
+
expect(translateButtonWrapper).toBeFalsy();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should render the notification if it's already translated", () => {
|
|
80
|
+
const initialStore = {
|
|
81
|
+
app: { globalSettings: { autoTranslation: true } },
|
|
82
|
+
pageEditor: { editorContent: { canBeTranslated: false }, isIATranslated: true },
|
|
83
|
+
};
|
|
84
|
+
const store = mockStore(initialStore);
|
|
85
|
+
|
|
86
|
+
render(
|
|
87
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
88
|
+
<TranslateButton {...defaultProps} />
|
|
89
|
+
</ThemeProvider>,
|
|
90
|
+
{ store }
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const translateButtonWrapper = screen.queryByTestId("translate-button-wrapper");
|
|
94
|
+
const translatedNotificationWrapper = screen.queryByTestId("translated-notification-wrapper");
|
|
95
|
+
expect(translatedNotificationWrapper).toBeTruthy();
|
|
96
|
+
expect(translateButtonWrapper).toBeFalsy();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should render the component if it's already translated but can be translated", () => {
|
|
100
|
+
const initialStore = {
|
|
101
|
+
app: { globalSettings: { autoTranslation: true } },
|
|
102
|
+
pageEditor: { editorContent: { canBeTranslated: true }, isIATranslated: true },
|
|
103
|
+
};
|
|
104
|
+
const store = mockStore(initialStore);
|
|
105
|
+
|
|
106
|
+
render(
|
|
107
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
108
|
+
<TranslateButton {...defaultProps} />
|
|
109
|
+
</ThemeProvider>,
|
|
110
|
+
{ store }
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const translateButtonWrapper = screen.getByTestId("translate-button-wrapper");
|
|
114
|
+
expect(translateButtonWrapper).toBeTruthy();
|
|
115
|
+
const translatedNotificationWrapper = screen.queryByTestId("translated-notification-wrapper");
|
|
116
|
+
expect(translatedNotificationWrapper).toBeFalsy();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -843,11 +843,11 @@ describe("Gallery component rendering", () => {
|
|
|
843
843
|
|
|
844
844
|
expect(screen.getByDisplayValue("a description")).toBeInTheDocument();
|
|
845
845
|
|
|
846
|
-
const
|
|
847
|
-
expect(
|
|
848
|
-
fireEvent.change(
|
|
846
|
+
const tagsFieldInput = screen.getByTestId("tag-field-input");
|
|
847
|
+
expect(tagsFieldInput).toBeTruthy();
|
|
848
|
+
fireEvent.change(tagsFieldInput, { target: { value: "a tag" } });
|
|
849
849
|
expect(screen.getByDisplayValue("a tag")).toBeInTheDocument();
|
|
850
|
-
fireEvent.keyDown(
|
|
850
|
+
fireEvent.keyDown(tagsFieldInput, { key: "Enter" });
|
|
851
851
|
expect(screen.queryByDisplayValue("a tag")).not.toBeInTheDocument();
|
|
852
852
|
});
|
|
853
853
|
});
|
package/src/api/pages.tsx
CHANGED
|
@@ -80,6 +80,16 @@ const SERVICES: { [key: string]: IServiceConfig } = {
|
|
|
80
80
|
endpoint: "/page/check",
|
|
81
81
|
method: "POST",
|
|
82
82
|
},
|
|
83
|
+
PAGE_AI_SUMMARY: {
|
|
84
|
+
...template,
|
|
85
|
+
endpoint: "/ai/summary/page",
|
|
86
|
+
method: "POST",
|
|
87
|
+
},
|
|
88
|
+
PAGE_AI_TRANSLATION: {
|
|
89
|
+
...template,
|
|
90
|
+
endpoint: "/translations/page/",
|
|
91
|
+
method: "POST",
|
|
92
|
+
},
|
|
83
93
|
};
|
|
84
94
|
|
|
85
95
|
const getPageInfo = async (pageID: number) => {
|
|
@@ -210,6 +220,17 @@ const pageCheck = async (data: any) => {
|
|
|
210
220
|
return sendRequest(SERVICES.PAGE_CHECK, { ...data });
|
|
211
221
|
};
|
|
212
222
|
|
|
223
|
+
const getPageSummary = async (data: any) => {
|
|
224
|
+
return sendRequest(SERVICES.PAGE_AI_SUMMARY, { ...data });
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const getPageTranslation = async (data: any, langID: number) => {
|
|
228
|
+
const { host, endpoint } = SERVICES.PAGE_AI_TRANSLATION;
|
|
229
|
+
SERVICES.PAGE_AI_TRANSLATION.dynamicUrl = `${host}${endpoint}${langID}`;
|
|
230
|
+
|
|
231
|
+
return sendRequest(SERVICES.PAGE_AI_TRANSLATION, { ...data });
|
|
232
|
+
};
|
|
233
|
+
|
|
213
234
|
export default {
|
|
214
235
|
getPageInfo,
|
|
215
236
|
updatePage,
|
|
@@ -226,4 +247,6 @@ export default {
|
|
|
226
247
|
sendPagePing,
|
|
227
248
|
getPublicPage,
|
|
228
249
|
pageCheck,
|
|
250
|
+
getPageSummary,
|
|
251
|
+
getPageTranslation
|
|
229
252
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Icon } from "@ax/components";
|
|
2
|
+
import { Icon, Loader } from "@ax/components";
|
|
3
3
|
|
|
4
4
|
import * as S from "./style";
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ enum buttonStyles {
|
|
|
10
10
|
INVERSE = "lineInverse",
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const Button = ({ children, type, disabled, icon, buttonStyle, onClick, className }: IButtonProps): JSX.Element => {
|
|
13
|
+
const Button = ({ children, type, disabled, icon, buttonStyle, onClick, className, loader }: IButtonProps): JSX.Element => {
|
|
14
14
|
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
15
15
|
if (onClick !== undefined) {
|
|
16
16
|
e.preventDefault();
|
|
@@ -18,13 +18,10 @@ const Button = ({ children, type, disabled, icon, buttonStyle, onClick, classNam
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
let iconWrapper;
|
|
22
|
-
if (icon) {
|
|
23
|
-
iconWrapper = <Icon name={icon} />;
|
|
24
|
-
}
|
|
21
|
+
let iconWrapper = icon ? <Icon name={icon} /> : loader ? <Loader name={loader} /> : undefined;
|
|
25
22
|
|
|
26
23
|
const buttonContent = (
|
|
27
|
-
<S.Label icon={icon}>
|
|
24
|
+
<S.Label icon={icon || loader}>
|
|
28
25
|
{iconWrapper}
|
|
29
26
|
<span>{children}</span>
|
|
30
27
|
</S.Label>
|
|
@@ -79,6 +76,7 @@ export interface IButtonProps {
|
|
|
79
76
|
type: "button" | "submit" | "reset" | undefined;
|
|
80
77
|
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
81
78
|
icon?: string;
|
|
79
|
+
loader?: string;
|
|
82
80
|
disabled?: boolean;
|
|
83
81
|
buttonStyle?: "solid" | "text" | "line" | "lineInverse" | undefined; // TODO: investigar si podemos hacer esto más elegante y aprovechar el enum buttonStyles
|
|
84
82
|
className?: string;
|
|
@@ -167,7 +167,7 @@ const ManualPanel = (props: IProps) => {
|
|
|
167
167
|
const source = state.sourceTitles.find((el: IDataSource) => el.id === item.structuredData);
|
|
168
168
|
const disabled = (hasMaxItems && !isChecked) || !!hasVersionInPageLanguage;
|
|
169
169
|
return (
|
|
170
|
-
<Item
|
|
170
|
+
<>{source && <Item
|
|
171
171
|
key={`${item.content.title}-${item.id}`}
|
|
172
172
|
isChecked={isChecked || !!isSelectedOtherLanguage}
|
|
173
173
|
handleOnClick={handleOnClick}
|
|
@@ -175,7 +175,7 @@ const ManualPanel = (props: IProps) => {
|
|
|
175
175
|
source={source}
|
|
176
176
|
disabled={disabled}
|
|
177
177
|
tooltip={hasVersionInPageLanguage ? "Content has version in page language" : ""}
|
|
178
|
-
/>
|
|
178
|
+
/>}</>
|
|
179
179
|
);
|
|
180
180
|
})}
|
|
181
181
|
</S.ItemList>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { connect } from "react-redux";
|
|
3
|
+
import { IRootState } from "@ax/types";
|
|
4
|
+
import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
5
|
+
|
|
6
|
+
import * as S from "./style";
|
|
7
|
+
|
|
8
|
+
const SummaryButton = (props: ISummaryButtonProps): JSX.Element => {
|
|
9
|
+
const { autoSummary, getPageSummary } = props;
|
|
10
|
+
|
|
11
|
+
const initialState = {
|
|
12
|
+
isLoading: false,
|
|
13
|
+
isClicked: false,
|
|
14
|
+
error: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const [state, setState] = useState(initialState);
|
|
18
|
+
|
|
19
|
+
const handleClick = async () => {
|
|
20
|
+
setState((state) => ({ ...state, isLoading: true, error: false }));
|
|
21
|
+
const generated = await getPageSummary();
|
|
22
|
+
setState((state) => ({ ...state, isLoading: false }));
|
|
23
|
+
if (generated) {
|
|
24
|
+
setState((state) => ({ ...state, isClicked: true }));
|
|
25
|
+
} else {
|
|
26
|
+
setState((state) => ({ ...state, error: true }));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const buttonText = state.isLoading
|
|
31
|
+
? "Processing..."
|
|
32
|
+
: state.isClicked
|
|
33
|
+
? "Regenerate SEO data with AI"
|
|
34
|
+
: "Generate SEO data with AI";
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
{autoSummary ? (
|
|
39
|
+
<S.Wrapper data-testid="summary-button-wrapper">
|
|
40
|
+
<S.Text>
|
|
41
|
+
You can automatically generate <strong>SEO meta descriptions</strong> and <strong>keywords</strong> quickly
|
|
42
|
+
with <strong>AI</strong>.
|
|
43
|
+
</S.Text>
|
|
44
|
+
<S.ButtonWrapper>
|
|
45
|
+
<S.StyledButton
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={handleClick}
|
|
48
|
+
icon={!state.isLoading ? "Ia" : undefined}
|
|
49
|
+
loader={state.isLoading ? "circle" : undefined}
|
|
50
|
+
>
|
|
51
|
+
{buttonText}
|
|
52
|
+
</S.StyledButton>
|
|
53
|
+
</S.ButtonWrapper>
|
|
54
|
+
{state.error && <S.ErrorText>We're having problems. Please try again in a few minutes.</S.ErrorText>}
|
|
55
|
+
</S.Wrapper>
|
|
56
|
+
) : (
|
|
57
|
+
<></>
|
|
58
|
+
)}
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export interface ISummaryButtonProps {
|
|
64
|
+
autoSummary: boolean;
|
|
65
|
+
getPageSummary: () => Promise<boolean>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const mapDispatchToProps = {
|
|
69
|
+
getPageSummary: pageEditorActions.getPageSummary,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const mapStateToProps = (state: IRootState) => ({
|
|
73
|
+
autoSummary: state.app.globalSettings.autoSummary,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export default connect(mapStateToProps, mapDispatchToProps)(SummaryButton);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
import { Button } from "@ax/components";
|
|
4
|
+
|
|
5
|
+
const Wrapper = styled.div`
|
|
6
|
+
background-color: ${(p) => p.theme.color.uiBackground03};
|
|
7
|
+
padding: ${(p) => p.theme.spacing.s};
|
|
8
|
+
border-radius: ${(p) => p.theme.radii.s};
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
const Text = styled.div`
|
|
12
|
+
${(p) => p.theme.textStyle.uiXS};
|
|
13
|
+
color: ${(p) => p.theme.color.textMediumEmphasis};
|
|
14
|
+
margin-bottom: ${(p) => p.theme.spacing.xs};
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const ButtonWrapper = styled.div`
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const StyledButton = styled((props) => <Button {...props} />)`
|
|
23
|
+
width: 100%;
|
|
24
|
+
max-width: 264px;
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const ErrorText = styled.div`
|
|
28
|
+
${(p) => p.theme.textStyle.uiXS};
|
|
29
|
+
color: ${(p) => p.theme.color.error};
|
|
30
|
+
margin-top: ${(p) => p.theme.spacing.xs};
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export { Wrapper, Text, ButtonWrapper, StyledButton, ErrorText };
|
|
@@ -3,7 +3,7 @@ import { Tag } from "@ax/components";
|
|
|
3
3
|
|
|
4
4
|
import * as S from "./style";
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const TagsField = (props: IProps): JSX.Element => {
|
|
7
7
|
const { value, onChange } = props;
|
|
8
8
|
const valueArray = value && Array.isArray(value) ? value : [];
|
|
9
9
|
const [inputValue, setInputValue] = useState("");
|
|
@@ -61,4 +61,4 @@ interface IProps {
|
|
|
61
61
|
onChange: (value: string[]) => void;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export default
|
|
64
|
+
export default TagsField;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { connect } from "react-redux";
|
|
3
|
+
import { Icon, Modal } from "@ax/components";
|
|
4
|
+
import { useModal } from "@ax/hooks";
|
|
5
|
+
import { IRootState } from "@ax/types";
|
|
6
|
+
import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
7
|
+
|
|
8
|
+
import * as S from "./style";
|
|
9
|
+
|
|
10
|
+
const TranslateButton = (props: ITranslateButtonProps): JSX.Element => {
|
|
11
|
+
const { lang, autoTranslation, getPageTranslation, content, isIATranslated, setIsTranslated } = props;
|
|
12
|
+
|
|
13
|
+
const { canBeTranslated } = content;
|
|
14
|
+
|
|
15
|
+
const initialState = {
|
|
16
|
+
isLoading: false,
|
|
17
|
+
error: false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const [state, setState] = useState(initialState);
|
|
21
|
+
const { isOpen, toggleModal } = useModal();
|
|
22
|
+
|
|
23
|
+
const handleClick = async () => {
|
|
24
|
+
toggleModal();
|
|
25
|
+
setState((state) => ({ ...state, isLoading: true, error: false }));
|
|
26
|
+
const generated = await getPageTranslation(lang);
|
|
27
|
+
setState((state) => ({ ...state, isLoading: false }));
|
|
28
|
+
if (!generated) {
|
|
29
|
+
setState((state) => ({ ...state, error: true }));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleClose = () => setIsTranslated(false);
|
|
34
|
+
|
|
35
|
+
const buttonText = state.isLoading ? "Processing..." : "Translate page with AI";
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
{isIATranslated && !canBeTranslated && (
|
|
40
|
+
<S.Wrapper data-testid="translated-notification-wrapper">
|
|
41
|
+
<S.NotificationContent>
|
|
42
|
+
<S.Text>This page is translated with Artificial Intelligence.</S.Text>
|
|
43
|
+
<S.IconWrapper onClick={handleClose}>
|
|
44
|
+
<Icon name="close" size="16" />
|
|
45
|
+
</S.IconWrapper>
|
|
46
|
+
</S.NotificationContent>
|
|
47
|
+
</S.Wrapper>
|
|
48
|
+
)}
|
|
49
|
+
{autoTranslation && canBeTranslated ? (
|
|
50
|
+
<>
|
|
51
|
+
<S.Wrapper data-testid="translate-button-wrapper">
|
|
52
|
+
<S.Text>
|
|
53
|
+
Using <strong>Artificial Intelligence</strong>, you can translate the page automatically.
|
|
54
|
+
</S.Text>
|
|
55
|
+
<S.ButtonWrapper>
|
|
56
|
+
<S.StyledButton
|
|
57
|
+
type="button"
|
|
58
|
+
onClick={toggleModal}
|
|
59
|
+
icon={!state.isLoading ? "Ia" : undefined}
|
|
60
|
+
loader={state.isLoading ? "circle" : undefined}
|
|
61
|
+
>
|
|
62
|
+
{buttonText}
|
|
63
|
+
</S.StyledButton>
|
|
64
|
+
</S.ButtonWrapper>
|
|
65
|
+
{state.error && <S.ErrorText>We're having problems. Please try again in a few minutes.</S.ErrorText>}
|
|
66
|
+
</S.Wrapper>
|
|
67
|
+
<Modal
|
|
68
|
+
isOpen={isOpen}
|
|
69
|
+
hide={toggleModal}
|
|
70
|
+
size="S"
|
|
71
|
+
title="Translate page with AI"
|
|
72
|
+
mainAction={{ title: "Translate with AI", icon: "Ia", onClick: handleClick }}
|
|
73
|
+
secondaryAction={{ title: "Cancel", onClick: toggleModal }}
|
|
74
|
+
>
|
|
75
|
+
<S.ModalContent>
|
|
76
|
+
The automatic translation <strong>will replace all the text on the page</strong>. Before proceeding, make
|
|
77
|
+
sure that the existing text is not critical.
|
|
78
|
+
</S.ModalContent>
|
|
79
|
+
</Modal>
|
|
80
|
+
</>
|
|
81
|
+
) : (
|
|
82
|
+
<></>
|
|
83
|
+
)}
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export interface ITranslateButtonProps {
|
|
89
|
+
lang: number;
|
|
90
|
+
autoTranslation: boolean;
|
|
91
|
+
content: any;
|
|
92
|
+
isIATranslated: boolean;
|
|
93
|
+
getPageTranslation: (langID: number) => Promise<boolean>;
|
|
94
|
+
setIsTranslated: (isTranslated: boolean) => Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const mapDispatchToProps = {
|
|
98
|
+
getPageTranslation: pageEditorActions.getPageTranslation,
|
|
99
|
+
setIsTranslated: pageEditorActions.setIsTranslated,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const mapStateToProps = (state: IRootState) => ({
|
|
103
|
+
autoTranslation: state.app.globalSettings.autoTranslation,
|
|
104
|
+
content: state.pageEditor.editorContent,
|
|
105
|
+
isIATranslated: state.pageEditor.isIATranslated
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export default connect(mapStateToProps, mapDispatchToProps)(TranslateButton);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
import { Button } from "@ax/components";
|
|
4
|
+
|
|
5
|
+
const Wrapper = styled.div`
|
|
6
|
+
background-color: ${(p) => p.theme.color.uiBackground03};
|
|
7
|
+
padding: ${(p) => p.theme.spacing.s};
|
|
8
|
+
border-radius: ${(p) => p.theme.radii.s};
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
const Text = styled.div`
|
|
12
|
+
${(p) => p.theme.textStyle.uiXS};
|
|
13
|
+
color: ${(p) => p.theme.color.textMediumEmphasis};
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const ButtonWrapper = styled.div`
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
margin-top: ${(p) => p.theme.spacing.xs};
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const StyledButton = styled((props) => <Button {...props} />)`
|
|
23
|
+
width: 100%;
|
|
24
|
+
max-width: 264px;
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const ErrorText = styled.div`
|
|
28
|
+
${(p) => p.theme.textStyle.uiXS};
|
|
29
|
+
color: ${(p) => p.theme.color.error};
|
|
30
|
+
margin-top: ${(p) => p.theme.spacing.xs};
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const ModalContent = styled.div`
|
|
34
|
+
padding: ${(p) => p.theme.spacing.m};
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const IconWrapper = styled.div`
|
|
38
|
+
width: ${(p) => p.theme.spacing.s};
|
|
39
|
+
height: ${(p) => p.theme.spacing.s};
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
margin-left: auto;
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const NotificationContent = styled.div`
|
|
45
|
+
display: flex;
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
export { Wrapper, Text, ButtonWrapper, StyledButton, ErrorText, ModalContent, NotificationContent, IconWrapper };
|
|
@@ -26,11 +26,13 @@ import ReferenceField from "./ReferenceField";
|
|
|
26
26
|
import RichText from "./RichText";
|
|
27
27
|
import Select from "./Select";
|
|
28
28
|
import SliderField from "./SliderField";
|
|
29
|
-
import
|
|
29
|
+
import SummaryButton from "./SummaryButton";
|
|
30
|
+
import TagsField from "./TagsField";
|
|
30
31
|
import TextArea from "./TextArea";
|
|
31
32
|
import TextField from "./TextField";
|
|
32
33
|
import TimeField from "./TimeField";
|
|
33
34
|
import ToggleField from "./ToggleField";
|
|
35
|
+
import TranslateButton from "./TranslateButton";
|
|
34
36
|
import UniqueCheck from "./UniqueCheck";
|
|
35
37
|
import UrlField from "./UrlField";
|
|
36
38
|
import VisualOption from "./VisualOption";
|
|
@@ -66,11 +68,13 @@ export {
|
|
|
66
68
|
RichText,
|
|
67
69
|
Select,
|
|
68
70
|
SliderField,
|
|
69
|
-
|
|
71
|
+
SummaryButton,
|
|
72
|
+
TagsField,
|
|
70
73
|
TextArea,
|
|
71
74
|
TextField,
|
|
72
75
|
TimeField,
|
|
73
76
|
ToggleField,
|
|
77
|
+
TranslateButton,
|
|
74
78
|
UniqueCheck,
|
|
75
79
|
UrlField,
|
|
76
80
|
VisualOption,
|
|
@@ -181,7 +181,7 @@ const GalleryDetailPanel = (props: IProps) => {
|
|
|
181
181
|
fieldType="TextArea"
|
|
182
182
|
onChange={handleDescription}
|
|
183
183
|
/>
|
|
184
|
-
<FieldsBehavior title="Tags" value={imageForm.tags} fieldType="
|
|
184
|
+
<FieldsBehavior title="Tags" value={imageForm.tags} fieldType="TagsField" onChange={handleTags} />
|
|
185
185
|
</S.FormWrapper>
|
|
186
186
|
</S.PanelForm>
|
|
187
187
|
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
const SvgIa = (props) => (
|
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} fill="none" {...props}>
|
|
4
|
+
<path
|
|
5
|
+
fill="#5057FF"
|
|
6
|
+
fillRule="evenodd"
|
|
7
|
+
d="M11.063 3.063 12 1l.938 2.063L15 4l-2.063.938L12 7l-.938-2.063L9 4l2.063-.938Zm-.22 10.093L8.5 8l-2.344 5.156L1 15.5l5.156 2.344L8.5 23l2.344-5.156L16 15.5l-5.156-2.344ZM18.5 5l-1.406 3.094L14 9.5l3.094 1.406L18.5 14l1.406-3.094L23 9.5l-3.094-1.406L18.5 5Z"
|
|
8
|
+
clipRule="evenodd"
|
|
9
|
+
/>
|
|
10
|
+
</svg>
|
|
11
|
+
);
|
|
12
|
+
export default SvgIa;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0625 3.0625L12 1L12.9375 3.0625L15 4L12.9375 4.9375L12 7L11.0625 4.9375L9 4L11.0625 3.0625ZM10.8438 13.1562L8.5 8L6.15625 13.1562L1 15.5L6.15625 17.8438L8.5 23L10.8438 17.8438L16 15.5L10.8438 13.1562ZM18.5 5L17.0938 8.09375L14 9.5L17.0938 10.9062L18.5 14L19.9062 10.9062L23 9.5L19.9062 8.09375L18.5 5Z" fill="#5057FF"/>
|
|
3
|
+
</svg>
|
|
@@ -19,9 +19,10 @@ const ActionMenuBtn = (props: any) => {
|
|
|
19
19
|
menu &&
|
|
20
20
|
menu.button && (
|
|
21
21
|
<S.ButtonWrapper>
|
|
22
|
-
<Button type="button" buttonStyle="line" onClick={menu.button.action}>
|
|
22
|
+
<Button type="button" buttonStyle="line" onClick={menu.button.action} disabled={menu.button.disabled}>
|
|
23
23
|
{menu.button.label}
|
|
24
24
|
</Button>
|
|
25
|
+
{menu.button.helpText && <S.HelpText>{menu.button.helpText}</S.HelpText>}
|
|
25
26
|
</S.ButtonWrapper>
|
|
26
27
|
)
|
|
27
28
|
);
|
|
@@ -89,6 +89,14 @@ const ButtonWrapper = styled.div`
|
|
|
89
89
|
}
|
|
90
90
|
`;
|
|
91
91
|
|
|
92
|
+
const HelpText = styled.div`
|
|
93
|
+
${(p) => p.theme.textStyle.uiXS};
|
|
94
|
+
color: ${(p) => p.theme.color.textMediumEmphasis};
|
|
95
|
+
white-space: normal;
|
|
96
|
+
width: 160px;
|
|
97
|
+
margin-top: ${(p) => p.theme.spacing.xs};
|
|
98
|
+
`;
|
|
99
|
+
|
|
92
100
|
const LanguageWrapper = styled.div`
|
|
93
101
|
position: relative;
|
|
94
102
|
display: flex;
|
|
@@ -206,4 +214,5 @@ export {
|
|
|
206
214
|
WrapperTitle,
|
|
207
215
|
TabsContent,
|
|
208
216
|
StatusBtn,
|
|
217
|
+
HelpText,
|
|
209
218
|
};
|
|
@@ -11,7 +11,7 @@ const Modal = (props: IModalProps): JSX.Element | null => {
|
|
|
11
11
|
const titleContent = title ? <S.Title>{title}</S.Title> : "";
|
|
12
12
|
|
|
13
13
|
const mainActionButton = mainAction ? (
|
|
14
|
-
<Button type="button" onClick={mainAction.onClick} disabled={mainAction.disabled}>
|
|
14
|
+
<Button type="button" onClick={mainAction.onClick} disabled={mainAction.disabled} icon={mainAction.icon}>
|
|
15
15
|
{mainAction.title}
|
|
16
16
|
</Button>
|
|
17
17
|
) : (
|
|
@@ -73,6 +73,7 @@ interface IAction {
|
|
|
73
73
|
title: string;
|
|
74
74
|
onClick: () => void;
|
|
75
75
|
disabled?: boolean;
|
|
76
|
+
icon?: string;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export default memo(Modal);
|
package/src/components/index.tsx
CHANGED
|
@@ -21,10 +21,12 @@ import {
|
|
|
21
21
|
ReferenceField,
|
|
22
22
|
RichText,
|
|
23
23
|
Select,
|
|
24
|
+
SummaryButton,
|
|
24
25
|
TextArea,
|
|
25
26
|
TextField,
|
|
26
27
|
TimeField,
|
|
27
28
|
ToggleField,
|
|
29
|
+
TranslateButton,
|
|
28
30
|
UniqueCheck,
|
|
29
31
|
UrlField,
|
|
30
32
|
VisualOption,
|
|
@@ -117,10 +119,12 @@ export {
|
|
|
117
119
|
ReferenceField,
|
|
118
120
|
RichText,
|
|
119
121
|
Select,
|
|
122
|
+
SummaryButton,
|
|
120
123
|
TextArea,
|
|
121
124
|
TextField,
|
|
122
125
|
TimeField,
|
|
123
126
|
ToggleField,
|
|
127
|
+
TranslateButton,
|
|
124
128
|
UniqueCheck,
|
|
125
129
|
UrlField,
|
|
126
130
|
VisualOption,
|
|
@@ -48,6 +48,8 @@ export interface IGlobalSettings {
|
|
|
48
48
|
welcomeText1: string;
|
|
49
49
|
welcomeText2: string;
|
|
50
50
|
skipReviewOnPublish?: boolean;
|
|
51
|
+
autoSummary: boolean;
|
|
52
|
+
autoTranslation: boolean;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export const initialState = {
|
|
@@ -76,6 +78,8 @@ export const initialState = {
|
|
|
76
78
|
siteLogoMini: "",
|
|
77
79
|
welcomeText1: "",
|
|
78
80
|
welcomeText2: "",
|
|
81
|
+
autoSummary: false,
|
|
82
|
+
autoTranslation: false,
|
|
79
83
|
},
|
|
80
84
|
sessionStartedAt: null,
|
|
81
85
|
};
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
SET_USER_EDITING,
|
|
59
59
|
SET_LAST_ELEMENT_ADDED_ID,
|
|
60
60
|
SET_COPY_MODULE,
|
|
61
|
+
SET_IS_IA_TRANSLATED,
|
|
61
62
|
} from "./constants";
|
|
62
63
|
|
|
63
64
|
import {
|
|
@@ -93,6 +94,7 @@ import {
|
|
|
93
94
|
pageStatus,
|
|
94
95
|
ISetLastElementAddedId,
|
|
95
96
|
ISetCopyModule,
|
|
97
|
+
ISetIsIATranslated,
|
|
96
98
|
} from "./interfaces";
|
|
97
99
|
|
|
98
100
|
const { setIsLoading, setIsSaving, handleError } = appActions;
|
|
@@ -190,6 +192,10 @@ function setCopyModule(moduleCopy: Record<string, unknown> | null): ISetCopyModu
|
|
|
190
192
|
return { type: SET_COPY_MODULE, payload: { moduleCopy } };
|
|
191
193
|
}
|
|
192
194
|
|
|
195
|
+
function setIsIATranslated(isIATranslated: boolean): ISetIsIATranslated {
|
|
196
|
+
return { type: SET_IS_IA_TRANSLATED, payload: { isIATranslated } };
|
|
197
|
+
}
|
|
198
|
+
|
|
193
199
|
function setTranslatedParent(): (dispatch: Dispatch, getState: any) => void {
|
|
194
200
|
return async (dispatch, getState) => {
|
|
195
201
|
try {
|
|
@@ -325,7 +331,11 @@ function getPage(pageID?: number, global?: boolean): (dispatch: Dispatch, getSta
|
|
|
325
331
|
|
|
326
332
|
const { liveStatus, component } = getDefaultSchema(baseSchema);
|
|
327
333
|
|
|
328
|
-
if (isNewTranslation)
|
|
334
|
+
if (isNewTranslation) {
|
|
335
|
+
page["liveStatus"] = liveStatus;
|
|
336
|
+
page["canBeTranslated"] = true;
|
|
337
|
+
page["originalLanguage"] = page.language;
|
|
338
|
+
}
|
|
329
339
|
|
|
330
340
|
if (global) page["component"] = component;
|
|
331
341
|
|
|
@@ -1108,6 +1118,7 @@ function resetPageEditor(): (dispatch: Dispatch) => Promise<void> {
|
|
|
1108
1118
|
dispatch(setSitePageID(null));
|
|
1109
1119
|
dispatch(setUserEditing(null));
|
|
1110
1120
|
dispatch(setLastElementAddedId(null));
|
|
1121
|
+
dispatch(setIsIATranslated(false));
|
|
1111
1122
|
localStorage.removeItem("selectedID");
|
|
1112
1123
|
} catch (e) {
|
|
1113
1124
|
console.log("Error", e); //TODO
|
|
@@ -1343,6 +1354,70 @@ function restorePageNavigation(key: string): (dispatch: Dispatch, getState: any)
|
|
|
1343
1354
|
};
|
|
1344
1355
|
}
|
|
1345
1356
|
|
|
1357
|
+
function getPageSummary(): (dispatch: Dispatch, getState: any) => Promise<boolean> {
|
|
1358
|
+
return async (dispatch, getState) => {
|
|
1359
|
+
try {
|
|
1360
|
+
const {
|
|
1361
|
+
pageEditor: { editorContent },
|
|
1362
|
+
} = getState();
|
|
1363
|
+
|
|
1364
|
+
const responseActions = {
|
|
1365
|
+
handleSuccess: (data: { summary: string; keywords: string[] }) => {
|
|
1366
|
+
const content = deepClone(editorContent);
|
|
1367
|
+
content["metaDescription"] = data.summary;
|
|
1368
|
+
content["metaKeywords"] = data.keywords;
|
|
1369
|
+
generatePageContent(content, dispatch, getState);
|
|
1370
|
+
},
|
|
1371
|
+
handleError: () => console.log("Error en GetPageSummary"),
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
const callback = async () => pages.getPageSummary(editorContent);
|
|
1375
|
+
|
|
1376
|
+
return await handleRequest(callback, responseActions, [])(dispatch);
|
|
1377
|
+
} catch (e) {
|
|
1378
|
+
console.log(e);
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function getPageTranslation(langID: number): (dispatch: Dispatch, getState: any) => Promise<boolean> {
|
|
1385
|
+
return async (dispatch, getState) => {
|
|
1386
|
+
try {
|
|
1387
|
+
const {
|
|
1388
|
+
pageEditor: { editorContent },
|
|
1389
|
+
} = getState();
|
|
1390
|
+
|
|
1391
|
+
const responseActions = {
|
|
1392
|
+
handleSuccess: (data: IPage) => {
|
|
1393
|
+
data["canBeTranslated"] = false;
|
|
1394
|
+
generatePageContent(data, dispatch, getState);
|
|
1395
|
+
dispatch(setCurrentPageName(data.title));
|
|
1396
|
+
dispatch(setIsIATranslated(true));
|
|
1397
|
+
},
|
|
1398
|
+
handleError: () => console.log("Error en GetPageTranslation"),
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
const callback = async () => pages.getPageTranslation(editorContent, langID);
|
|
1402
|
+
|
|
1403
|
+
return await handleRequest(callback, responseActions, [])(dispatch);
|
|
1404
|
+
} catch (e) {
|
|
1405
|
+
console.log(e);
|
|
1406
|
+
return false;
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function setIsTranslated(isTranslated: boolean): (dispatch: Dispatch) => Promise<void> {
|
|
1412
|
+
return async (dispatch) => {
|
|
1413
|
+
try {
|
|
1414
|
+
dispatch(setIsIATranslated(isTranslated));
|
|
1415
|
+
} catch (e) {
|
|
1416
|
+
console.log("Error", e);
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1346
1421
|
export {
|
|
1347
1422
|
setEditorContent,
|
|
1348
1423
|
setTemplate,
|
|
@@ -1391,4 +1466,7 @@ export {
|
|
|
1391
1466
|
pasteModule,
|
|
1392
1467
|
removeNavigationFromPage,
|
|
1393
1468
|
restorePageNavigation,
|
|
1469
|
+
getPageSummary,
|
|
1470
|
+
getPageTranslation,
|
|
1471
|
+
setIsTranslated
|
|
1394
1472
|
};
|
|
@@ -20,6 +20,7 @@ export const SET_SITE_PAGE_ID = `${NAME}/SET_SITE_PAGE_ID`;
|
|
|
20
20
|
export const SET_USER_EDITING = `${NAME}/SET_USER_EDITING`;
|
|
21
21
|
export const SET_LAST_ELEMENT_ADDED_ID = `${NAME}/SET_LAST_ELEMENT_ADDED_ID`;
|
|
22
22
|
export const SET_COPY_MODULE = `${NAME}/SET_COPY_MODULE`;
|
|
23
|
+
export const SET_IS_IA_TRANSLATED = `${NAME}/SET_IS_IA_TRANSLATED`;
|
|
23
24
|
|
|
24
25
|
export const INITIAL_TEMPLATE = "BasicTemplate";
|
|
25
26
|
export const MULTIMEDIA_COMPONENTS = ["LinkableImage", "Video"];
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
SET_USER_EDITING,
|
|
20
20
|
SET_LAST_ELEMENT_ADDED_ID,
|
|
21
21
|
SET_COPY_MODULE,
|
|
22
|
+
SET_IS_IA_TRANSLATED,
|
|
22
23
|
} from "./constants";
|
|
23
24
|
import { IBreadcrumbItem, ISchema, IPage, IErrorItem, IUserEditing } from "@ax/types";
|
|
24
25
|
|
|
@@ -122,6 +123,11 @@ export interface ISetCopyModule {
|
|
|
122
123
|
payload: { moduleCopy: Record<string, unknown> | null };
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
export interface ISetIsIATranslated {
|
|
127
|
+
type: typeof SET_IS_IA_TRANSLATED;
|
|
128
|
+
payload: { isIATranslated: boolean };
|
|
129
|
+
}
|
|
130
|
+
|
|
125
131
|
export interface IFieldProps {
|
|
126
132
|
id: number;
|
|
127
133
|
key: string;
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
SET_USER_EDITING,
|
|
23
23
|
SET_LAST_ELEMENT_ADDED_ID,
|
|
24
24
|
SET_COPY_MODULE,
|
|
25
|
+
SET_IS_IA_TRANSLATED,
|
|
25
26
|
} from "./constants";
|
|
26
27
|
|
|
27
28
|
import { PageEditorActionsCreators } from "./interfaces";
|
|
@@ -47,6 +48,7 @@ export interface IPageEditorState {
|
|
|
47
48
|
userEditing: IUserEditing | null;
|
|
48
49
|
lastElementAddedId: null | number;
|
|
49
50
|
moduleCopy: { date: string; element: Record<string, unknown> } | null;
|
|
51
|
+
isIATranslated: boolean;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export const initialState = {
|
|
@@ -70,6 +72,7 @@ export const initialState = {
|
|
|
70
72
|
userEditing: null,
|
|
71
73
|
lastElementAddedId: null,
|
|
72
74
|
moduleCopy: null,
|
|
75
|
+
isIATranslated: false,
|
|
73
76
|
};
|
|
74
77
|
|
|
75
78
|
export function reducer(state = initialState, action: PageEditorActionsCreators): IPageEditorState {
|
|
@@ -95,6 +98,7 @@ export function reducer(state = initialState, action: PageEditorActionsCreators)
|
|
|
95
98
|
case SET_USER_EDITING:
|
|
96
99
|
case SET_LAST_ELEMENT_ADDED_ID:
|
|
97
100
|
case SET_COPY_MODULE:
|
|
101
|
+
case SET_IS_IA_TRANSLATED:
|
|
98
102
|
return { ...state, ...action.payload };
|
|
99
103
|
default:
|
|
100
104
|
return state;
|
|
@@ -67,6 +67,8 @@ const PageEditor = (props: IProps) => {
|
|
|
67
67
|
const isTranslated = pageLanguages.length > 1;
|
|
68
68
|
const structuredData = editorContent ? editorContent.structuredData : "";
|
|
69
69
|
const isEditLive = isPublished && hasDraft;
|
|
70
|
+
const canBeUnpublished = editorContent && editorContent.canBeUnpublished;
|
|
71
|
+
const deleteHelpText = !canBeUnpublished ? "This is the canonical site of the page. You cannot unpublish it." : null;
|
|
70
72
|
|
|
71
73
|
const errorNotificationText =
|
|
72
74
|
"There are some errors on the page so you can not publish yet. Please review them in the error panel.";
|
|
@@ -76,6 +78,7 @@ const PageEditor = (props: IProps) => {
|
|
|
76
78
|
const { pageID, getPage, setTab, sendPagePing } = props;
|
|
77
79
|
const defaultTab = "content";
|
|
78
80
|
const handleGetPage = async () => await getPage(pageID);
|
|
81
|
+
|
|
79
82
|
setTab(defaultTab);
|
|
80
83
|
handleGetPage();
|
|
81
84
|
|
|
@@ -245,6 +248,8 @@ const PageEditor = (props: IProps) => {
|
|
|
245
248
|
: {
|
|
246
249
|
label: "Unpublish",
|
|
247
250
|
action: hasDraft ? toggleUnpublishModal : unpublishPage,
|
|
251
|
+
disabled: !canBeUnpublished,
|
|
252
|
+
helpText: deleteHelpText,
|
|
248
253
|
};
|
|
249
254
|
case pageStatus.MODIFIED:
|
|
250
255
|
return {
|
|
@@ -7,6 +7,11 @@ export default {
|
|
|
7
7
|
{
|
|
8
8
|
title: "content",
|
|
9
9
|
fields: [
|
|
10
|
+
{
|
|
11
|
+
title: "",
|
|
12
|
+
type: "TranslateButton",
|
|
13
|
+
key: "translate",
|
|
14
|
+
},
|
|
10
15
|
{
|
|
11
16
|
title: "Title",
|
|
12
17
|
type: "TextField",
|
|
@@ -52,6 +57,11 @@ export default {
|
|
|
52
57
|
key: "seoData",
|
|
53
58
|
collapsed: true,
|
|
54
59
|
fields: [
|
|
60
|
+
{
|
|
61
|
+
title: "",
|
|
62
|
+
type: "SummaryButton",
|
|
63
|
+
key: "summary",
|
|
64
|
+
},
|
|
55
65
|
{
|
|
56
66
|
title: "Meta title",
|
|
57
67
|
type: "TextField",
|
|
@@ -62,6 +72,11 @@ export default {
|
|
|
62
72
|
type: "TextArea",
|
|
63
73
|
key: "metaDescription",
|
|
64
74
|
},
|
|
75
|
+
{
|
|
76
|
+
title: "Keywords",
|
|
77
|
+
type: "TagsField",
|
|
78
|
+
key: "metaKeywords",
|
|
79
|
+
},
|
|
65
80
|
{
|
|
66
81
|
title: "Meta robots index",
|
|
67
82
|
type: "RadioGroup",
|
|
@@ -9,6 +9,11 @@ export default {
|
|
|
9
9
|
{
|
|
10
10
|
title: "content",
|
|
11
11
|
fields: [
|
|
12
|
+
{
|
|
13
|
+
title: "",
|
|
14
|
+
type: "TranslateButton",
|
|
15
|
+
key: "translate",
|
|
16
|
+
},
|
|
12
17
|
{
|
|
13
18
|
title: "Title",
|
|
14
19
|
type: "TextField",
|
|
@@ -111,6 +116,11 @@ export default {
|
|
|
111
116
|
key: "seoData",
|
|
112
117
|
collapsed: true,
|
|
113
118
|
fields: [
|
|
119
|
+
{
|
|
120
|
+
title: "",
|
|
121
|
+
type: "SummaryButton",
|
|
122
|
+
key: "summary",
|
|
123
|
+
},
|
|
114
124
|
{
|
|
115
125
|
title: "Meta title",
|
|
116
126
|
type: "TextField",
|
|
@@ -121,6 +131,11 @@ export default {
|
|
|
121
131
|
type: "TextArea",
|
|
122
132
|
key: "metaDescription",
|
|
123
133
|
},
|
|
134
|
+
{
|
|
135
|
+
title: "Keywords",
|
|
136
|
+
type: "TagsField",
|
|
137
|
+
key: "metaKeywords",
|
|
138
|
+
},
|
|
124
139
|
{
|
|
125
140
|
title: "Canonical URL",
|
|
126
141
|
type: "TextField",
|
package/src/types/index.tsx
CHANGED
|
File without changes
|