@griddo/ax 11.4.14 → 11.4.16
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 +7 -3
- package/src/api/utils.tsx +15 -0
- package/src/components/Fields/HeadingField/index.tsx +10 -4
- package/src/components/Fields/Wysiwyg/config.tsx +41 -3
- package/src/components/Fields/Wysiwyg/helpers.tsx +70 -1
- package/src/components/Fields/Wysiwyg/index.tsx +15 -6
- package/src/components/Fields/Wysiwyg/languages.js +136 -0
- package/src/components/Fields/Wysiwyg/style.tsx +52 -9
- package/src/components/Icon/components/Translate.js +10 -0
- package/src/components/Icon/svgs/translate.svg +3 -0
- package/src/components/MainWrapper/index.tsx +10 -0
- package/src/global.d.ts +1 -0
- package/src/helpers/requests.tsx +2 -12
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/network.tsx +31 -0
- package/src/modules/Content/OptionTable/index.tsx +14 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griddo/ax",
|
|
3
3
|
"description": "Griddo Author Experience",
|
|
4
|
-
"version": "11.4.
|
|
4
|
+
"version": "11.4.16",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
|
|
7
7
|
"Diego M. Béjar <diego.bejar@secuoyas.com>",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"react-dom": "18.2.0",
|
|
119
119
|
"react-draft-wysiwyg": "1.15.0",
|
|
120
120
|
"react-error-boundary": "4.0.13",
|
|
121
|
-
"react-froala-wysiwyg": "
|
|
121
|
+
"react-froala-wysiwyg": "4.0.4",
|
|
122
122
|
"react-redux": "7.2.9",
|
|
123
123
|
"react-refresh": "0.14.2",
|
|
124
124
|
"react-router-dom": "5.1.2",
|
|
@@ -152,6 +152,10 @@
|
|
|
152
152
|
},
|
|
153
153
|
"devDependencies": {
|
|
154
154
|
"@babel/core": "7.26.10",
|
|
155
|
+
"@babel/helper-environment-visitor": "7.24.7",
|
|
156
|
+
"@babel/helper-function-name": "7.24.7",
|
|
157
|
+
"@babel/helper-hoist-variables": "7.24.7",
|
|
158
|
+
"@babel/helper-split-export-declaration": "7.24.7",
|
|
155
159
|
"@babel/plugin-transform-optional-chaining": "7.25.9",
|
|
156
160
|
"@babel/preset-env": "7.26.9",
|
|
157
161
|
"@babel/preset-react": "7.26.3",
|
|
@@ -220,5 +224,5 @@
|
|
|
220
224
|
"publishConfig": {
|
|
221
225
|
"access": "public"
|
|
222
226
|
},
|
|
223
|
-
"gitHead": "
|
|
227
|
+
"gitHead": "c49a8f24585bb180c3230fe22b55a5212a5d8a4c"
|
|
224
228
|
}
|
package/src/api/utils.tsx
CHANGED
|
@@ -108,6 +108,11 @@ export const sendInitialRequest = async (
|
|
|
108
108
|
const response = await axios(config);
|
|
109
109
|
return response;
|
|
110
110
|
} catch (e) {
|
|
111
|
+
if (e.message !== "Network Error") {
|
|
112
|
+
return (
|
|
113
|
+
e.response || { status: e.status, data: { code: e.code || "unknown", message: e.message || "Platform error" } }
|
|
114
|
+
);
|
|
115
|
+
}
|
|
111
116
|
return e.response;
|
|
112
117
|
}
|
|
113
118
|
};
|
|
@@ -146,6 +151,11 @@ export const sendRequest = async (
|
|
|
146
151
|
// return response;
|
|
147
152
|
return await axios(requestConfig);
|
|
148
153
|
} catch (e) {
|
|
154
|
+
if (e.message !== "Network Error") {
|
|
155
|
+
return (
|
|
156
|
+
e.response || { status: e.status, data: { code: e.code || "unknown", message: e.message || "Platform error" } }
|
|
157
|
+
);
|
|
158
|
+
}
|
|
149
159
|
return e.response;
|
|
150
160
|
}
|
|
151
161
|
};
|
|
@@ -174,6 +184,11 @@ export const sendUploadRequest = async (
|
|
|
174
184
|
|
|
175
185
|
return await axios(requestConfig);
|
|
176
186
|
} catch (e) {
|
|
187
|
+
if (e.message !== "Network Error") {
|
|
188
|
+
return (
|
|
189
|
+
e.response || { status: e.status, data: { code: e.code || "unknown", message: e.message || "Platform error" } }
|
|
190
|
+
);
|
|
191
|
+
}
|
|
177
192
|
return e.response;
|
|
178
193
|
}
|
|
179
194
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, { memo } from "react";
|
|
2
|
-
import { IHeadingField } from "@ax/types";
|
|
3
|
-
import { FieldsBehavior, TextField } from "@ax/components";
|
|
2
|
+
import { IHeadingField, ISite } from "@ax/types";
|
|
3
|
+
import { FieldsBehavior, TextField, Wysiwyg } from "@ax/components";
|
|
4
4
|
|
|
5
5
|
import * as S from "./style";
|
|
6
6
|
|
|
7
7
|
const HeadingField = (props: IHeadingFieldProps): JSX.Element => {
|
|
8
|
-
const { value, onChange, options, showAdvanced } = props;
|
|
8
|
+
const { value, onChange, options, showAdvanced, toolbar = false } = props;
|
|
9
9
|
|
|
10
10
|
const getContentValue = () => value?.content || props.default?.content || "";
|
|
11
11
|
const getTagValue = () => value?.tag || props.default?.tag || "";
|
|
@@ -22,7 +22,11 @@ const HeadingField = (props: IHeadingFieldProps): JSX.Element => {
|
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<>
|
|
25
|
-
|
|
25
|
+
{toolbar ? (
|
|
26
|
+
<Wysiwyg {...props} inline={true} value={getContentValue()} onChange={handleChange} />
|
|
27
|
+
) : (
|
|
28
|
+
<TextField {...props} value={getContentValue()} onChange={handleChange} />
|
|
29
|
+
)}
|
|
26
30
|
{showAdvanced && (
|
|
27
31
|
<S.AdvancedWrapper data-testid="text-field-advanced-wrapper">
|
|
28
32
|
<FieldsBehavior fieldType="Select" options={options} value={getTagValue()} onChange={handleSelectChange} />
|
|
@@ -39,6 +43,8 @@ interface IHeadingFieldProps {
|
|
|
39
43
|
options: IOption[];
|
|
40
44
|
showAdvanced: boolean;
|
|
41
45
|
default: IHeadingField;
|
|
46
|
+
site: ISite;
|
|
47
|
+
toolbar?: boolean;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
interface IOption {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import FroalaEditor from "froala-editor";
|
|
2
|
+
import { getLanguageMenuHtml, getRichTextConfig, parseClassNames } from "./helpers";
|
|
3
|
+
import { languages } from "./languages";
|
|
2
4
|
|
|
3
5
|
const API_URL = process.env.GRIDDO_API_URL || process.env.REACT_APP_API_ENDPOINT;
|
|
4
6
|
const richTextConfig = getRichTextConfig();
|
|
@@ -23,6 +25,7 @@ const miscButtons = [
|
|
|
23
25
|
"html",
|
|
24
26
|
"insertTable",
|
|
25
27
|
paragraphStyles ? "paragraphStyle" : undefined,
|
|
28
|
+
"langDropdown",
|
|
26
29
|
];
|
|
27
30
|
|
|
28
31
|
const buttonsFull = {
|
|
@@ -42,9 +45,19 @@ const buttonsFull = {
|
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
const buttons = [
|
|
45
|
-
[
|
|
48
|
+
[
|
|
49
|
+
"bold",
|
|
50
|
+
"italic",
|
|
51
|
+
"formatUL",
|
|
52
|
+
"insertLink",
|
|
53
|
+
"paragraphFormat",
|
|
54
|
+
paragraphStyles ? "paragraphStyle" : undefined,
|
|
55
|
+
"langDropdown",
|
|
56
|
+
],
|
|
46
57
|
];
|
|
47
58
|
|
|
59
|
+
const inlineToolbar = ["langDropdown", "undo", "redo"];
|
|
60
|
+
|
|
48
61
|
const wysiwygConfig = {
|
|
49
62
|
key: process.env.REACT_APP_FROALA_KEY,
|
|
50
63
|
// pastePlain: false,
|
|
@@ -85,4 +98,29 @@ const wysiwygConfig = {
|
|
|
85
98
|
imageUploadRemoteUrls: false,
|
|
86
99
|
};
|
|
87
100
|
|
|
88
|
-
|
|
101
|
+
FroalaEditor.DefineIcon("langIcon", {
|
|
102
|
+
PATH: "m12.87 15.07-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04ZM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12Zm-2.62 7 1.62-4.33L19.12 17h-3.24Z",
|
|
103
|
+
template: "svg",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
FroalaEditor.RegisterCommand("langDropdown", {
|
|
107
|
+
title: "Language",
|
|
108
|
+
type: "dropdown",
|
|
109
|
+
icon: "langIcon",
|
|
110
|
+
undo: true,
|
|
111
|
+
focus: true,
|
|
112
|
+
refreshAfterCallback: true,
|
|
113
|
+
html: function () {
|
|
114
|
+
return getLanguageMenuHtml(richTextConfig?.editorLangs ? richTextConfig.editorLangs : languages);
|
|
115
|
+
},
|
|
116
|
+
callback: function (cmd: any, val: string) {
|
|
117
|
+
const html = this.selection.text();
|
|
118
|
+
if (val !== "") {
|
|
119
|
+
this.html.insert(`<span lang="${val}" translate="no">${html}</span>`, true);
|
|
120
|
+
} else {
|
|
121
|
+
this.html.insert(html, true);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export { buttonsFull, buttons, inlineToolbar, wysiwygConfig };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/* eslint-disable jsx-a11y/anchor-is-valid */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
1
4
|
import { config } from "components";
|
|
2
5
|
|
|
3
6
|
const getRichTextConfig = (): IRichTextConfig | null => {
|
|
@@ -14,6 +17,72 @@ interface IRichTextConfig {
|
|
|
14
17
|
paragraphStyles?: { label: string; className: string }[];
|
|
15
18
|
tableStyles?: { label: string; className: string }[];
|
|
16
19
|
tableCellStyles?: { label: string; className: string }[];
|
|
20
|
+
editorLangs?: { name: string; iso: string }[];
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
const getLanguageMenuHtml = (languages: { name: string; iso: string; featured?: boolean }[]) => {
|
|
24
|
+
const borderStyle = {
|
|
25
|
+
borderBottom: "1px solid #DBDDE9",
|
|
26
|
+
paddingBottom: "4px",
|
|
27
|
+
marginBottom: "4px",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const featuredLanguages = languages
|
|
31
|
+
.filter((lang) => lang.featured === true)
|
|
32
|
+
.map((lang, index, { length }) => (
|
|
33
|
+
<li role="presentation" key={lang.iso} style={index + 1 === length ? borderStyle : {}}>
|
|
34
|
+
<a
|
|
35
|
+
className="fr-command"
|
|
36
|
+
tabIndex={-1}
|
|
37
|
+
role="option"
|
|
38
|
+
data-cmd="langDropdown"
|
|
39
|
+
data-param1={lang.iso}
|
|
40
|
+
title={lang.name}
|
|
41
|
+
aria-selected="false"
|
|
42
|
+
>
|
|
43
|
+
{lang.name}
|
|
44
|
+
</a>
|
|
45
|
+
</li>
|
|
46
|
+
));
|
|
47
|
+
|
|
48
|
+
const otherLanguages = languages
|
|
49
|
+
.filter((lang) => lang.featured !== true)
|
|
50
|
+
.map((lang) => (
|
|
51
|
+
<li role="presentation" key={lang.iso}>
|
|
52
|
+
<a
|
|
53
|
+
className="fr-command"
|
|
54
|
+
tabIndex={-1}
|
|
55
|
+
role="option"
|
|
56
|
+
data-cmd="langDropdown"
|
|
57
|
+
data-param1={lang.iso}
|
|
58
|
+
title={lang.name}
|
|
59
|
+
aria-selected="false"
|
|
60
|
+
>
|
|
61
|
+
{lang.name}
|
|
62
|
+
</a>
|
|
63
|
+
</li>
|
|
64
|
+
));
|
|
65
|
+
|
|
66
|
+
return renderToString(
|
|
67
|
+
<ul className="fr-dropdown-list" role="presentation">
|
|
68
|
+
<li role="presentation" key="remove" style={borderStyle}>
|
|
69
|
+
<a
|
|
70
|
+
className="fr-command"
|
|
71
|
+
tabIndex={-1}
|
|
72
|
+
role="option"
|
|
73
|
+
data-cmd="langDropdown"
|
|
74
|
+
data-param1={""}
|
|
75
|
+
title="Remove"
|
|
76
|
+
aria-selected="false"
|
|
77
|
+
style={{ fontStyle: "italic" }}
|
|
78
|
+
>
|
|
79
|
+
Remove
|
|
80
|
+
</a>
|
|
81
|
+
</li>
|
|
82
|
+
{featuredLanguages}
|
|
83
|
+
{otherLanguages}
|
|
84
|
+
</ul>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { getRichTextConfig, parseClassNames, getLanguageMenuHtml };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
|
-
import
|
|
3
|
+
import FroalaEditorComponent from "react-froala-wysiwyg";
|
|
4
|
+
import FroalaEditor from "froala-editor";
|
|
4
5
|
import { decodeEntities } from "@ax/helpers";
|
|
5
6
|
import { IImage, ISite, IRootState } from "@ax/types";
|
|
6
7
|
import { galleryActions } from "@ax/containers/Gallery";
|
|
7
8
|
|
|
8
|
-
import { wysiwygConfig, buttons, buttonsFull } from "./config";
|
|
9
|
+
import { wysiwygConfig, buttons, buttonsFull, inlineToolbar } from "./config";
|
|
9
10
|
import "./vendors";
|
|
10
11
|
import * as S from "./style";
|
|
11
12
|
|
|
@@ -21,6 +22,7 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
21
22
|
site,
|
|
22
23
|
token,
|
|
23
24
|
uploadImage,
|
|
25
|
+
inline = false,
|
|
24
26
|
} = props;
|
|
25
27
|
|
|
26
28
|
const imageSite = site ? site.id : "global";
|
|
@@ -33,13 +35,18 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
33
35
|
|
|
34
36
|
const dynamicConfig = {
|
|
35
37
|
placeholderText: placeholder,
|
|
36
|
-
toolbarButtons: full ? buttonsFull : buttons,
|
|
37
|
-
|
|
38
|
+
toolbarButtons: inline ? inlineToolbar : full ? buttonsFull : buttons,
|
|
39
|
+
toolbarInline: inline,
|
|
40
|
+
charCounterCount: !inline,
|
|
41
|
+
imageUpload: full && !inline ? true : false,
|
|
42
|
+
multiLine: !inline,
|
|
43
|
+
enter: inline ? FroalaEditor.ENTER_BR : FroalaEditor.ENTER_P,
|
|
38
44
|
requestHeaders: {
|
|
39
45
|
Authorization: `bearer ${token}`,
|
|
40
46
|
},
|
|
41
47
|
events: {
|
|
42
48
|
initialized() {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
43
50
|
const editor: any = this;
|
|
44
51
|
if (disabled) {
|
|
45
52
|
setTimeout(() => {
|
|
@@ -48,12 +55,14 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
48
55
|
}
|
|
49
56
|
},
|
|
50
57
|
"image.beforeUpload": async function (images: FileList) {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
51
59
|
const editor: any = this;
|
|
52
60
|
const { url } = await uploadImage(images[0], imageSite);
|
|
53
61
|
editor.image.insert(url, true, null, editor.image.get(), null);
|
|
54
62
|
return false;
|
|
55
63
|
},
|
|
56
64
|
blur: function () {
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
57
66
|
const editor: any = this;
|
|
58
67
|
const html = editor.html.get();
|
|
59
68
|
const stripedHtml = decodeEntities(html);
|
|
@@ -66,13 +75,12 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
66
75
|
|
|
67
76
|
return (
|
|
68
77
|
<S.EditorWrapper error={error} disabled={disabled} data-testid="wysiwyg-wrapper">
|
|
69
|
-
<
|
|
78
|
+
<FroalaEditorComponent tag="textarea" model={value} config={config} onModelChange={handleChange} />
|
|
70
79
|
</S.EditorWrapper>
|
|
71
80
|
);
|
|
72
81
|
};
|
|
73
82
|
|
|
74
83
|
interface IProps {
|
|
75
|
-
name: string;
|
|
76
84
|
value: string;
|
|
77
85
|
title: string;
|
|
78
86
|
full?: boolean;
|
|
@@ -84,6 +92,7 @@ interface IProps {
|
|
|
84
92
|
disabled?: boolean;
|
|
85
93
|
handleValidation?: (value: string) => void;
|
|
86
94
|
site: ISite;
|
|
95
|
+
inline?: boolean;
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
const mapStateToProps = (state: IRootState) => ({
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const languages = [
|
|
2
|
+
{
|
|
3
|
+
name: "Arabic (Saudi Arabia)",
|
|
4
|
+
iso: "ar-SA",
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
name: "Chinese (China)",
|
|
8
|
+
iso: "zh-CN",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "Chinese (Taiwan)",
|
|
12
|
+
iso: "zh-TW",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "Czech",
|
|
16
|
+
iso: "cs-CZ",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "Danish",
|
|
20
|
+
iso: "da-DK",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "Dutch",
|
|
24
|
+
iso: "nl-NL",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "English (United Kingdom)",
|
|
28
|
+
iso: "en-GB",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "English (United States)",
|
|
32
|
+
iso: "en-US",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "Finnish",
|
|
36
|
+
iso: "fi-FI",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "French (Canada)",
|
|
40
|
+
iso: "fr-CA",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "French (France)",
|
|
44
|
+
iso: "fr-FR",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "German",
|
|
48
|
+
iso: "de-DE",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "Greek",
|
|
52
|
+
iso: "el-GR",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "Hebrew",
|
|
56
|
+
iso: "he-IL",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "Hindi",
|
|
60
|
+
iso: "hi-IN",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "Hungarian",
|
|
64
|
+
iso: "hu-HU",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "Italian",
|
|
68
|
+
iso: "it-IT",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "Japanese",
|
|
72
|
+
iso: "ja-JP",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "Korean",
|
|
76
|
+
iso: "ko-KR",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "Norwegian",
|
|
80
|
+
iso: "no-NO",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "Polish",
|
|
84
|
+
iso: "pl-PL",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "Portuguese (Brazil)",
|
|
88
|
+
iso: "pt-BR",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "Portuguese (Portugal)",
|
|
92
|
+
iso: "pt-PT",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "Romanian",
|
|
96
|
+
iso: "ro-RO",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "Russian",
|
|
100
|
+
iso: "ru-RU",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "Slovak",
|
|
104
|
+
iso: "sk-SK",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "Spanish (Mexico)",
|
|
108
|
+
iso: "es-MX",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "Spanish (Spain)",
|
|
112
|
+
iso: "es-ES",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "Swedish",
|
|
116
|
+
iso: "sv-SE",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "Thai",
|
|
120
|
+
iso: "th-TH",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "Turkish",
|
|
124
|
+
iso: "tr-TR",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "Ukrainian",
|
|
128
|
+
iso: "uk-UA",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "Vietnamese",
|
|
132
|
+
iso: "vi-VN",
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
export { languages };
|
|
@@ -22,6 +22,9 @@ export const EditorWrapper = styled.div<{ error: boolean | undefined; disabled?:
|
|
|
22
22
|
${(p) => p.theme.textStyle.fieldContent};
|
|
23
23
|
margin-block-start: 1em;
|
|
24
24
|
margin-block-end: 1em;
|
|
25
|
+
span[lang] {
|
|
26
|
+
color: #F9861B;
|
|
27
|
+
}
|
|
25
28
|
}
|
|
26
29
|
p:first-child {
|
|
27
30
|
margin-block-start: 0;
|
|
@@ -59,6 +62,13 @@ export const EditorWrapper = styled.div<{ error: boolean | undefined; disabled?:
|
|
|
59
62
|
.fr-popup.fr-desktop.fr-active {
|
|
60
63
|
margin-left: -70px;
|
|
61
64
|
}
|
|
65
|
+
|
|
66
|
+
.fr-command.fr-btn svg {
|
|
67
|
+
margin: 5px 5px;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
.fr-toolbar.fr-toolbar-open {
|
|
71
|
+
padding-bottom: 16px;
|
|
62
72
|
}
|
|
63
73
|
.fr-second-toolbar {
|
|
64
74
|
border-color: ${(p) =>
|
|
@@ -71,15 +81,15 @@ export const EditorWrapper = styled.div<{ error: boolean | undefined; disabled?:
|
|
|
71
81
|
p.error === true
|
|
72
82
|
? p.theme.color.error
|
|
73
83
|
: p.disabled
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
? p.theme.color.interactiveDisabled
|
|
85
|
+
: p.theme.color.uiLine};
|
|
76
86
|
border-right: 1px solid
|
|
77
87
|
${(p) =>
|
|
78
88
|
p.error === true
|
|
79
89
|
? p.theme.color.error
|
|
80
90
|
: p.disabled
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
? p.theme.color.interactiveDisabled
|
|
92
|
+
: p.theme.color.uiLine};
|
|
83
93
|
}
|
|
84
94
|
.fr-disabled {
|
|
85
95
|
color: ${(p) => p.theme.color.interactiveDisabled};
|
|
@@ -91,8 +101,8 @@ export const EditorWrapper = styled.div<{ error: boolean | undefined; disabled?:
|
|
|
91
101
|
p.error === true
|
|
92
102
|
? p.theme.color.error
|
|
93
103
|
: p.disabled
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
? p.theme.color.interactiveDisabled
|
|
105
|
+
: p.theme.color.interactive01};
|
|
96
106
|
}
|
|
97
107
|
.fr-wrapper {
|
|
98
108
|
border-left: 1px solid
|
|
@@ -100,16 +110,49 @@ export const EditorWrapper = styled.div<{ error: boolean | undefined; disabled?:
|
|
|
100
110
|
p.error === true
|
|
101
111
|
? p.theme.color.error
|
|
102
112
|
: p.disabled
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
? p.theme.color.interactiveDisabled
|
|
114
|
+
: p.theme.color.interactive01};
|
|
105
115
|
border-right: 1px solid
|
|
106
116
|
${(p) =>
|
|
107
117
|
p.error === true
|
|
108
118
|
? p.theme.color.error
|
|
109
119
|
: p.disabled
|
|
120
|
+
? p.theme.color.interactiveDisabled
|
|
121
|
+
: p.theme.color.interactive01};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
.fr-inline {
|
|
126
|
+
.fr-wrapper {
|
|
127
|
+
background: ${(p) => p.theme.color.uiBackground02};
|
|
128
|
+
border-radius: ${(p) => p.theme.radii.s};
|
|
129
|
+
min-height: ${(p) => p.theme.spacing.l};
|
|
130
|
+
border: 1px solid
|
|
131
|
+
${(p) =>
|
|
132
|
+
p.error === true
|
|
133
|
+
? p.theme.color.error
|
|
134
|
+
: p.disabled
|
|
110
135
|
? p.theme.color.interactiveDisabled
|
|
111
|
-
: p.theme.color.
|
|
136
|
+
: p.theme.color.uiLine};
|
|
137
|
+
}
|
|
138
|
+
.fr-element {
|
|
139
|
+
${(p) => p.theme.textStyle.fieldContent};
|
|
140
|
+
color: ${(p) => p.theme.color.textHighEmphasis};
|
|
141
|
+
padding: ${(p) => `12px ${p.theme.spacing.s}`};
|
|
142
|
+
|
|
143
|
+
span[lang] {
|
|
144
|
+
color: #F9861B;
|
|
112
145
|
}
|
|
113
146
|
}
|
|
147
|
+
${Wrapper}:focus-within & {
|
|
148
|
+
.fr-wrapper {
|
|
149
|
+
border: 1px solid
|
|
150
|
+
${(p) =>
|
|
151
|
+
p.error === true
|
|
152
|
+
? p.theme.color.error
|
|
153
|
+
: p.disabled
|
|
154
|
+
? p.theme.color.interactiveDisabled
|
|
155
|
+
: p.theme.color.interactive01};
|
|
156
|
+
}
|
|
114
157
|
}
|
|
115
158
|
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
const SvgTranslate = (props) => (
|
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} fill="none" {...props}>
|
|
4
|
+
<path
|
|
5
|
+
fill="#5057FF"
|
|
6
|
+
d="m12.87 15.07-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04ZM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12Zm-2.62 7 1.62-4.33L19.12 17h-3.24Z"
|
|
7
|
+
/>
|
|
8
|
+
</svg>
|
|
9
|
+
);
|
|
10
|
+
export default SvgTranslate;
|
|
@@ -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 d="M12.87 15.07L10.33 12.56L10.36 12.53C12.1 10.59 13.34 8.36 14.07 6H17V4H10V2H8V4H1V5.99H12.17C11.5 7.92 10.44 9.75 9 11.35C8.07 10.32 7.3 9.19 6.69 8H4.69C5.42 9.63 6.42 11.17 7.67 12.56L2.58 17.58L4 19L9 14L12.11 17.11L12.87 15.07ZM18.5 10H16.5L12 22H14L15.12 19H19.87L21 22H23L18.5 10ZM15.88 17L17.5 12.67L19.12 17H15.88Z" fill="#5057FF"/>
|
|
3
|
+
</svg>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { IErrorItem, ILanguage } from "@ax/types";
|
|
3
|
+
import { useNetworkStatus } from "@ax/hooks";
|
|
4
|
+
import { Notification } from "@ax/components";
|
|
3
5
|
|
|
4
6
|
import AppBar from "./AppBar";
|
|
5
7
|
|
|
@@ -8,11 +10,19 @@ import * as S from "./style";
|
|
|
8
10
|
const MainWrapper = (props: IWrapperProps): JSX.Element => {
|
|
9
11
|
const { children, fixedAppBar, fullWidth, hasAnimation } = props;
|
|
10
12
|
|
|
13
|
+
const { isOnline } = useNetworkStatus();
|
|
14
|
+
|
|
11
15
|
return (
|
|
12
16
|
<S.Wrapper fixedAppBar={fixedAppBar} data-testid="main-wrapper">
|
|
13
17
|
{hasAnimation && <S.BackgroundAnimation />}
|
|
14
18
|
<AppBar {...props} />
|
|
15
19
|
<S.Main fullWidth={fullWidth} data-testid="main-component" id="main-component">
|
|
20
|
+
{!isOnline && (
|
|
21
|
+
<Notification
|
|
22
|
+
type="error"
|
|
23
|
+
text="You are offline. Please, check your internet connection. Changes you make may not be saved."
|
|
24
|
+
/>
|
|
25
|
+
)}
|
|
16
26
|
{children}
|
|
17
27
|
</S.Main>
|
|
18
28
|
</S.Wrapper>
|
package/src/global.d.ts
CHANGED
package/src/helpers/requests.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { setError } from "@ax/containers/App/actions";
|
|
2
1
|
import { Dispatch } from "redux";
|
|
3
2
|
|
|
4
3
|
const isReqOk = (reqStatus: number) => reqStatus >= 200 && reqStatus < 400;
|
|
@@ -15,23 +14,14 @@ function handleRequest(
|
|
|
15
14
|
|
|
16
15
|
const response = await callback();
|
|
17
16
|
|
|
18
|
-
if (!response) {
|
|
19
|
-
dispatch(
|
|
20
|
-
setError({
|
|
21
|
-
text: "You are offline. Please, check your internet connection. Changes you make may not be saved.",
|
|
22
|
-
})
|
|
23
|
-
);
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
const responseArr = Array.isArray(response) ? response : [response];
|
|
28
18
|
|
|
29
19
|
let result = true;
|
|
30
20
|
|
|
31
21
|
responseArr.forEach((response) => {
|
|
32
|
-
if (!isReqOk(response.status)) {
|
|
22
|
+
if (!response || !isReqOk(response.status)) {
|
|
33
23
|
result = false;
|
|
34
|
-
handleError(response);
|
|
24
|
+
!!response && handleError(response);
|
|
35
25
|
}
|
|
36
26
|
});
|
|
37
27
|
|
package/src/hooks/index.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { useWindowSize } from "./window";
|
|
|
7
7
|
import { useOnMessageReceivedFromIframe, useOnMessageReceivedFromOutside } from "./iframe";
|
|
8
8
|
import { usePermission, useGlobalPermission } from "./users";
|
|
9
9
|
import { useResizable } from "./resize";
|
|
10
|
+
import { useNetworkStatus } from "./network";
|
|
10
11
|
|
|
11
12
|
export {
|
|
12
13
|
useModal,
|
|
@@ -28,4 +29,5 @@ export {
|
|
|
28
29
|
useGlobalPermission,
|
|
29
30
|
useOnMessageReceivedFromIframe,
|
|
30
31
|
useOnMessageReceivedFromOutside,
|
|
32
|
+
useNetworkStatus,
|
|
31
33
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const useNetworkStatus = () => {
|
|
4
|
+
const [isOnline, setOnline] = useState<boolean>(true);
|
|
5
|
+
|
|
6
|
+
const updateNetworkStatus = () => {
|
|
7
|
+
setOnline(navigator.onLine);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// sometimes, the load event does not trigger on some browsers, that is why manually calling updateNetworkStatus on initial mount
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
updateNetworkStatus();
|
|
13
|
+
}, []);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
window.addEventListener("load", updateNetworkStatus);
|
|
17
|
+
window.addEventListener("online", updateNetworkStatus);
|
|
18
|
+
window.addEventListener("offline", updateNetworkStatus);
|
|
19
|
+
|
|
20
|
+
return () => {
|
|
21
|
+
window.removeEventListener("load", updateNetworkStatus);
|
|
22
|
+
window.removeEventListener("online", updateNetworkStatus);
|
|
23
|
+
window.removeEventListener("offline", updateNetworkStatus);
|
|
24
|
+
};
|
|
25
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
|
+
}, [navigator.onLine]);
|
|
27
|
+
|
|
28
|
+
return { isOnline };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { useNetworkStatus };
|
|
@@ -2,7 +2,7 @@ import React, { useReducer, useEffect } from "react";
|
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
3
|
|
|
4
4
|
import { IRootState, IStructuredData, ITemplateOption, IThemeElements } from "@ax/types";
|
|
5
|
-
import { filterThemeTemplates, getThumbnailProps
|
|
5
|
+
import { filterThemeTemplates, getThumbnailProps } from "@ax/helpers";
|
|
6
6
|
import { MenuItem, RadioGroup } from "@ax/components";
|
|
7
7
|
import { structuredDataActions } from "@ax/containers/StructuredData";
|
|
8
8
|
import { SecondaryActionButton, MainActionButton } from "./../atoms";
|
|
@@ -93,6 +93,18 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
|
|
|
93
93
|
isOptionInType(filteredOptionsByDataPack) &&
|
|
94
94
|
getThumbnailProps(state.selectedOption, true, theme);
|
|
95
95
|
|
|
96
|
+
const removeDuplicatesByTypeAndMode = (arr: ITemplateOption[]) => {
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
return arr.filter((item) => {
|
|
99
|
+
const key = `${item.type}::${item.mode}`;
|
|
100
|
+
if (item.mode && seen.has(key)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
seen.add(key);
|
|
104
|
+
return true;
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
96
108
|
const displayOptions = (item: IOptionFilter) => {
|
|
97
109
|
const { value } = item;
|
|
98
110
|
|
|
@@ -123,7 +135,7 @@ const OptionTable = (props: IOptionTableProps): JSX.Element => {
|
|
|
123
135
|
}
|
|
124
136
|
});
|
|
125
137
|
|
|
126
|
-
filteredOptionsByDataPack =
|
|
138
|
+
filteredOptionsByDataPack = removeDuplicatesByTypeAndMode(filteredOptionsByDataPack);
|
|
127
139
|
|
|
128
140
|
filteredOptionsByDataPack.sort((ele, comp) => {
|
|
129
141
|
// Use localeCompare to compare strings alphabetically
|