@griddo/ax 11.10.18 → 11.10.19
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/components/Fields/AsyncSelect/index.tsx +41 -12
- package/src/components/Fields/Select/index.tsx +37 -4
- package/src/components/Fields/Wysiwyg/config.tsx +3 -5
- package/src/components/Fields/Wysiwyg/index.tsx +3 -4
- package/src/components/OcassionalToast/index.tsx +3 -3
- package/src/components/OcassionalToast/style.tsx +2 -1
- package/src/components/SearchField/index.tsx +4 -4
- package/src/containers/PageEditor/actions.tsx +39 -28
- package/src/containers/PageEditor/reducer.tsx +1 -1
- package/src/modules/Forms/FormEditor/Editor/SideModal/SectionOption/index.tsx +9 -3
- package/src/modules/Forms/FormEditor/Editor/SideModal/SideModalOption/index.tsx +8 -2
- package/src/modules/Forms/FormEditor/Editor/SideModal/index.tsx +9 -9
- package/src/modules/Forms/FormEditor/Editor/SideModal/style.tsx +23 -3
- package/src/modules/GlobalEditor/index.tsx +35 -19
- package/src/modules/Login/index.tsx +4 -5
- package/src/modules/PageEditor/index.tsx +40 -23
- package/src/themes/theme.json +6 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griddo/ax",
|
|
3
3
|
"description": "Griddo Author Experience",
|
|
4
|
-
"version": "11.10.
|
|
4
|
+
"version": "11.10.19",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
|
|
7
7
|
"Diego M. Béjar <diego.bejar@secuoyas.com>",
|
|
@@ -217,5 +217,5 @@
|
|
|
217
217
|
"publishConfig": {
|
|
218
218
|
"access": "public"
|
|
219
219
|
},
|
|
220
|
-
"gitHead": "
|
|
220
|
+
"gitHead": "857cf0aff1532903cf81af0656bfce05c4fdff4a"
|
|
221
221
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { components } from "react-select";
|
|
3
|
+
import type { GroupBase, InputActionMeta, OptionsOrGroups } from "react-select";
|
|
3
4
|
|
|
4
5
|
import { isReqOk } from "@ax/helpers";
|
|
5
6
|
import { selects, checkgroups } from "@ax/api";
|
|
6
|
-
import { ILanguage, IPage, ISite } from "@ax/types";
|
|
7
|
+
import type { ILanguage, IPage, ISite } from "@ax/types";
|
|
7
8
|
|
|
8
9
|
import * as S from "./style";
|
|
9
10
|
|
|
@@ -38,9 +39,13 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
|
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
const [state, setState] = useState(initialState);
|
|
42
|
+
const menuRef = useRef<HTMLDivElement | null>(null);
|
|
43
|
+
|
|
41
44
|
|
|
42
45
|
const isPage = entity === "pages";
|
|
43
46
|
const isCategories = entity === "categories";
|
|
47
|
+
const className = error ? `react-select-error ${type}` : type;
|
|
48
|
+
const isSearchable = type !== "inline";
|
|
44
49
|
|
|
45
50
|
const languagesIds = Array.isArray(contentLanguages)
|
|
46
51
|
? languages?.filter((lang) => contentLanguages.includes(lang.locale)).map((lang) => lang.id)
|
|
@@ -56,7 +61,7 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
|
|
|
56
61
|
if (entity) {
|
|
57
62
|
let result = null;
|
|
58
63
|
if (site && !isCategories) {
|
|
59
|
-
const fullOptions = languagesIds
|
|
64
|
+
const fullOptions = languagesIds?.length ? { ...options, languagesIds } : options;
|
|
60
65
|
result =
|
|
61
66
|
isPage && selectedContent
|
|
62
67
|
? await selects.getSelectSiteItems(site.id, entity, fullOptions, selectedContent.id)
|
|
@@ -80,7 +85,6 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
|
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
} catch (e) {
|
|
83
|
-
// biome-ignore lint/suspicious/noConsole: TODO: fix this
|
|
84
88
|
console.log(e);
|
|
85
89
|
}
|
|
86
90
|
return data;
|
|
@@ -97,7 +101,6 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
|
|
|
97
101
|
: optionValues;
|
|
98
102
|
if (isMounted) setState((state) => ({ ...state, items: filteredOptions }));
|
|
99
103
|
})
|
|
100
|
-
// biome-ignore lint/suspicious/noConsole: TODO: fix this
|
|
101
104
|
.catch((apiError) => console.log(apiError));
|
|
102
105
|
return () => {
|
|
103
106
|
isMounted = false;
|
|
@@ -121,21 +124,45 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
|
|
|
121
124
|
}
|
|
122
125
|
};
|
|
123
126
|
|
|
124
|
-
const loadOptions = (inputValue: string, callback:
|
|
127
|
+
const loadOptions = (inputValue: string, callback: (options: OptionsOrGroups<unknown, GroupBase<unknown>>) => void) => {
|
|
125
128
|
setTimeout(() => {
|
|
126
129
|
callback(filterOptions(inputValue));
|
|
127
130
|
}, 0);
|
|
128
131
|
};
|
|
129
132
|
|
|
130
|
-
const getObjectValue = (val:
|
|
133
|
+
const getObjectValue = (val: string | number | null | undefined, options: IOption[]) => {
|
|
131
134
|
const fixedVal = Array.isArray(val) ? val[0] : val;
|
|
132
135
|
const selectedValue = fixedVal && typeof fixedVal === "object" ? fixedVal.id : fixedVal;
|
|
133
136
|
|
|
134
|
-
return options
|
|
137
|
+
return options?.find((option: IOption) => option.value === selectedValue);
|
|
135
138
|
};
|
|
136
139
|
|
|
137
|
-
const
|
|
138
|
-
|
|
140
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
141
|
+
if (!menuRef.current || isSearchable) return;
|
|
142
|
+
|
|
143
|
+
const char = e.key.toLowerCase();
|
|
144
|
+
if (!/^[a-z]$/i.test(char)) return;
|
|
145
|
+
|
|
146
|
+
const optionNodes = menuRef.current.querySelectorAll<HTMLDivElement>(
|
|
147
|
+
".react-select__option"
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const targetNode = Array.from(optionNodes).find((node) =>
|
|
151
|
+
node.innerText.trim().toLowerCase().startsWith(char)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (targetNode) {
|
|
155
|
+
targetNode.scrollIntoView({
|
|
156
|
+
block: "nearest",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const Menu = (menuProps: any) => (
|
|
162
|
+
<div ref={menuRef}>
|
|
163
|
+
<components.Menu {...menuProps} />
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
139
166
|
|
|
140
167
|
return (
|
|
141
168
|
<div data-testid="asyncSelect">
|
|
@@ -153,9 +180,11 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
|
|
|
153
180
|
className={className}
|
|
154
181
|
required={mandatory}
|
|
155
182
|
isClearable={false}
|
|
156
|
-
isSearchable={
|
|
183
|
+
isSearchable={isSearchable}
|
|
157
184
|
maxWidth={maxWidth}
|
|
158
185
|
inputValue={state.inputText}
|
|
186
|
+
components={{ Menu }}
|
|
187
|
+
onKeyDown={handleKeyDown}
|
|
159
188
|
/>
|
|
160
189
|
</div>
|
|
161
190
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { InputActionMeta } from "react-select";
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import type { InputActionMeta } from "react-select";
|
|
3
|
+
import { components } from "react-select";
|
|
3
4
|
|
|
4
5
|
import * as S from "./style";
|
|
5
6
|
|
|
@@ -19,11 +20,16 @@ const Select = (props: ISelectProps): JSX.Element => {
|
|
|
19
20
|
alignRight,
|
|
20
21
|
maxWidth,
|
|
21
22
|
} = props;
|
|
23
|
+
|
|
22
24
|
const className = error ? `react-select-error ${type}` : type;
|
|
23
25
|
const emptyOption = { value: "", label: placeholder || "Empty" };
|
|
26
|
+
const isSearchable = !(type === "inline" || type === "mini" );
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
const [hasEmptyOption, setHasEmptyOption] = useState(!mandatory);
|
|
26
30
|
const [inputText, setInputText] = useState<string>("");
|
|
31
|
+
const menuRef = useRef<HTMLDivElement | null>(null);
|
|
32
|
+
|
|
27
33
|
|
|
28
34
|
const optionValues: IOptionProps[] = hasEmptyOption ? [emptyOption, ...options] : options;
|
|
29
35
|
|
|
@@ -37,10 +43,35 @@ const Select = (props: ISelectProps): JSX.Element => {
|
|
|
37
43
|
}
|
|
38
44
|
};
|
|
39
45
|
|
|
46
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
47
|
+
if (!menuRef.current || isSearchable) return;
|
|
48
|
+
|
|
49
|
+
const char = e.key.toLowerCase();
|
|
50
|
+
if (!/^[a-z]$/i.test(char)) return;
|
|
51
|
+
|
|
52
|
+
const optionNodes = menuRef.current.querySelectorAll<HTMLDivElement>(
|
|
53
|
+
".react-select__option"
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const targetNode = Array.from(optionNodes).find((node) =>
|
|
57
|
+
node.innerText.trim().toLowerCase().startsWith(char)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (targetNode) {
|
|
61
|
+
targetNode.scrollIntoView({
|
|
62
|
+
block: "nearest",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
40
67
|
const getObjectValue = (value: string | undefined, options: IOptionProps[]) =>
|
|
41
68
|
options.find((option) => option.value === value);
|
|
42
69
|
|
|
43
|
-
const
|
|
70
|
+
const Menu = (menuProps: any) => (
|
|
71
|
+
<div ref={menuRef}>
|
|
72
|
+
<components.Menu {...menuProps} />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
44
75
|
|
|
45
76
|
return (
|
|
46
77
|
<div data-testid="select-component">
|
|
@@ -56,13 +87,15 @@ const Select = (props: ISelectProps): JSX.Element => {
|
|
|
56
87
|
classNamePrefix="react-select"
|
|
57
88
|
error={error}
|
|
58
89
|
onChange={handleChange}
|
|
59
|
-
isSearchable={
|
|
90
|
+
isSearchable={isSearchable}
|
|
60
91
|
isClearable={false}
|
|
61
92
|
onInputChange={handleInputChange}
|
|
62
93
|
alignRight={alignRight}
|
|
63
94
|
aria-label={name}
|
|
64
95
|
maxWidth={maxWidth}
|
|
65
96
|
inputValue={inputText}
|
|
97
|
+
components={{ Menu }}
|
|
98
|
+
onKeyDown={handleKeyDown}
|
|
66
99
|
/>
|
|
67
100
|
</div>
|
|
68
101
|
);
|
|
@@ -59,7 +59,7 @@ const buttons = [
|
|
|
59
59
|
const inlineToolbar = ["langDropdown", "undo", "redo"];
|
|
60
60
|
|
|
61
61
|
const wysiwygConfig = {
|
|
62
|
-
key: process.env.REACT_APP_FROALA_KEY,
|
|
62
|
+
key: process.env.GRIDDO_FROALA_KEY || process.env.REACT_APP_FROALA_KEY,
|
|
63
63
|
// pastePlain: false,
|
|
64
64
|
pasteDeniedAttrs: ["class", "id", "style", "dir"],
|
|
65
65
|
listAdvancedTypes: false,
|
|
@@ -110,10 +110,8 @@ FroalaEditor.RegisterCommand("langDropdown", {
|
|
|
110
110
|
undo: true,
|
|
111
111
|
focus: true,
|
|
112
112
|
refreshAfterCallback: true,
|
|
113
|
-
html:
|
|
114
|
-
|
|
115
|
-
},
|
|
116
|
-
callback: function (cmd: any, val: string) {
|
|
113
|
+
html: () => getLanguageMenuHtml(richTextConfig?.editorLangs ? richTextConfig.editorLangs : languages),
|
|
114
|
+
callback: function (_cmd: any, val: string) {
|
|
117
115
|
const html = this.selection.text();
|
|
118
116
|
if (val !== "") {
|
|
119
117
|
this.html.insert(`<span lang="${val}" translate="no">${html}</span>`, true);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { connect } from "react-redux";
|
|
3
2
|
import FroalaEditorComponent from "react-froala-wysiwyg";
|
|
4
3
|
import FroalaEditor from "froala-editor";
|
|
5
4
|
import { decodeEntities } from "@ax/helpers";
|
|
6
|
-
import { IImage, ISite, IRootState } from "@ax/types";
|
|
5
|
+
import type { IImage, ISite, IRootState } from "@ax/types";
|
|
7
6
|
import { galleryActions } from "@ax/containers/Gallery";
|
|
8
7
|
|
|
9
8
|
import { wysiwygConfig, buttons, buttonsFull, inlineToolbar } from "./config";
|
|
@@ -40,7 +39,7 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
40
39
|
toolbarButtons: inline ? inlineToolbar : full ? buttonsFull : buttons,
|
|
41
40
|
toolbarInline: inline,
|
|
42
41
|
charCounterCount: !inline,
|
|
43
|
-
imageUpload: full && !inline
|
|
42
|
+
imageUpload: !!(full && !inline ),
|
|
44
43
|
multiLine: !inline,
|
|
45
44
|
enter: inline ? FroalaEditor.ENTER_BR : FroalaEditor.ENTER_P,
|
|
46
45
|
requestHeaders: {
|
|
@@ -70,7 +69,7 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
70
69
|
const editor: any = this;
|
|
71
70
|
const html = editor.html.get();
|
|
72
71
|
const stripedHtml = decodeEntities(html);
|
|
73
|
-
handleValidation
|
|
72
|
+
handleValidation?.(stripedHtml);
|
|
74
73
|
},
|
|
75
74
|
},
|
|
76
75
|
};
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { createPortal } from "react-dom";
|
|
3
2
|
import { Icon } from "@ax/components";
|
|
4
3
|
|
|
5
4
|
import * as S from "./style";
|
|
6
5
|
|
|
7
6
|
const OcassionalToast = (props: IOcassionalToastProps): JSX.Element => {
|
|
8
|
-
const { message } = props;
|
|
7
|
+
const { message, icon = "alert" } = props;
|
|
9
8
|
|
|
10
9
|
return createPortal(
|
|
11
10
|
<S.Wrapper data-testid="occasional-toast-wrapper">
|
|
12
11
|
<S.IconWrapper>
|
|
13
|
-
<Icon name=
|
|
12
|
+
<Icon name={icon} />
|
|
14
13
|
</S.IconWrapper>
|
|
15
14
|
<S.Text data-testid="occasional-toast-message">{message}</S.Text>
|
|
16
15
|
</S.Wrapper>,
|
|
@@ -20,6 +19,7 @@ const OcassionalToast = (props: IOcassionalToastProps): JSX.Element => {
|
|
|
20
19
|
|
|
21
20
|
export interface IOcassionalToastProps {
|
|
22
21
|
message: string;
|
|
22
|
+
icon?: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export default OcassionalToast;
|
|
@@ -3,9 +3,10 @@ import styled from "styled-components";
|
|
|
3
3
|
const Wrapper = styled.div`
|
|
4
4
|
position: fixed;
|
|
5
5
|
display: flex;
|
|
6
|
-
background: ${(p) => p.theme.color.
|
|
6
|
+
background: ${(p) => p.theme.color.uiBackground04};
|
|
7
7
|
justify-content: space-between;
|
|
8
8
|
align-items: center;
|
|
9
|
+
border: 1px solid ${(p) => p.theme.color.borderInverse};
|
|
9
10
|
border-radius: ${(p) => p.theme.radii.s};
|
|
10
11
|
z-index: 2000;
|
|
11
12
|
bottom: ${(p) => p.theme.spacing.m};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Icon } from "@ax/components";
|
|
2
|
-
import
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Select } from "../Fields";
|
|
4
4
|
|
|
5
5
|
import * as S from "./style";
|
|
@@ -19,7 +19,7 @@ const SearchField = (props: ISearchFieldProps): JSX.Element => {
|
|
|
19
19
|
value,
|
|
20
20
|
} = props;
|
|
21
21
|
|
|
22
|
-
const [isOpen, setIsOpen] = useState(value && value.trim() !== ""
|
|
22
|
+
const [isOpen, setIsOpen] = useState(!!(value && value.trim() !== "" ));
|
|
23
23
|
const [inputValue, setInputValue] = useState(value || "");
|
|
24
24
|
const [selectValue, setSelectValue] = useState<string>("");
|
|
25
25
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
@@ -71,14 +71,14 @@ const SearchField = (props: ISearchFieldProps): JSX.Element => {
|
|
|
71
71
|
|
|
72
72
|
const handleSelectChange = (value: string) => {
|
|
73
73
|
setSelectValue(value);
|
|
74
|
-
onFilterChange
|
|
74
|
+
onFilterChange?.(value);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
return (
|
|
78
78
|
<S.Wrapper data-testid="search-field-wrapper">
|
|
79
79
|
{showField ? (
|
|
80
80
|
<S.FieldWrapper closeOnInactive={closeOnInactive} disabled={disabled} data-testid="field-wrapper">
|
|
81
|
-
{searchFilters
|
|
81
|
+
{searchFilters?.length && (
|
|
82
82
|
<>
|
|
83
83
|
<S.FilterWrapper data-testid="filter-wrapper">
|
|
84
84
|
<Select
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dispatch } from "redux";
|
|
1
|
+
import type { Dispatch } from "redux";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
isReqOk,
|
|
@@ -75,7 +75,7 @@ import {
|
|
|
75
75
|
SET_SCROLL_EDITOR_ID,
|
|
76
76
|
} from "./constants";
|
|
77
77
|
|
|
78
|
-
import {
|
|
78
|
+
import type {
|
|
79
79
|
IPage,
|
|
80
80
|
ISavePageParams,
|
|
81
81
|
IBreadcrumbItem,
|
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
IRootState,
|
|
91
91
|
IGetSitePagesParams,
|
|
92
92
|
} from "@ax/types";
|
|
93
|
-
import {
|
|
93
|
+
import type {
|
|
94
94
|
ISetBreadcrumb,
|
|
95
95
|
ISetSchema,
|
|
96
96
|
ISetTab,
|
|
@@ -110,7 +110,6 @@ import {
|
|
|
110
110
|
ISetValidated,
|
|
111
111
|
ISetSitePageID,
|
|
112
112
|
ISetUserEditing,
|
|
113
|
-
pageStatus,
|
|
114
113
|
ISetLastElementAddedId,
|
|
115
114
|
ISetCopyModule,
|
|
116
115
|
ISetIsIATranslated,
|
|
@@ -119,6 +118,8 @@ import {
|
|
|
119
118
|
ISetScrollEditorID,
|
|
120
119
|
} from "./interfaces";
|
|
121
120
|
|
|
121
|
+
import { pageStatus } from "./interfaces";
|
|
122
|
+
|
|
122
123
|
const { setIsLoading, setIsSaving, handleError } = appActions;
|
|
123
124
|
const { getDefaults } = navigationActions;
|
|
124
125
|
|
|
@@ -353,12 +354,12 @@ function getPage(pageID?: number, global?: boolean): (dispatch: Dispatch, getSta
|
|
|
353
354
|
const { liveStatus, component } = getDefaultSchema(baseSchema);
|
|
354
355
|
|
|
355
356
|
if (isNewTranslation) {
|
|
356
|
-
page
|
|
357
|
-
page
|
|
358
|
-
page
|
|
357
|
+
page.liveStatus = liveStatus;
|
|
358
|
+
page.canBeTranslated = true;
|
|
359
|
+
page.originalLanguage = page.language;
|
|
359
360
|
}
|
|
360
361
|
|
|
361
|
-
if (global) page
|
|
362
|
+
if (global) page.component = component;
|
|
362
363
|
|
|
363
364
|
const pageLiveStatus =
|
|
364
365
|
page.draftFromPage && !page.publicationScheduled
|
|
@@ -409,14 +410,16 @@ function getTemplatePage(template: string): (dispatch: Dispatch, getState: () =>
|
|
|
409
410
|
};
|
|
410
411
|
}
|
|
411
412
|
|
|
412
|
-
function savePage(
|
|
413
|
-
createDraft
|
|
414
|
-
publishPage?:
|
|
413
|
+
function savePage(data?: {
|
|
414
|
+
createDraft?: boolean,
|
|
415
|
+
publishPage?: { status: string },
|
|
415
416
|
publishDraft?: boolean,
|
|
416
|
-
|
|
417
|
+
setToast?: (msg: string) => void
|
|
418
|
+
}): (dispatch: Dispatch, getState: any) => Promise<boolean> {
|
|
417
419
|
return async (dispatch, getState) => {
|
|
418
420
|
try {
|
|
419
421
|
dispatch(setIsSaving(true));
|
|
422
|
+
const { createDraft = false, publishPage, publishDraft, setToast } = data || {};
|
|
420
423
|
const {
|
|
421
424
|
pageEditor: {
|
|
422
425
|
selectedEditorID,
|
|
@@ -449,20 +452,20 @@ function savePage(
|
|
|
449
452
|
}
|
|
450
453
|
|
|
451
454
|
if (createDraft) {
|
|
452
|
-
values
|
|
455
|
+
values.draftFromPage = values.id;
|
|
453
456
|
delete values.id;
|
|
454
457
|
}
|
|
455
458
|
|
|
456
459
|
/* remove header and footer if page should get the default */
|
|
457
460
|
const { defaultHeader, defaultFooter } =
|
|
458
|
-
(templateConfig
|
|
461
|
+
(templateConfig?.templates?.[template.templateType]) || {};
|
|
459
462
|
|
|
460
463
|
if (header && ((header.setAsDefault && !defaultHeader) || header.id === defaultHeader)) {
|
|
461
|
-
values
|
|
464
|
+
values.header = null;
|
|
462
465
|
}
|
|
463
466
|
|
|
464
467
|
if (footer && ((footer.setAsDefault && !defaultFooter) || footer.id === defaultFooter)) {
|
|
465
|
-
values
|
|
468
|
+
values.footer = null;
|
|
466
469
|
}
|
|
467
470
|
|
|
468
471
|
const saveResponse =
|
|
@@ -503,7 +506,11 @@ function savePage(
|
|
|
503
506
|
dispatch(setIsSaving(false));
|
|
504
507
|
return true;
|
|
505
508
|
} else {
|
|
506
|
-
|
|
509
|
+
if(setToast){
|
|
510
|
+
setToast(saveResponse.data.message);
|
|
511
|
+
} else {
|
|
512
|
+
appActions.handleError(saveResponse)(dispatch);
|
|
513
|
+
}
|
|
507
514
|
dispatch(setIsSaving(false));
|
|
508
515
|
return false;
|
|
509
516
|
}
|
|
@@ -738,7 +745,7 @@ function addComponent(
|
|
|
738
745
|
|
|
739
746
|
generatePageContent(updatedPageContent)(dispatch, getState);
|
|
740
747
|
|
|
741
|
-
if (typeof type === "object" && Object.
|
|
748
|
+
if (typeof type === "object" && Object.hasOwn(type, "editorID")) {
|
|
742
749
|
setSelectedContent(type.editorID)(dispatch, getState);
|
|
743
750
|
} else {
|
|
744
751
|
const { sections: generatedSections } = getStateValues(getState);
|
|
@@ -804,7 +811,7 @@ function addModule(
|
|
|
804
811
|
type,
|
|
805
812
|
};
|
|
806
813
|
|
|
807
|
-
let updatedSections,
|
|
814
|
+
let updatedSections: any,
|
|
808
815
|
updatedSectionIndex = 0;
|
|
809
816
|
|
|
810
817
|
if (isComponentModule) {
|
|
@@ -1068,7 +1075,7 @@ function pasteModule(
|
|
|
1068
1075
|
if (isReqOk(response.status)) {
|
|
1069
1076
|
const validDataIds: number[] = [];
|
|
1070
1077
|
response.data.items.forEach(
|
|
1071
|
-
(data: IStructuredDataContent) => data.relatedSite && validDataIds.push(data.id),
|
|
1078
|
+
(data: IStructuredDataContent) => { data.relatedSite && validDataIds.push(data.id) },
|
|
1072
1079
|
);
|
|
1073
1080
|
const hasInvalidData = validDataIds.length < fixed.length;
|
|
1074
1081
|
validatedModuleCopy.data.fixed = validDataIds;
|
|
@@ -1111,7 +1118,11 @@ function overwriteHeaderConfig(params: IFieldProps): (dispatch: Dispatch, getSta
|
|
|
1111
1118
|
const updatedContent = deepClone(content);
|
|
1112
1119
|
const { headerConfig } = updatedContent.editorContent;
|
|
1113
1120
|
|
|
1114
|
-
|
|
1121
|
+
if(headerConfig[id]){
|
|
1122
|
+
headerConfig[id][key] = value
|
|
1123
|
+
} else {
|
|
1124
|
+
headerConfig[id] = { [key]: value }
|
|
1125
|
+
}
|
|
1115
1126
|
|
|
1116
1127
|
dispatch(setEditorContent({ ...updatedContent }));
|
|
1117
1128
|
};
|
|
@@ -1126,7 +1137,7 @@ function generatePageContent(editorContent: IPage): (dispatch: Dispatch, getStat
|
|
|
1126
1137
|
} = getState();
|
|
1127
1138
|
|
|
1128
1139
|
const { header, footer, isGlobal } = editorContent;
|
|
1129
|
-
const { defaultHeader, defaultFooter } = (configFormData?.templates
|
|
1140
|
+
const { defaultHeader, defaultFooter } = (configFormData?.templates?.[template]) || {};
|
|
1130
1141
|
|
|
1131
1142
|
const headerID =
|
|
1132
1143
|
header === null || header === undefined ? defaultHeader : typeof header === "object" ? header.id : header;
|
|
@@ -1333,7 +1344,7 @@ function getTemplateConfig(template: string): (dispatch: Dispatch, getState: ()
|
|
|
1333
1344
|
const {
|
|
1334
1345
|
sites: { currentSiteInfo },
|
|
1335
1346
|
} = getState();
|
|
1336
|
-
const currentSiteID = currentSiteInfo
|
|
1347
|
+
const currentSiteID = currentSiteInfo?.id || null;
|
|
1337
1348
|
const responseActions = {
|
|
1338
1349
|
handleSuccess: (data: any) => dispatch(setTemplateConfig(data)),
|
|
1339
1350
|
handleError: () => console.log("Error en getTemplateConfig"),
|
|
@@ -1381,7 +1392,7 @@ function validatePage(publish?: boolean): (dispatch: Dispatch, getState: () => I
|
|
|
1381
1392
|
const fieldErrors = findFieldsErrors(content);
|
|
1382
1393
|
|
|
1383
1394
|
errors = [...errors, ...fieldErrors];
|
|
1384
|
-
let packagesActivationErrors;
|
|
1395
|
+
let packagesActivationErrors: any;
|
|
1385
1396
|
if (!isGlobalPage) {
|
|
1386
1397
|
packagesActivationErrors = findPackagesActivationErrors(pageEditor, modules, templates);
|
|
1387
1398
|
}
|
|
@@ -1541,8 +1552,8 @@ function getPageSummary(): (dispatch: Dispatch, getState: () => IRootState) => P
|
|
|
1541
1552
|
const responseActions = {
|
|
1542
1553
|
handleSuccess: (data: { summary: string; keywords: string[] }) => {
|
|
1543
1554
|
const content = deepClone(editorContent);
|
|
1544
|
-
content
|
|
1545
|
-
content
|
|
1555
|
+
content.metaDescription = data.summary;
|
|
1556
|
+
content.metaKeywords = data.keywords;
|
|
1546
1557
|
generatePageContent(content)(dispatch, getState);
|
|
1547
1558
|
},
|
|
1548
1559
|
handleError: () => console.log("Error en GetPageSummary"),
|
|
@@ -1567,7 +1578,7 @@ function getPageTranslation(langID: number): (dispatch: Dispatch, getState: () =
|
|
|
1567
1578
|
|
|
1568
1579
|
const responseActions = {
|
|
1569
1580
|
handleSuccess: (data: IPage) => {
|
|
1570
|
-
data
|
|
1581
|
+
data.canBeTranslated = false;
|
|
1571
1582
|
generatePageContent(data)(dispatch, getState);
|
|
1572
1583
|
dispatch(setCurrentPageName(data.title));
|
|
1573
1584
|
dispatch(setIsIATranslated(true));
|
|
@@ -1609,7 +1620,7 @@ function schedulePublication(
|
|
|
1609
1620
|
const updatedEditorContent = { ...editorContent, publicationScheduled: date };
|
|
1610
1621
|
|
|
1611
1622
|
dispatch(setEditorContent(updatedEditorContent));
|
|
1612
|
-
return await savePage(false, { status })(dispatch, getState);
|
|
1623
|
+
return await savePage({createDraft: false, publishPage: { status }})(dispatch, getState);
|
|
1613
1624
|
} catch (e) {
|
|
1614
1625
|
console.log("Error", e);
|
|
1615
1626
|
return false;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ISchemaField } from "@ax/types";
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import type { ISchemaField } from "@ax/types";
|
|
3
3
|
|
|
4
4
|
import * as S from "./style";
|
|
5
5
|
|
|
@@ -8,8 +8,14 @@ const SectionOption = (props: IProps) => {
|
|
|
8
8
|
|
|
9
9
|
const setOption = () => handleClick(option);
|
|
10
10
|
|
|
11
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
12
|
+
if (e.key === "Enter") {
|
|
13
|
+
setOption();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
11
17
|
return (
|
|
12
|
-
<S.Item onClick={setOption} data-testid="section-option">
|
|
18
|
+
<S.Item onClick={setOption} onKeyDown={handleKeyDown} tabIndex={0} data-testid="section-option">
|
|
13
19
|
Fields for {option.title}
|
|
14
20
|
</S.Item>
|
|
15
21
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { memo } from "react";
|
|
2
2
|
|
|
3
3
|
import { getDisplayName, getThumbnailProps, filterImageText } from "@ax/helpers";
|
|
4
4
|
|
|
@@ -16,8 +16,14 @@ const SideModalOption = (props: IProps) => {
|
|
|
16
16
|
|
|
17
17
|
const setOption = () => handleClick(option);
|
|
18
18
|
|
|
19
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
20
|
+
if (e.key === "Enter") {
|
|
21
|
+
setOption();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
19
25
|
return (
|
|
20
|
-
<S.Item onClick={setOption} data-testid="side-modal-option">
|
|
26
|
+
<S.Item onClick={setOption} onKeyDown={handleKeyDown} tabIndex={0} data-testid="side-modal-option">
|
|
21
27
|
{thumbnailProps && <S.Thumbnail data-testid="side-modal-option-img" {...thumbnailProps} />}
|
|
22
28
|
{label}
|
|
23
29
|
</S.Item>
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { useState } from "react";
|
|
3
2
|
import { getDisplayName } from "@ax/helpers";
|
|
4
3
|
import { SearchField, FloatingButton, NoteField } from "@ax/components";
|
|
5
|
-
import { ISchemaField } from "@ax/types";
|
|
4
|
+
import type { ISchemaField } from "@ax/types";
|
|
6
5
|
|
|
7
6
|
import SideModalOption from "./SideModalOption";
|
|
8
7
|
import SectionOption from "./SectionOption";
|
|
@@ -27,19 +26,19 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
|
|
|
27
26
|
setIsOpen((state) => !state);
|
|
28
27
|
};
|
|
29
28
|
|
|
30
|
-
const filteredOptions = selectedField?.whiteList?.map((option: string
|
|
29
|
+
const filteredOptions = selectedField?.whiteList?.map((option: string) => {
|
|
31
30
|
const displayName = getDisplayName(option);
|
|
32
31
|
if (searchQuery.length > 0) {
|
|
33
32
|
const name = displayName.toLowerCase();
|
|
34
33
|
const search = searchQuery.toLocaleLowerCase();
|
|
35
|
-
if (!name.includes(search)) return
|
|
34
|
+
if (!name.includes(search)) return null;
|
|
36
35
|
}
|
|
37
36
|
return (
|
|
38
|
-
<SideModalOption option={option} handleClick={handleClick} key={
|
|
37
|
+
<SideModalOption option={option} handleClick={handleClick} key={option} theme={theme}>
|
|
39
38
|
{displayName}
|
|
40
39
|
</SideModalOption>
|
|
41
40
|
);
|
|
42
|
-
}) ||
|
|
41
|
+
}) || null;
|
|
43
42
|
|
|
44
43
|
const sectionOptions = fieldArrays.map((option) => (
|
|
45
44
|
<SectionOption option={option} handleClick={handleSectionClick} key={option.key} />
|
|
@@ -69,7 +68,7 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
|
|
|
69
68
|
</S.ColumnsWrapper>
|
|
70
69
|
</>
|
|
71
70
|
) : (
|
|
72
|
-
|
|
71
|
+
<S.ModalContent>
|
|
73
72
|
<S.Header>
|
|
74
73
|
{isMultiArray && selectedField && (
|
|
75
74
|
<S.BreadCrumb>
|
|
@@ -86,6 +85,7 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
|
|
|
86
85
|
closeOnInactive={false}
|
|
87
86
|
small={true}
|
|
88
87
|
placeholder="Search field"
|
|
88
|
+
focus={false}
|
|
89
89
|
/>
|
|
90
90
|
</S.SearchWrapper>
|
|
91
91
|
</S.Header>
|
|
@@ -95,7 +95,7 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
|
|
|
95
95
|
</S.FloatingButtonWrapper>
|
|
96
96
|
<S.Content>{filteredOptions}</S.Content>
|
|
97
97
|
</S.ColumnsWrapper>
|
|
98
|
-
|
|
98
|
+
</S.ModalContent>
|
|
99
99
|
)}
|
|
100
100
|
</S.Wrapper>
|
|
101
101
|
<S.ClosedWrapper isOpen={!isOpen}>
|
|
@@ -7,17 +7,19 @@ const Header = styled.div`
|
|
|
7
7
|
padding: ${(p) => `${p.theme.spacing.s} ${p.theme.spacing.s} 0`};
|
|
8
8
|
`;
|
|
9
9
|
|
|
10
|
-
const Content = styled.
|
|
10
|
+
const Content = styled.ul`
|
|
11
11
|
list-style: none;
|
|
12
12
|
padding: ${(p) => p.theme.spacing.s};
|
|
13
|
-
height:
|
|
13
|
+
height: 100%;
|
|
14
14
|
width: ${(p) => `calc(${p.theme.spacing.xl} * 3)`};
|
|
15
|
-
overflow: auto;
|
|
16
15
|
border-right: 1px solid ${(p) => p.theme.colors.uiLine};
|
|
17
16
|
&:last-child {
|
|
18
17
|
border-right: 0;
|
|
19
18
|
width: ${(p) => `calc(${p.theme.spacing.xl} * 4)`};
|
|
20
19
|
}
|
|
20
|
+
& > li:last-child {
|
|
21
|
+
margin-bottom: 0;
|
|
22
|
+
}
|
|
21
23
|
`;
|
|
22
24
|
|
|
23
25
|
const Wrapper = styled.div<{ isOpen: boolean }>`
|
|
@@ -102,6 +104,23 @@ const BreadCrumbItem = styled.button<{ isLastItem: boolean }>`
|
|
|
102
104
|
}
|
|
103
105
|
`;
|
|
104
106
|
|
|
107
|
+
const ModalContent = styled.div`
|
|
108
|
+
width: 100%;
|
|
109
|
+
height: 100%;
|
|
110
|
+
overflow: auto;
|
|
111
|
+
|
|
112
|
+
::-webkit-scrollbar {
|
|
113
|
+
-webkit-appearance: none;
|
|
114
|
+
width: 4px;
|
|
115
|
+
height: 100%;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
::-webkit-scrollbar-thumb {
|
|
119
|
+
border-radius: 4px;
|
|
120
|
+
background-color: ${(p) => p.theme.color.iconNonActive};
|
|
121
|
+
}
|
|
122
|
+
`;
|
|
123
|
+
|
|
105
124
|
export {
|
|
106
125
|
Wrapper,
|
|
107
126
|
Content,
|
|
@@ -113,4 +132,5 @@ export {
|
|
|
113
132
|
ClosedFloatingButtonWrapper,
|
|
114
133
|
BreadCrumb,
|
|
115
134
|
BreadCrumbItem,
|
|
135
|
+
ModalContent
|
|
116
136
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, useRef } from "react";
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
|
-
import { RouteComponentProps } from "react-router-dom";
|
|
3
|
+
import type { RouteComponentProps } from "react-router-dom";
|
|
4
4
|
import { withErrorBoundary } from "react-error-boundary";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import type {
|
|
7
7
|
IErrorItem,
|
|
8
8
|
ILanguage,
|
|
9
9
|
INotification,
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
CancelScheduleModal,
|
|
24
24
|
ScheduleModal,
|
|
25
25
|
RestoreModal,
|
|
26
|
+
OcassionalToast,
|
|
26
27
|
} from "@ax/components";
|
|
27
28
|
import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
28
29
|
import { structuredDataActions } from "@ax/containers/StructuredData";
|
|
@@ -81,6 +82,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
81
82
|
const [isReadOnly, setIsReadOnly] = useState(false);
|
|
82
83
|
const [selectedTab, setSelectedTab] = useState(defaultTab);
|
|
83
84
|
const [notification, setNotification] = useState<INotification | null>(null);
|
|
85
|
+
const [toastMsg, setToastMsg] = useState<string | null>(null);
|
|
84
86
|
const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent, isNewTranslation);
|
|
85
87
|
const [errorPagesChecked, setErrorPagesChecked] = useState(false);
|
|
86
88
|
const [scheduleDate, setScheduleDate] = useState({ date: dateToString(new Date(), "yyy/MM/dd"), time: "12:00 am" });
|
|
@@ -92,7 +94,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
92
94
|
const isPublished = props.pageStatus === pageStatus.PUBLISHED || props.pageStatus === pageStatus.UPLOAD_PENDING;
|
|
93
95
|
const isDraft = props.pageStatus === pageStatus.MODIFIED || !!editorContent.draftFromPage;
|
|
94
96
|
const hasDraft: boolean = editorContent && !!editorContent.haveDraftPage;
|
|
95
|
-
const isLivePageChanged = editorContent
|
|
97
|
+
const isLivePageChanged = editorContent?.liveChanged;
|
|
96
98
|
const structuredData = editorContent ? editorContent.structuredData : "";
|
|
97
99
|
const isEditLive = isPublished && hasDraft;
|
|
98
100
|
const isScheduled = props.pageStatus === pageStatus.SCHEDULED;
|
|
@@ -117,10 +119,11 @@ const GlobalEditor = (props: IProps) => {
|
|
|
117
119
|
useEffect(() => {
|
|
118
120
|
const { pageID, getPage, setTab, sendPagePing, setStructuredDataFilter } = props;
|
|
119
121
|
|
|
120
|
-
editorContent
|
|
122
|
+
editorContent?.structuredData && setStructuredDataFilter(editorContent.structuredData);
|
|
121
123
|
const defaultTab = "content";
|
|
122
124
|
const handleGetPage = async () => await getPage(pageID, true);
|
|
123
125
|
setTab(defaultTab);
|
|
126
|
+
setToastMsg(null);
|
|
124
127
|
handleGetPage();
|
|
125
128
|
if (!pageID) {
|
|
126
129
|
setIsDirty(false);
|
|
@@ -186,7 +189,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
186
189
|
const isSaved =
|
|
187
190
|
pageID && !isNewTranslation
|
|
188
191
|
? await updatePageStatus([pageID], pageStatus.UPLOAD_PENDING)
|
|
189
|
-
: await savePage(
|
|
192
|
+
: await savePage({ publishPage});
|
|
190
193
|
|
|
191
194
|
if (isSaved) {
|
|
192
195
|
resetDirty();
|
|
@@ -206,7 +209,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
206
209
|
status: pageStatus.UPLOAD_PENDING,
|
|
207
210
|
};
|
|
208
211
|
|
|
209
|
-
const isSaved = await savePage(
|
|
212
|
+
const isSaved = await savePage({publishPage});
|
|
210
213
|
if (isSaved) {
|
|
211
214
|
resetDirty();
|
|
212
215
|
}
|
|
@@ -240,7 +243,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
240
243
|
const validated = skipReviewOnPublish ? true : await validatePage(true);
|
|
241
244
|
|
|
242
245
|
if (validated) {
|
|
243
|
-
const isSaved = await savePage(
|
|
246
|
+
const isSaved = await savePage({ publishDraft: true });
|
|
244
247
|
if (isSaved) resetDirty();
|
|
245
248
|
} else {
|
|
246
249
|
setNotification({ text: errorNotificationText, type: "error" });
|
|
@@ -262,7 +265,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
262
265
|
const handleCreateDraft = async () => {
|
|
263
266
|
const { savePage } = props;
|
|
264
267
|
|
|
265
|
-
const isSaved = await savePage(true);
|
|
268
|
+
const isSaved = await savePage({ createDraft: true});
|
|
266
269
|
if (isSaved) resetDirty();
|
|
267
270
|
};
|
|
268
271
|
|
|
@@ -378,13 +381,16 @@ const GlobalEditor = (props: IProps) => {
|
|
|
378
381
|
options: menuOptions,
|
|
379
382
|
};
|
|
380
383
|
|
|
381
|
-
const isEditable = editorContent
|
|
384
|
+
const isEditable = editorContent?.editable;
|
|
382
385
|
|
|
383
|
-
const handleSavePage = async () => {
|
|
386
|
+
const handleSavePage = async (setToast?: (msg: string) => void) => {
|
|
384
387
|
const { savePage } = props;
|
|
385
388
|
|
|
386
|
-
const isSaved = await savePage(
|
|
387
|
-
if (isSaved)
|
|
389
|
+
const isSaved = await savePage({setToast});
|
|
390
|
+
if (isSaved){
|
|
391
|
+
resetDirty();
|
|
392
|
+
setToastMsg(null);
|
|
393
|
+
}
|
|
388
394
|
};
|
|
389
395
|
|
|
390
396
|
const goToPages = (path: string) => setRoute(path);
|
|
@@ -504,10 +510,17 @@ const GlobalEditor = (props: IProps) => {
|
|
|
504
510
|
]
|
|
505
511
|
: [{ name: "view", text: "Preview mode" }];
|
|
506
512
|
|
|
513
|
+
const handleSelectedTab = async (tab: string) => {
|
|
514
|
+
if(tab === "view" && (!isPublished || isDraft)){
|
|
515
|
+
handleSavePage(setToastMsg);
|
|
516
|
+
}
|
|
517
|
+
setSelectedTab(tab);
|
|
518
|
+
}
|
|
519
|
+
|
|
507
520
|
const tabsPreview = {
|
|
508
521
|
icons: tabIcons,
|
|
509
522
|
selectedTab,
|
|
510
|
-
action: (tab: string) =>
|
|
523
|
+
action: (tab: string) => handleSelectedTab(tab),
|
|
511
524
|
};
|
|
512
525
|
|
|
513
526
|
const filteredLanguages = globalLangs.filter((lang) =>
|
|
@@ -627,7 +640,10 @@ const GlobalEditor = (props: IProps) => {
|
|
|
627
640
|
</S.Content>
|
|
628
641
|
</>
|
|
629
642
|
) : (
|
|
630
|
-
|
|
643
|
+
<>
|
|
644
|
+
<Preview theme={theme} />
|
|
645
|
+
{toastMsg?.length && <OcassionalToast icon="warning" message={toastMsg} />}
|
|
646
|
+
</>
|
|
631
647
|
)}
|
|
632
648
|
<Modal
|
|
633
649
|
isOpen={isOpen}
|
|
@@ -640,8 +656,8 @@ const GlobalEditor = (props: IProps) => {
|
|
|
640
656
|
{isOpen && (
|
|
641
657
|
<S.ModalContent>
|
|
642
658
|
<p>
|
|
643
|
-
<strong>{userEditing
|
|
644
|
-
the page but <strong>you cannot make changes to it</strong> until {userEditing
|
|
659
|
+
<strong>{userEditing?.name}</strong> is currently working on this page. You can preview
|
|
660
|
+
the page but <strong>you cannot make changes to it</strong> until {userEditing?.name}{" "}
|
|
645
661
|
leaves the page.
|
|
646
662
|
</p>
|
|
647
663
|
</S.ModalContent>
|
|
@@ -705,7 +721,7 @@ const mapStateToProps = (state: IRootState): IPageEditorStateProps => ({
|
|
|
705
721
|
sitePageID: state.pageEditor.sitePageID,
|
|
706
722
|
savedSiteInfo: state.sites.savedSiteInfo,
|
|
707
723
|
userEditing: state.pageEditor.userEditing,
|
|
708
|
-
currentUserID: state.users.currentUser
|
|
724
|
+
currentUserID: state.users.currentUser?.id || null,
|
|
709
725
|
isNewTranslation: state.pageEditor.isNewTranslation,
|
|
710
726
|
currentSiteErrorPages: state.sites.currentSiteErrorPages,
|
|
711
727
|
skipReviewOnPublish: state.app.globalSettings.skipReviewOnPublish,
|
|
@@ -756,7 +772,7 @@ const mapDispatchToProps = {
|
|
|
756
772
|
|
|
757
773
|
interface IPageEditorDispatchProps {
|
|
758
774
|
getPage(pageID?: number, global?: boolean): Promise<void>;
|
|
759
|
-
savePage(createDraft
|
|
775
|
+
savePage(data?: {createDraft?: boolean; publishPage?: { status: string }; publishDraft?: boolean; setToast?: (msg: string) => void}): Promise<boolean>;
|
|
760
776
|
deletePage(params?: ISavePageParams): Promise<boolean>;
|
|
761
777
|
validatePage(publish?: boolean): Promise<boolean>;
|
|
762
778
|
updatePageStatus(id: number[], status: string): Promise<boolean>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
3
|
import { useParams } from "react-router-dom";
|
|
4
4
|
|
|
5
|
-
import { IRootState } from "@ax/types";
|
|
5
|
+
import type { IRootState } from "@ax/types";
|
|
6
6
|
import { appActions } from "@ax/containers/App";
|
|
7
7
|
import { Login } from "@ax/components";
|
|
8
|
-
import { IGlobalSettings } from "@ax/containers/App/reducer";
|
|
8
|
+
import type { IGlobalSettings } from "@ax/containers/App/reducer";
|
|
9
9
|
import { errorText } from "./constants";
|
|
10
10
|
|
|
11
11
|
const LoginModule = (props: IProps) => {
|
|
@@ -49,7 +49,7 @@ const LoginModule = (props: IProps) => {
|
|
|
49
49
|
}, [petitionId, errorSSO]);
|
|
50
50
|
|
|
51
51
|
useEffect(() => {
|
|
52
|
-
const prefix = process.env.
|
|
52
|
+
const prefix = process.env.GRIDDO_SITE_TITLE || process.env.REACT_APP_SITE_TITLE || "";
|
|
53
53
|
document.title = `${prefix} ${globalSettings.welcomeText2}`;
|
|
54
54
|
}, [globalSettings]);
|
|
55
55
|
|
|
@@ -110,7 +110,6 @@ const LoginModule = (props: IProps) => {
|
|
|
110
110
|
|
|
111
111
|
interface IProps {
|
|
112
112
|
isLoggingIn: boolean;
|
|
113
|
-
children: any;
|
|
114
113
|
globalSettings: IGlobalSettings;
|
|
115
114
|
login(email?: string, password?: string, rememberMe?: boolean, petitionId?: string): Promise<boolean>;
|
|
116
115
|
loginSSO(): Promise<string | null>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useState, useRef } from "react";
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
|
-
import { RouteComponentProps } from "react-router-dom";
|
|
3
|
+
import type { RouteComponentProps } from "react-router-dom";
|
|
4
4
|
import { withErrorBoundary } from "react-error-boundary";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import type {
|
|
7
7
|
IErrorItem,
|
|
8
8
|
INotification,
|
|
9
9
|
IRootState,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
CancelScheduleModal,
|
|
23
23
|
ScheduleModal,
|
|
24
24
|
RestoreModal,
|
|
25
|
+
OcassionalToast,
|
|
25
26
|
} from "@ax/components";
|
|
26
27
|
import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
27
28
|
import { appActions } from "@ax/containers/App";
|
|
@@ -82,6 +83,7 @@ const PageEditor = (props: IProps) => {
|
|
|
82
83
|
const [isReadOnly, setIsReadOnly] = useState(false);
|
|
83
84
|
const [selectedTab, setSelectedTab] = useState(defaultTab);
|
|
84
85
|
const [notification, setNotification] = useState<INotification | null>(null);
|
|
86
|
+
const [toastMsg, setToastMsg] = useState<string | null>(null);
|
|
85
87
|
const [scheduleDate, setScheduleDate] = useState({ date: dateToString(new Date(), "yyy/MM/dd"), time: "12:00 am" });
|
|
86
88
|
const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent, isNewTranslation);
|
|
87
89
|
const { isOpen, toggleModal } = useModal();
|
|
@@ -94,16 +96,16 @@ const PageEditor = (props: IProps) => {
|
|
|
94
96
|
const browserRef = useRef<HTMLDivElement>(null);
|
|
95
97
|
|
|
96
98
|
const isGlobal = editorContent && editorContent.origin === "GLOBAL";
|
|
97
|
-
const isEditable = editorContent
|
|
99
|
+
const isEditable = editorContent?.editable;
|
|
98
100
|
const isPublished = props.pageStatus === pageStatus.PUBLISHED || props.pageStatus === pageStatus.UPLOAD_PENDING;
|
|
99
101
|
const isDraft = props.pageStatus === pageStatus.MODIFIED || !!editorContent.draftFromPage;
|
|
100
|
-
const hasDraft = editorContent
|
|
101
|
-
const isLivePageChanged = editorContent
|
|
102
|
+
const hasDraft = editorContent?.haveDraftPage;
|
|
103
|
+
const isLivePageChanged = editorContent?.liveChanged;
|
|
102
104
|
const isTranslated = pageLanguages.length > 1;
|
|
103
105
|
const structuredData = editorContent ? editorContent.structuredData : "";
|
|
104
106
|
const isEditLive = isPublished && hasDraft;
|
|
105
107
|
const isAllowedToDelete = (isPublished && isAllowedToDeletePublishedPage) || (!isPublished && isAllowedToDeletePage);
|
|
106
|
-
const canBeUnpublished = editorContent
|
|
108
|
+
const canBeUnpublished = editorContent?.canBeUnpublished;
|
|
107
109
|
const deleteHelpText = !canBeUnpublished ? "This is the canonical site of the page. You cannot unpublish it." : null;
|
|
108
110
|
const isScheduled = !!editorContent && !!editorContent.publicationScheduled;
|
|
109
111
|
const isDeleted = editorContent.deleted;
|
|
@@ -127,6 +129,7 @@ const PageEditor = (props: IProps) => {
|
|
|
127
129
|
|
|
128
130
|
setTab(defaultTab);
|
|
129
131
|
resetDirty();
|
|
132
|
+
setToastMsg(null);
|
|
130
133
|
handleGetPage();
|
|
131
134
|
|
|
132
135
|
if (!pageID) {
|
|
@@ -190,7 +193,7 @@ const PageEditor = (props: IProps) => {
|
|
|
190
193
|
const isSaved =
|
|
191
194
|
pageID && !isNewTranslation
|
|
192
195
|
? await updatePageStatus([pageID], pageStatus.UPLOAD_PENDING)
|
|
193
|
-
: await savePage(
|
|
196
|
+
: await savePage({publishPage});
|
|
194
197
|
|
|
195
198
|
if (isSaved) {
|
|
196
199
|
resetDirty();
|
|
@@ -210,7 +213,7 @@ const PageEditor = (props: IProps) => {
|
|
|
210
213
|
status: pageStatus.UPLOAD_PENDING,
|
|
211
214
|
};
|
|
212
215
|
|
|
213
|
-
const isSaved = await savePage(
|
|
216
|
+
const isSaved = await savePage({publishPage});
|
|
214
217
|
if (isSaved) {
|
|
215
218
|
resetDirty();
|
|
216
219
|
}
|
|
@@ -244,7 +247,7 @@ const PageEditor = (props: IProps) => {
|
|
|
244
247
|
const validated = skipReviewOnPublish ? true : await validatePage(true);
|
|
245
248
|
|
|
246
249
|
if (validated) {
|
|
247
|
-
const isSaved = await savePage(
|
|
250
|
+
const isSaved = await savePage({publishDraft: true});
|
|
248
251
|
if (isSaved) resetDirty();
|
|
249
252
|
} else {
|
|
250
253
|
setNotification({ text: errorNotificationText, type: "error" });
|
|
@@ -267,7 +270,7 @@ const PageEditor = (props: IProps) => {
|
|
|
267
270
|
const handleCreateDraft = async () => {
|
|
268
271
|
const { savePage } = props;
|
|
269
272
|
|
|
270
|
-
const isSaved = await savePage(true);
|
|
273
|
+
const isSaved = await savePage({createDraft: true});
|
|
271
274
|
if (isSaved) resetDirty();
|
|
272
275
|
};
|
|
273
276
|
|
|
@@ -386,11 +389,14 @@ const PageEditor = (props: IProps) => {
|
|
|
386
389
|
options: !isGlobal ? menuOptions : [],
|
|
387
390
|
};
|
|
388
391
|
|
|
389
|
-
const handleSavePage = async () => {
|
|
392
|
+
const handleSavePage = async (setToast?: (msg: string) => void) => {
|
|
390
393
|
const { savePage } = props;
|
|
391
394
|
|
|
392
|
-
const isSaved = await savePage(
|
|
393
|
-
if (isSaved)
|
|
395
|
+
const isSaved = await savePage({ setToast });
|
|
396
|
+
if (isSaved) {
|
|
397
|
+
resetDirty();
|
|
398
|
+
setToastMsg(null);
|
|
399
|
+
}
|
|
394
400
|
};
|
|
395
401
|
|
|
396
402
|
const goToPages = (path: string) => setRoute(path);
|
|
@@ -412,7 +418,7 @@ const PageEditor = (props: IProps) => {
|
|
|
412
418
|
let isTemplateActivated = true;
|
|
413
419
|
let hasDeactivatedModules = false;
|
|
414
420
|
let deactivatedModules: string[] = [];
|
|
415
|
-
if (editorContent
|
|
421
|
+
if (editorContent?.template) {
|
|
416
422
|
const editorTemplate = editorContent.template;
|
|
417
423
|
const mainContentModules = editorTemplate?.mainContent?.modules;
|
|
418
424
|
|
|
@@ -423,7 +429,7 @@ const PageEditor = (props: IProps) => {
|
|
|
423
429
|
hasDeactivatedModules = isModuleDisabled(selectedComponent, schema.schemaType, activatedModules);
|
|
424
430
|
}
|
|
425
431
|
|
|
426
|
-
isTemplateActivated = activatedTemplates.find((temp) => temp.id === editorTemplate.templateType)
|
|
432
|
+
isTemplateActivated = !!activatedTemplates.find((temp) => temp.id === editorTemplate.templateType);
|
|
427
433
|
}
|
|
428
434
|
|
|
429
435
|
let availableLanguages = siteLanguages;
|
|
@@ -505,7 +511,7 @@ const PageEditor = (props: IProps) => {
|
|
|
505
511
|
"This content is part of disabled content type package. To edit it, you must first activate it.";
|
|
506
512
|
|
|
507
513
|
const handleClickNotification = () => {
|
|
508
|
-
if (editorContent
|
|
514
|
+
if (editorContent?.template) {
|
|
509
515
|
const editorTemplate = editorContent.template;
|
|
510
516
|
if (!isTemplateActivated) {
|
|
511
517
|
getSiteDataPackbyTemplate(editorTemplate.templateType);
|
|
@@ -554,10 +560,17 @@ const PageEditor = (props: IProps) => {
|
|
|
554
560
|
]
|
|
555
561
|
: [{ name: "view", text: "Preview mode" }];
|
|
556
562
|
|
|
563
|
+
const handleSelectedTab = async (tab: string) => {
|
|
564
|
+
if(tab === "view" && (!isPublished || isDraft)){
|
|
565
|
+
handleSavePage(setToastMsg);
|
|
566
|
+
}
|
|
567
|
+
setSelectedTab(tab);
|
|
568
|
+
}
|
|
569
|
+
|
|
557
570
|
const tabsPreview = {
|
|
558
571
|
icons: tabIcons,
|
|
559
572
|
selectedTab,
|
|
560
|
-
action: (tab: string) =>
|
|
573
|
+
action: (tab: string) => handleSelectedTab(tab),
|
|
561
574
|
};
|
|
562
575
|
|
|
563
576
|
const contentLanguages: ILanguage[] = [];
|
|
@@ -680,8 +693,12 @@ const PageEditor = (props: IProps) => {
|
|
|
680
693
|
</S.Content>
|
|
681
694
|
</>
|
|
682
695
|
) : (
|
|
683
|
-
|
|
696
|
+
<>
|
|
697
|
+
<Preview />
|
|
698
|
+
{toastMsg?.length && <OcassionalToast icon="warning" message={toastMsg} />}
|
|
699
|
+
</>
|
|
684
700
|
)}
|
|
701
|
+
|
|
685
702
|
<Modal
|
|
686
703
|
isOpen={isOpen}
|
|
687
704
|
hide={toggleModal}
|
|
@@ -693,8 +710,8 @@ const PageEditor = (props: IProps) => {
|
|
|
693
710
|
{isOpen && (
|
|
694
711
|
<S.ModalContent>
|
|
695
712
|
<p>
|
|
696
|
-
<strong>{userEditing
|
|
697
|
-
the page but <strong>you cannot make changes to it</strong> until {userEditing
|
|
713
|
+
<strong>{userEditing?.name}</strong> is currently working on this page. You can preview
|
|
714
|
+
the page but <strong>you cannot make changes to it</strong> until {userEditing?.name}{" "}
|
|
698
715
|
leaves the page.
|
|
699
716
|
</p>
|
|
700
717
|
</S.ModalContent>
|
|
@@ -767,7 +784,7 @@ const mapStateToProps = (state: IRootState): IPageEditorStateProps => ({
|
|
|
767
784
|
activatedModules: state.dataPacks.modules,
|
|
768
785
|
errors: state.pageEditor.errors,
|
|
769
786
|
userEditing: state.pageEditor.userEditing,
|
|
770
|
-
currentUserID: state.users.currentUser
|
|
787
|
+
currentUserID: state.users.currentUser?.id || null,
|
|
771
788
|
isNewTranslation: state.pageEditor.isNewTranslation,
|
|
772
789
|
currentSiteErrorPages: state.sites.currentSiteErrorPages,
|
|
773
790
|
skipReviewOnPublish: state.app.globalSettings.skipReviewOnPublish,
|
|
@@ -821,7 +838,7 @@ const mapDispatchToProps = {
|
|
|
821
838
|
|
|
822
839
|
interface IPageEditorDispatchProps {
|
|
823
840
|
getPage(pageID?: number): Promise<void>;
|
|
824
|
-
savePage(createDraft
|
|
841
|
+
savePage(data?: { createDraft?: boolean; publishPage?: { status: string }; publishDraft?: boolean; setToast?: (msg: string) => void }): Promise<boolean>;
|
|
825
842
|
deletePage(params?: ISavePageParams): Promise<boolean>;
|
|
826
843
|
validatePage(publish?: boolean): Promise<boolean>;
|
|
827
844
|
updatePageStatus(id: number[], status: string): Promise<boolean>;
|
package/src/themes/theme.json
CHANGED
|
@@ -226,6 +226,12 @@
|
|
|
226
226
|
"warning": {
|
|
227
227
|
"description": "warning modified",
|
|
228
228
|
"value": "#ffbb37"
|
|
229
|
+
},
|
|
230
|
+
"borderInverse": {
|
|
231
|
+
"description": "Border for ocassional toasts",
|
|
232
|
+
"type": "RGB",
|
|
233
|
+
"value": ["219", "221", "233"],
|
|
234
|
+
"opacity": "0.3"
|
|
229
235
|
}
|
|
230
236
|
}
|
|
231
237
|
},
|