@griddo/ax 11.10.18 → 11.10.20
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 +42 -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.20",
|
|
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": "cc4cb47324099928259991fc63e6a516679c83db"
|
|
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,19 @@ 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 || {};
|
|
423
|
+
|
|
424
|
+
console.log(setToast, "setToast");
|
|
425
|
+
|
|
420
426
|
const {
|
|
421
427
|
pageEditor: {
|
|
422
428
|
selectedEditorID,
|
|
@@ -449,20 +455,20 @@ function savePage(
|
|
|
449
455
|
}
|
|
450
456
|
|
|
451
457
|
if (createDraft) {
|
|
452
|
-
values
|
|
458
|
+
values.draftFromPage = values.id;
|
|
453
459
|
delete values.id;
|
|
454
460
|
}
|
|
455
461
|
|
|
456
462
|
/* remove header and footer if page should get the default */
|
|
457
463
|
const { defaultHeader, defaultFooter } =
|
|
458
|
-
(templateConfig
|
|
464
|
+
(templateConfig?.templates?.[template.templateType]) || {};
|
|
459
465
|
|
|
460
466
|
if (header && ((header.setAsDefault && !defaultHeader) || header.id === defaultHeader)) {
|
|
461
|
-
values
|
|
467
|
+
values.header = null;
|
|
462
468
|
}
|
|
463
469
|
|
|
464
470
|
if (footer && ((footer.setAsDefault && !defaultFooter) || footer.id === defaultFooter)) {
|
|
465
|
-
values
|
|
471
|
+
values.footer = null;
|
|
466
472
|
}
|
|
467
473
|
|
|
468
474
|
const saveResponse =
|
|
@@ -503,7 +509,11 @@ function savePage(
|
|
|
503
509
|
dispatch(setIsSaving(false));
|
|
504
510
|
return true;
|
|
505
511
|
} else {
|
|
506
|
-
|
|
512
|
+
if(typeof setToast === 'function'){
|
|
513
|
+
setToast(saveResponse.data.message);
|
|
514
|
+
} else {
|
|
515
|
+
appActions.handleError(saveResponse)(dispatch);
|
|
516
|
+
}
|
|
507
517
|
dispatch(setIsSaving(false));
|
|
508
518
|
return false;
|
|
509
519
|
}
|
|
@@ -738,7 +748,7 @@ function addComponent(
|
|
|
738
748
|
|
|
739
749
|
generatePageContent(updatedPageContent)(dispatch, getState);
|
|
740
750
|
|
|
741
|
-
if (typeof type === "object" && Object.
|
|
751
|
+
if (typeof type === "object" && Object.hasOwn(type, "editorID")) {
|
|
742
752
|
setSelectedContent(type.editorID)(dispatch, getState);
|
|
743
753
|
} else {
|
|
744
754
|
const { sections: generatedSections } = getStateValues(getState);
|
|
@@ -804,7 +814,7 @@ function addModule(
|
|
|
804
814
|
type,
|
|
805
815
|
};
|
|
806
816
|
|
|
807
|
-
let updatedSections,
|
|
817
|
+
let updatedSections: any,
|
|
808
818
|
updatedSectionIndex = 0;
|
|
809
819
|
|
|
810
820
|
if (isComponentModule) {
|
|
@@ -1068,7 +1078,7 @@ function pasteModule(
|
|
|
1068
1078
|
if (isReqOk(response.status)) {
|
|
1069
1079
|
const validDataIds: number[] = [];
|
|
1070
1080
|
response.data.items.forEach(
|
|
1071
|
-
(data: IStructuredDataContent) => data.relatedSite && validDataIds.push(data.id),
|
|
1081
|
+
(data: IStructuredDataContent) => { data.relatedSite && validDataIds.push(data.id) },
|
|
1072
1082
|
);
|
|
1073
1083
|
const hasInvalidData = validDataIds.length < fixed.length;
|
|
1074
1084
|
validatedModuleCopy.data.fixed = validDataIds;
|
|
@@ -1111,7 +1121,11 @@ function overwriteHeaderConfig(params: IFieldProps): (dispatch: Dispatch, getSta
|
|
|
1111
1121
|
const updatedContent = deepClone(content);
|
|
1112
1122
|
const { headerConfig } = updatedContent.editorContent;
|
|
1113
1123
|
|
|
1114
|
-
|
|
1124
|
+
if(headerConfig[id]){
|
|
1125
|
+
headerConfig[id][key] = value
|
|
1126
|
+
} else {
|
|
1127
|
+
headerConfig[id] = { [key]: value }
|
|
1128
|
+
}
|
|
1115
1129
|
|
|
1116
1130
|
dispatch(setEditorContent({ ...updatedContent }));
|
|
1117
1131
|
};
|
|
@@ -1126,7 +1140,7 @@ function generatePageContent(editorContent: IPage): (dispatch: Dispatch, getStat
|
|
|
1126
1140
|
} = getState();
|
|
1127
1141
|
|
|
1128
1142
|
const { header, footer, isGlobal } = editorContent;
|
|
1129
|
-
const { defaultHeader, defaultFooter } = (configFormData?.templates
|
|
1143
|
+
const { defaultHeader, defaultFooter } = (configFormData?.templates?.[template]) || {};
|
|
1130
1144
|
|
|
1131
1145
|
const headerID =
|
|
1132
1146
|
header === null || header === undefined ? defaultHeader : typeof header === "object" ? header.id : header;
|
|
@@ -1333,7 +1347,7 @@ function getTemplateConfig(template: string): (dispatch: Dispatch, getState: ()
|
|
|
1333
1347
|
const {
|
|
1334
1348
|
sites: { currentSiteInfo },
|
|
1335
1349
|
} = getState();
|
|
1336
|
-
const currentSiteID = currentSiteInfo
|
|
1350
|
+
const currentSiteID = currentSiteInfo?.id || null;
|
|
1337
1351
|
const responseActions = {
|
|
1338
1352
|
handleSuccess: (data: any) => dispatch(setTemplateConfig(data)),
|
|
1339
1353
|
handleError: () => console.log("Error en getTemplateConfig"),
|
|
@@ -1381,7 +1395,7 @@ function validatePage(publish?: boolean): (dispatch: Dispatch, getState: () => I
|
|
|
1381
1395
|
const fieldErrors = findFieldsErrors(content);
|
|
1382
1396
|
|
|
1383
1397
|
errors = [...errors, ...fieldErrors];
|
|
1384
|
-
let packagesActivationErrors;
|
|
1398
|
+
let packagesActivationErrors: any;
|
|
1385
1399
|
if (!isGlobalPage) {
|
|
1386
1400
|
packagesActivationErrors = findPackagesActivationErrors(pageEditor, modules, templates);
|
|
1387
1401
|
}
|
|
@@ -1541,8 +1555,8 @@ function getPageSummary(): (dispatch: Dispatch, getState: () => IRootState) => P
|
|
|
1541
1555
|
const responseActions = {
|
|
1542
1556
|
handleSuccess: (data: { summary: string; keywords: string[] }) => {
|
|
1543
1557
|
const content = deepClone(editorContent);
|
|
1544
|
-
content
|
|
1545
|
-
content
|
|
1558
|
+
content.metaDescription = data.summary;
|
|
1559
|
+
content.metaKeywords = data.keywords;
|
|
1546
1560
|
generatePageContent(content)(dispatch, getState);
|
|
1547
1561
|
},
|
|
1548
1562
|
handleError: () => console.log("Error en GetPageSummary"),
|
|
@@ -1567,7 +1581,7 @@ function getPageTranslation(langID: number): (dispatch: Dispatch, getState: () =
|
|
|
1567
1581
|
|
|
1568
1582
|
const responseActions = {
|
|
1569
1583
|
handleSuccess: (data: IPage) => {
|
|
1570
|
-
data
|
|
1584
|
+
data.canBeTranslated = false;
|
|
1571
1585
|
generatePageContent(data)(dispatch, getState);
|
|
1572
1586
|
dispatch(setCurrentPageName(data.title));
|
|
1573
1587
|
dispatch(setIsIATranslated(true));
|
|
@@ -1609,7 +1623,7 @@ function schedulePublication(
|
|
|
1609
1623
|
const updatedEditorContent = { ...editorContent, publicationScheduled: date };
|
|
1610
1624
|
|
|
1611
1625
|
dispatch(setEditorContent(updatedEditorContent));
|
|
1612
|
-
return await savePage(false, { status })(dispatch, getState);
|
|
1626
|
+
return await savePage({createDraft: false, publishPage: { status }})(dispatch, getState);
|
|
1613
1627
|
} catch (e) {
|
|
1614
1628
|
console.log("Error", e);
|
|
1615
1629
|
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
|
},
|