@griddo/ax 12.4.0 → 12.5.1
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/config/__tests__/ModulePathInMessagesPlugin.test.js +93 -0
- package/config/webpack/ModulePathInMessagesPlugin.js +47 -0
- package/config/webpack.config.js +10 -0
- package/package.json +2 -2
- package/src/__tests__/components/ConfigPanel/Form/Form.test.tsx +47 -57
- package/src/__tests__/components/ConfigPanel/Header/Header.test.tsx +46 -42
- package/src/__tests__/components/ElementsTooltip/ElementsTooltip.test.tsx +6 -17
- package/src/__tests__/components/EmptyState/EmptyState.test.tsx +9 -9
- package/src/__tests__/components/Fields/AsyncSelect/AsyncSelect.test.tsx +60 -50
- package/src/__tests__/components/Fields/ColorPicker/ColorPicker.test.tsx +66 -58
- package/src/__tests__/components/Fields/ComponentContainer/ComponentContainer.test.tsx +222 -425
- package/src/__tests__/components/Fields/FileField/FileField.test.tsx +13 -14
- package/src/__tests__/components/Fields/ImageField/ImageField.test.tsx +275 -259
- package/src/__tests__/components/Fields/IntegrationsField/IntegrationsField.test.tsx +50 -48
- package/src/__tests__/components/Fields/NoteField/NoteField.test.tsx +9 -8
- package/src/__tests__/components/Fields/NumberField/NumberField.test.tsx +8 -12
- package/src/__tests__/components/Fields/TextArea/TextArea.test.tsx +6 -2
- package/src/__tests__/components/Fields/ToggleField/ToggleField.test.tsx +15 -23
- package/src/__tests__/components/Fields/Tooltip/Tooltip.test.tsx +8 -2
- package/src/__tests__/components/Fields/VisualUniqueSelection/VisualUniqueSelection.test.tsx +29 -21
- package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.config.test.tsx +103 -0
- package/src/__tests__/components/FieldsBehavior/FieldsBehavior.test.tsx +7 -2
- package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +8 -6
- package/src/__tests__/components/SideModal/SideModal.test.tsx +150 -104
- package/src/__tests__/components/TableFilters/LiveFilter/LiveFilter.test.tsx +15 -29
- package/src/__tests__/components/TableFilters/StatusFilter/StatusFilter.test.tsx +61 -36
- package/src/__tests__/components/Tabs/Tabs.test.tsx +65 -61
- package/src/__tests__/components/Toast/Toast.test.tsx +19 -21
- package/src/__tests__/modules/FramePreview/FramePreview.test.tsx +16 -0
- package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +24 -19
- package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +15 -6
- package/src/__tests__/modules/Users/Roles/BulkHeader/BulkHeader.test.tsx +69 -64
- package/src/__tests__/modules/Users/Roles/Roles.test.tsx +7 -4
- package/src/components/Fields/Wysiwyg/config.tsx +68 -17
- package/src/components/Fields/Wysiwyg/helpers.tsx +2 -0
- package/src/components/Fields/Wysiwyg/index.tsx +41 -10
- package/src/components/ResizePanel/index.tsx +7 -2
|
@@ -17,28 +17,35 @@ import { defaultStore } from "./../../../../../../__mocks__/store/GenericStore";
|
|
|
17
17
|
afterEach(cleanup);
|
|
18
18
|
|
|
19
19
|
const store = configureStore([thunk])(defaultStore);
|
|
20
|
-
|
|
20
|
+
// El escenario base es el que el primer test dejaba en el objeto compartido y
|
|
21
|
+
// del que el segundo dependía sin declararlo.
|
|
22
|
+
const makeProps = (overrides: Partial<IBulkHeaderProps> = {}): IBulkHeaderProps =>
|
|
23
|
+
Object.assign(
|
|
24
|
+
mock<IBulkHeaderProps>(),
|
|
25
|
+
{
|
|
26
|
+
showBulk: false,
|
|
27
|
+
checkState: { isAllSelected: false, indeterminate: false },
|
|
28
|
+
filterValues: {
|
|
29
|
+
order: [{ value: "lastAccess-desc", label: "lastAccess-desc" }],
|
|
30
|
+
liveStatus: [{ value: "all", label: "All" }],
|
|
31
|
+
},
|
|
32
|
+
sortedListStatus: {
|
|
33
|
+
isAscending: false,
|
|
34
|
+
sortedByTitle: false,
|
|
35
|
+
sortedByLastAccess: true,
|
|
36
|
+
sortedByDateCreated: false,
|
|
37
|
+
},
|
|
38
|
+
totalItems: 22,
|
|
39
|
+
},
|
|
40
|
+
overrides,
|
|
41
|
+
);
|
|
21
42
|
|
|
22
43
|
describe("BulkHeader component rendering", () => {
|
|
23
44
|
it("should render table header", async () => {
|
|
24
|
-
defaultProps.showBulk = false;
|
|
25
|
-
defaultProps.checkState = { isAllSelected: false, indeterminate: false };
|
|
26
|
-
defaultProps.filterValues = {
|
|
27
|
-
order: [{ value: "lastAccess-desc", label: "lastAccess-desc" }],
|
|
28
|
-
liveStatus: [{ value: "all", label: "All" }],
|
|
29
|
-
};
|
|
30
|
-
defaultProps.sortedListStatus = {
|
|
31
|
-
isAscending: false,
|
|
32
|
-
sortedByTitle: false,
|
|
33
|
-
sortedByLastAccess: true,
|
|
34
|
-
sortedByDateCreated: false,
|
|
35
|
-
};
|
|
36
|
-
defaultProps.totalItems = 22;
|
|
37
|
-
|
|
38
45
|
await act(async () => {
|
|
39
46
|
render(
|
|
40
47
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
41
|
-
<BulkHeader {...
|
|
48
|
+
<BulkHeader {...makeProps({ showBulk: false })} />
|
|
42
49
|
</ThemeProvider>,
|
|
43
50
|
{ store },
|
|
44
51
|
);
|
|
@@ -48,12 +55,10 @@ describe("BulkHeader component rendering", () => {
|
|
|
48
55
|
});
|
|
49
56
|
|
|
50
57
|
it("should render bulk header", async () => {
|
|
51
|
-
defaultProps.showBulk = true;
|
|
52
|
-
|
|
53
58
|
await act(async () => {
|
|
54
59
|
render(
|
|
55
60
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
56
|
-
<BulkHeader {...
|
|
61
|
+
<BulkHeader {...makeProps({ showBulk: true })} />
|
|
57
62
|
</ThemeProvider>,
|
|
58
63
|
{ store },
|
|
59
64
|
);
|
|
@@ -12,7 +12,7 @@ import configureStore from "redux-mock-store";
|
|
|
12
12
|
import thunk from "redux-thunk";
|
|
13
13
|
import { ThemeProvider } from "styled-components";
|
|
14
14
|
|
|
15
|
-
import { act, cleanup, fireEvent, render, screen, waitFor } from "./../../../../../config/tests/test-utils";
|
|
15
|
+
import { act, cleanup, fireEvent, render, screen, waitFor, within } from "./../../../../../config/tests/test-utils";
|
|
16
16
|
import { sitesResponse } from "./../../../../__mocks__/axios/SitesList";
|
|
17
17
|
import { sitesOptions } from "./../../../../__mocks__/axios/SitesListOptions";
|
|
18
18
|
import { sitesDataMock, sitesDataNoThumbnailMock, userDataMock } from "./../../../../__mocks__/store/SitesList";
|
|
@@ -59,6 +59,15 @@ const renderSitesList = async (storeData: any = initialStore) => {
|
|
|
59
59
|
|
|
60
60
|
beforeEach(() => {
|
|
61
61
|
mockedAxios.mockResolvedValue(sitesResponse);
|
|
62
|
+
// Tres cosas sobreviven al test que las toca y hacían que el resto solo
|
|
63
|
+
// funcionase en un orden concreto: el `history` de @ax/routes es un
|
|
64
|
+
// singleton y los tests de navegación lo dejaban en /sites/pages; SitesList
|
|
65
|
+
// guarda el modo de vista en localStorage, así que el siguiente test
|
|
66
|
+
// arrancaba en la vista equivocada y no encontraba los testids `grid-*`; y
|
|
67
|
+
// el parche de scrollTo se hacía sobre el prototipo dentro de dos tests.
|
|
68
|
+
history.replace("/");
|
|
69
|
+
localStorage.clear();
|
|
70
|
+
window.HTMLElement.prototype.scrollTo = vi.fn();
|
|
62
71
|
});
|
|
63
72
|
|
|
64
73
|
describe("Sites module rendering", () => {
|
|
@@ -198,8 +207,6 @@ describe("Sites module events", () => {
|
|
|
198
207
|
});
|
|
199
208
|
|
|
200
209
|
it("should go to site on click recent site item", async () => {
|
|
201
|
-
window.HTMLElement.prototype.scrollTo = vi.fn();
|
|
202
|
-
|
|
203
210
|
await renderSitesList();
|
|
204
211
|
|
|
205
212
|
const recentSiteItems = screen.getAllByTestId("recent-sites-item");
|
|
@@ -224,8 +231,6 @@ describe("Sites module events", () => {
|
|
|
224
231
|
});
|
|
225
232
|
|
|
226
233
|
it("should go to site on click list site item", async () => {
|
|
227
|
-
window.HTMLElement.prototype.scrollTo = vi.fn();
|
|
228
|
-
|
|
229
234
|
await renderSitesList();
|
|
230
235
|
|
|
231
236
|
const gridIconAction = screen.getByTestId("icon-action-Grid2");
|
|
@@ -238,7 +243,11 @@ describe("Sites module events", () => {
|
|
|
238
243
|
const listSitesItems = screen.getAllByTestId("list-site-item");
|
|
239
244
|
expect(listSitesItems.length).toEqual(2);
|
|
240
245
|
|
|
241
|
-
|
|
246
|
+
// La fila solo tiene onContextMenu; navegan las celdas. Antes se pulsaba la
|
|
247
|
+
// fila y no pasaba nada: la aserción se cumplía porque otro test había
|
|
248
|
+
// dejado ya el history en /sites/pages.
|
|
249
|
+
const nameCell = within(listSitesItems[0]).getAllByRole("cell")[2];
|
|
250
|
+
fireEvent.click(nameCell);
|
|
242
251
|
await waitFor(() => expect(history.location.pathname).toEqual("/sites/pages"));
|
|
243
252
|
});
|
|
244
253
|
|
|
@@ -13,49 +13,53 @@ import globalTheme from "@ax/themes/theme.json";
|
|
|
13
13
|
|
|
14
14
|
afterEach(cleanup);
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
]
|
|
16
|
+
const makeProps = (overrides: Partial<IBulkHeaderProps> = {}): IBulkHeaderProps =>
|
|
17
|
+
Object.assign(
|
|
18
|
+
mock<IBulkHeaderProps>(),
|
|
19
|
+
{
|
|
20
|
+
selectedRoles: [],
|
|
21
|
+
checkState: { isAllSelected: false },
|
|
22
|
+
roles: [
|
|
23
|
+
{
|
|
24
|
+
id: 1,
|
|
25
|
+
name: "Administrator",
|
|
26
|
+
hex: "#C7ECF8",
|
|
27
|
+
description:
|
|
28
|
+
"You can manage all of your site's settings, users and roles. You can create and publish all pages of your site.",
|
|
29
|
+
permissions: { totalPermissions: "78/78", sitePermissions: [], globalPermissions: [] },
|
|
30
|
+
active: true,
|
|
31
|
+
users: [],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 2,
|
|
35
|
+
name: "Constructor",
|
|
36
|
+
hex: "#B5FBFF",
|
|
37
|
+
description:
|
|
38
|
+
"You can edit your website, add pages, modules, modify URLs, menus, colours and themes, html tags. You don't manage users, or manage the site configuration.",
|
|
39
|
+
permissions: { totalPermissions: "49/78", sitePermissions: [], globalPermissions: [] },
|
|
40
|
+
active: true,
|
|
41
|
+
users: [],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 3,
|
|
45
|
+
name: "Editor",
|
|
46
|
+
hex: "#E2EE9F",
|
|
47
|
+
description: "You have permission to view, add and edit site content, such as pages and blocks.",
|
|
48
|
+
permissions: { totalPermissions: "21/78", sitePermissions: [], globalPermissions: [] },
|
|
49
|
+
active: false,
|
|
50
|
+
users: [],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
overrides,
|
|
55
|
+
);
|
|
49
56
|
|
|
50
57
|
describe("BulkHeader component rendering", () => {
|
|
51
58
|
it("should render bulk header", async () => {
|
|
52
|
-
defaultProps.showBulk = true;
|
|
53
|
-
defaultProps.checkState.isAllSelected = true;
|
|
54
|
-
|
|
55
59
|
await act(async () => {
|
|
56
60
|
render(
|
|
57
61
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
58
|
-
<BulkHeader {...
|
|
62
|
+
<BulkHeader {...makeProps({ showBulk: true, checkState: { isAllSelected: true } })} />
|
|
59
63
|
</ThemeProvider>,
|
|
60
64
|
);
|
|
61
65
|
});
|
|
@@ -64,13 +68,10 @@ describe("BulkHeader component rendering", () => {
|
|
|
64
68
|
});
|
|
65
69
|
|
|
66
70
|
it("should render bulk header options", async () => {
|
|
67
|
-
defaultProps.showBulk = true;
|
|
68
|
-
defaultProps.checkState.isAllSelected = true;
|
|
69
|
-
|
|
70
71
|
await act(async () => {
|
|
71
72
|
render(
|
|
72
73
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
73
|
-
<BulkHeader {...
|
|
74
|
+
<BulkHeader {...makeProps({ showBulk: true, checkState: { isAllSelected: true } })} />
|
|
74
75
|
</ThemeProvider>,
|
|
75
76
|
);
|
|
76
77
|
});
|
|
@@ -79,14 +80,16 @@ describe("BulkHeader component rendering", () => {
|
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
it("should render underline when isScrolling ", async () => {
|
|
82
|
-
defaultProps.showBulk = false;
|
|
83
|
-
defaultProps.isScrolling = true;
|
|
84
|
-
defaultProps.sortedListStatus = { isAscending: false, sortedByTitle: false };
|
|
85
|
-
|
|
86
83
|
await act(async () => {
|
|
87
84
|
render(
|
|
88
85
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
89
|
-
<BulkHeader
|
|
86
|
+
<BulkHeader
|
|
87
|
+
{...makeProps({
|
|
88
|
+
showBulk: false,
|
|
89
|
+
isScrolling: true,
|
|
90
|
+
sortedListStatus: { isAscending: false, sortedByTitle: false },
|
|
91
|
+
})}
|
|
92
|
+
/>
|
|
90
93
|
</ThemeProvider>,
|
|
91
94
|
);
|
|
92
95
|
});
|
|
@@ -95,14 +98,16 @@ describe("BulkHeader component rendering", () => {
|
|
|
95
98
|
});
|
|
96
99
|
|
|
97
100
|
it("should render users filter when isSiteView ", async () => {
|
|
98
|
-
defaultProps.showBulk = false;
|
|
99
|
-
defaultProps.sortedListStatus = { isAscending: false, sortedByTitle: false, sortedByUsers: false };
|
|
100
|
-
defaultProps.isSiteView = true;
|
|
101
|
-
|
|
102
101
|
await act(async () => {
|
|
103
102
|
render(
|
|
104
103
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
105
|
-
<BulkHeader
|
|
104
|
+
<BulkHeader
|
|
105
|
+
{...makeProps({
|
|
106
|
+
showBulk: false,
|
|
107
|
+
sortedListStatus: { isAscending: false, sortedByTitle: false, sortedByUsers: false },
|
|
108
|
+
isSiteView: true,
|
|
109
|
+
})}
|
|
110
|
+
/>
|
|
106
111
|
</ThemeProvider>,
|
|
107
112
|
);
|
|
108
113
|
});
|
|
@@ -114,18 +119,18 @@ describe("BulkHeader component rendering", () => {
|
|
|
114
119
|
|
|
115
120
|
describe("BulkHeader module events", () => {
|
|
116
121
|
it("should call deactivate role on click option", async () => {
|
|
117
|
-
defaultProps.showBulk = true;
|
|
118
|
-
defaultProps.isSiteView = false;
|
|
119
|
-
defaultProps.checkState.isAllSelected = true;
|
|
120
122
|
const activateRolesMock = vi.fn();
|
|
121
123
|
|
|
122
|
-
defaultProps.activateRoles = activateRolesMock as CalledWithMock<void, [active: boolean]> &
|
|
123
|
-
((active: boolean) => void);
|
|
124
|
-
|
|
125
124
|
await act(async () => {
|
|
126
125
|
render(
|
|
127
126
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
128
|
-
<BulkHeader
|
|
127
|
+
<BulkHeader
|
|
128
|
+
{...makeProps({
|
|
129
|
+
showBulk: true,
|
|
130
|
+
isSiteView: false,
|
|
131
|
+
activateRoles: activateRolesMock as CalledWithMock<void, [active: boolean]> & ((active: boolean) => void),
|
|
132
|
+
})}
|
|
133
|
+
/>
|
|
129
134
|
</ThemeProvider>,
|
|
130
135
|
);
|
|
131
136
|
});
|
|
@@ -136,17 +141,17 @@ describe("BulkHeader module events", () => {
|
|
|
136
141
|
});
|
|
137
142
|
|
|
138
143
|
it("should call activate role on click option", async () => {
|
|
139
|
-
defaultProps.showBulk = true;
|
|
140
|
-
defaultProps.checkState.isAllSelected = true;
|
|
141
144
|
const activateRolesMock = vi.fn();
|
|
142
145
|
|
|
143
|
-
defaultProps.activateRoles = activateRolesMock as CalledWithMock<void, [active: boolean]> &
|
|
144
|
-
((active: boolean) => void);
|
|
145
|
-
|
|
146
146
|
await act(async () => {
|
|
147
147
|
render(
|
|
148
148
|
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
149
|
-
<BulkHeader
|
|
149
|
+
<BulkHeader
|
|
150
|
+
{...makeProps({
|
|
151
|
+
showBulk: true,
|
|
152
|
+
activateRoles: activateRolesMock as CalledWithMock<void, [active: boolean]> & ((active: boolean) => void),
|
|
153
|
+
})}
|
|
154
|
+
/>
|
|
150
155
|
</ThemeProvider>,
|
|
151
156
|
);
|
|
152
157
|
});
|
|
@@ -20,6 +20,13 @@ import { rolesDataResponse } from "./../../../../__mocks__/axios/Roles";
|
|
|
20
20
|
|
|
21
21
|
afterEach(cleanup);
|
|
22
22
|
|
|
23
|
+
// jsdom no implementa scrollIntoView. Lo parcheaban cuatro tests dentro de su
|
|
24
|
+
// propio cuerpo, así que los que lo necesitaban solo funcionaban si alguno de
|
|
25
|
+
// esos ya había corrido: es un parche sobre el prototipo y sobrevive al test.
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
|
28
|
+
});
|
|
29
|
+
|
|
23
30
|
vi.mock("axios");
|
|
24
31
|
const mockedAxios = axios as MockedFunction<typeof axios>;
|
|
25
32
|
const defaultProps = mock<IRolesProps>();
|
|
@@ -27,7 +34,6 @@ const defaultProps = mock<IRolesProps>();
|
|
|
27
34
|
describe("Roles module rendering", () => {
|
|
28
35
|
it("should render component", async () => {
|
|
29
36
|
mockedAxios.mockResolvedValue(rolesDataResponse);
|
|
30
|
-
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
|
31
37
|
const initialStore = {
|
|
32
38
|
app: { isLoading: false },
|
|
33
39
|
users: { roles: rolesDataMock },
|
|
@@ -61,7 +67,6 @@ describe("Roles module rendering", () => {
|
|
|
61
67
|
|
|
62
68
|
it("should render role items", async () => {
|
|
63
69
|
mockedAxios.mockResolvedValue(rolesDataResponse);
|
|
64
|
-
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
|
65
70
|
const initialStore = {
|
|
66
71
|
app: { isLoading: false },
|
|
67
72
|
users: { roles: rolesDataMock },
|
|
@@ -93,7 +98,6 @@ describe("Roles module rendering", () => {
|
|
|
93
98
|
|
|
94
99
|
it("should render tag colors", async () => {
|
|
95
100
|
mockedAxios.mockResolvedValue(rolesDataResponse);
|
|
96
|
-
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
|
97
101
|
const initialStore = {
|
|
98
102
|
app: { isLoading: false },
|
|
99
103
|
users: { roles: rolesDataMock },
|
|
@@ -427,7 +431,6 @@ describe("Roles module events", () => {
|
|
|
427
431
|
|
|
428
432
|
it("should render FullArrowDown icon if is filtered by descending name", async () => {
|
|
429
433
|
mockedAxios.mockResolvedValue(rolesDataResponse);
|
|
430
|
-
window.HTMLElement.prototype.scrollIntoView = vi.fn();
|
|
431
434
|
const getRolesMock = vi.fn();
|
|
432
435
|
const activateRolesMock = vi.fn();
|
|
433
436
|
defaultProps.getRoles = getRolesMock as CalledWithMock<Promise<void>, [params?: IGetRoles | undefined]>;
|
|
@@ -19,6 +19,18 @@ const getTableCellStyles = () => {
|
|
|
19
19
|
const config = getRichTextConfigLazy();
|
|
20
20
|
return config?.tableCellStyles ? parseClassNames(config.tableCellStyles) : undefined;
|
|
21
21
|
};
|
|
22
|
+
const getImageStyles = () => {
|
|
23
|
+
const config = getRichTextConfigLazy();
|
|
24
|
+
return config?.imageStyles ? parseClassNames(config.imageStyles) : undefined;
|
|
25
|
+
};
|
|
26
|
+
// Opt-in: instances not asking for it keep their images unresizable.
|
|
27
|
+
const getImageResize = () => getRichTextConfigLazy()?.imageResize === true;
|
|
28
|
+
// When resizing is allowed the size is stored as a rounded percentage, so it
|
|
29
|
+
// still adapts to its container instead of being pinned to a pixel width.
|
|
30
|
+
const getImageResizeConfig = () => {
|
|
31
|
+
const imageResize = getImageResize();
|
|
32
|
+
return { imageResize, imageResizeWithPercent: imageResize, imageRoundPercent: imageResize };
|
|
33
|
+
};
|
|
22
34
|
|
|
23
35
|
const getMiscButtons = () => {
|
|
24
36
|
const paragraphStyles = getParagraphStyles();
|
|
@@ -49,7 +61,14 @@ const getButtonsFull = () => ({
|
|
|
49
61
|
buttonsVisible: 6,
|
|
50
62
|
},
|
|
51
63
|
moreText2: {
|
|
52
|
-
buttons: [
|
|
64
|
+
buttons: [
|
|
65
|
+
"insertLinkGriddo",
|
|
66
|
+
"insertImageGriddo",
|
|
67
|
+
"insertFileGriddo",
|
|
68
|
+
"insertVideo",
|
|
69
|
+
"clearFormatting",
|
|
70
|
+
"fullscreen",
|
|
71
|
+
],
|
|
53
72
|
buttonsVisible: 6,
|
|
54
73
|
},
|
|
55
74
|
moreMisc: {
|
|
@@ -76,6 +95,25 @@ const getButtons = () => {
|
|
|
76
95
|
|
|
77
96
|
const getInlineToolbar = () => ["langDropdown", "undo", "redo"];
|
|
78
97
|
|
|
98
|
+
const getImageEditButtons = () => {
|
|
99
|
+
const imageStyles = getImageStyles();
|
|
100
|
+
return [
|
|
101
|
+
"imageReplaceGriddo",
|
|
102
|
+
"imageAlign",
|
|
103
|
+
"imageCaption",
|
|
104
|
+
"imageRemove",
|
|
105
|
+
"|",
|
|
106
|
+
"imageLinkGriddo",
|
|
107
|
+
"linkOpen",
|
|
108
|
+
"linkRemove",
|
|
109
|
+
"-",
|
|
110
|
+
"imageDisplay",
|
|
111
|
+
imageStyles ? "imageStyle" : undefined,
|
|
112
|
+
"imageAlt",
|
|
113
|
+
getImageResize() ? "imageSize" : undefined,
|
|
114
|
+
];
|
|
115
|
+
};
|
|
116
|
+
|
|
79
117
|
const getWysiwygConfig = () => ({
|
|
80
118
|
key: process.env.GRIDDO_FROALA_KEY || process.env.REACT_APP_FROALA_KEY,
|
|
81
119
|
// pastePlain: false,
|
|
@@ -89,20 +127,10 @@ const getWysiwygConfig = () => ({
|
|
|
89
127
|
H3: "Heading 3",
|
|
90
128
|
H4: "Heading 4",
|
|
91
129
|
},
|
|
92
|
-
imageEditButtons:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"imageRemove",
|
|
97
|
-
"|",
|
|
98
|
-
"imageLinkGriddo",
|
|
99
|
-
"linkOpen",
|
|
100
|
-
"linkRemove",
|
|
101
|
-
"-",
|
|
102
|
-
"imageDisplay",
|
|
103
|
-
"imageAlt",
|
|
104
|
-
],
|
|
105
|
-
imageResize: false,
|
|
130
|
+
imageEditButtons: getImageEditButtons(),
|
|
131
|
+
...getImageResizeConfig(),
|
|
132
|
+
imageDefaultWidth: null,
|
|
133
|
+
imageDefaultHeight: null,
|
|
106
134
|
linkEditButtons: ["linkOpen", "editLinkGriddo", "linkRemove"],
|
|
107
135
|
videoInsertButtons: ["videoBack", "|", "videoByURL", "videoEmbed"],
|
|
108
136
|
imageAllowedTypes: ["jpeg", "jpg", "png", "svg"],
|
|
@@ -112,6 +140,7 @@ const getWysiwygConfig = () => ({
|
|
|
112
140
|
paragraphStyles: getParagraphStyles(),
|
|
113
141
|
tableStyles: getTableStyles(),
|
|
114
142
|
tableCellStyles: getTableCellStyles(),
|
|
143
|
+
imageStyles: getImageStyles(),
|
|
115
144
|
imageUploadRemoteUrls: false,
|
|
116
145
|
});
|
|
117
146
|
|
|
@@ -161,6 +190,7 @@ FroalaEditor.RegisterCommand("insertImageGriddo", {
|
|
|
161
190
|
undo: false,
|
|
162
191
|
focus: false,
|
|
163
192
|
callback: function (this: any) {
|
|
193
|
+
this.selection.save();
|
|
164
194
|
const el = this.$el?.get?.(0) || this.el;
|
|
165
195
|
el?.dispatchEvent(
|
|
166
196
|
new CustomEvent("griddo:openGallery", {
|
|
@@ -177,6 +207,7 @@ FroalaEditor.RegisterCommand("insertFileGriddo", {
|
|
|
177
207
|
undo: false,
|
|
178
208
|
focus: false,
|
|
179
209
|
callback: function (this: any) {
|
|
210
|
+
this.selection.save();
|
|
180
211
|
const el = this.$el?.get?.(0) || this.el;
|
|
181
212
|
el?.dispatchEvent(
|
|
182
213
|
new CustomEvent("griddo:openFileGallery", {
|
|
@@ -296,7 +327,17 @@ FroalaEditor.RegisterCommand("imageLinkGriddo", {
|
|
|
296
327
|
editorEl.dispatchEvent(
|
|
297
328
|
new CustomEvent("griddo:openLinkPopover", {
|
|
298
329
|
bubbles: true,
|
|
299
|
-
detail: {
|
|
330
|
+
detail: {
|
|
331
|
+
x,
|
|
332
|
+
y,
|
|
333
|
+
selectedText: "",
|
|
334
|
+
linkUrl,
|
|
335
|
+
isNewTab,
|
|
336
|
+
isNoFollow,
|
|
337
|
+
isEditing: true,
|
|
338
|
+
linkPageId,
|
|
339
|
+
isImageLink: true,
|
|
340
|
+
},
|
|
300
341
|
}),
|
|
301
342
|
);
|
|
302
343
|
} else {
|
|
@@ -304,7 +345,17 @@ FroalaEditor.RegisterCommand("imageLinkGriddo", {
|
|
|
304
345
|
editorEl.dispatchEvent(
|
|
305
346
|
new CustomEvent("griddo:openLinkPopover", {
|
|
306
347
|
bubbles: true,
|
|
307
|
-
detail: {
|
|
348
|
+
detail: {
|
|
349
|
+
x,
|
|
350
|
+
y,
|
|
351
|
+
selectedText: "",
|
|
352
|
+
linkUrl: "",
|
|
353
|
+
isNewTab: false,
|
|
354
|
+
isNoFollow: false,
|
|
355
|
+
isEditing: false,
|
|
356
|
+
linkPageId: null,
|
|
357
|
+
isImageLink: true,
|
|
358
|
+
},
|
|
308
359
|
}),
|
|
309
360
|
);
|
|
310
361
|
}
|
|
@@ -17,6 +17,8 @@ interface IRichTextConfig {
|
|
|
17
17
|
paragraphStyles?: { label: string; className: string }[];
|
|
18
18
|
tableStyles?: { label: string; className: string }[];
|
|
19
19
|
tableCellStyles?: { label: string; className: string }[];
|
|
20
|
+
imageStyles?: { label: string; className: string }[];
|
|
21
|
+
imageResize?: boolean;
|
|
20
22
|
editorLangs?: { name: string; iso: string }[];
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -84,10 +84,16 @@ const Wysiwyg = (props: IWysiwygProps) => {
|
|
|
84
84
|
|
|
85
85
|
const handleFileSelected = (file: IFile) => {
|
|
86
86
|
if (file?.url && editorRef.current) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
// Wait for Froala to stabilize after re-render before restoring selection
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
if (!editorRef.current) return;
|
|
90
|
+
editorRef.current.selection.restore();
|
|
91
|
+
// Use html.insert instead of link.insert to avoid selection issues
|
|
92
|
+
const linkHtml = `<a href="${file.url}" target="_blank">${file.fileName}</a>`;
|
|
93
|
+
editorRef.current.html.insert(linkHtml);
|
|
94
|
+
const updatedHtml = editorRef.current.html.get();
|
|
95
|
+
handleChange(updatedHtml);
|
|
96
|
+
}, 100);
|
|
91
97
|
}
|
|
92
98
|
};
|
|
93
99
|
|
|
@@ -99,14 +105,23 @@ const Wysiwyg = (props: IWysiwygProps) => {
|
|
|
99
105
|
if (img.alt) {
|
|
100
106
|
imgElement.alt = img.alt;
|
|
101
107
|
}
|
|
108
|
+
replaceImgRef.current = null;
|
|
109
|
+
const updatedHtml = editorRef.current.html.get();
|
|
110
|
+
handleChange(updatedHtml);
|
|
102
111
|
} else {
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
// Wait for Froala to stabilize after re-render before restoring selection
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
if (!editorRef.current) return;
|
|
115
|
+
editorRef.current.selection.restore();
|
|
116
|
+
// Use html.insert instead of image.insert to avoid selection issues
|
|
117
|
+
const imgHtml = `<img src="${img.url}" alt="${img.alt || ""}" />`;
|
|
118
|
+
editorRef.current.html.insert(imgHtml);
|
|
119
|
+
const updatedHtml = editorRef.current.html.get();
|
|
120
|
+
handleChange(updatedHtml);
|
|
121
|
+
}, 100);
|
|
105
122
|
}
|
|
106
123
|
|
|
107
124
|
replaceImgRef.current = null;
|
|
108
|
-
const updatedHtml = editorRef.current.html.get();
|
|
109
|
-
handleChange(updatedHtml);
|
|
110
125
|
}
|
|
111
126
|
};
|
|
112
127
|
|
|
@@ -219,12 +234,10 @@ const Wysiwyg = (props: IWysiwygProps) => {
|
|
|
219
234
|
editorEl.addEventListener("griddo:openGallery", (e: Event) => {
|
|
220
235
|
const detail = (e as CustomEvent).detail;
|
|
221
236
|
replaceImgRef.current = detail?.mode === "replace" ? detail.$img : null;
|
|
222
|
-
editorRef.current?.selection.save();
|
|
223
237
|
toggleModal("image");
|
|
224
238
|
});
|
|
225
239
|
|
|
226
240
|
editorEl.addEventListener("griddo:openFileGallery", () => {
|
|
227
|
-
editorRef.current?.selection.save();
|
|
228
241
|
toggleModal("file");
|
|
229
242
|
});
|
|
230
243
|
|
|
@@ -278,6 +291,24 @@ const Wysiwyg = (props: IWysiwygProps) => {
|
|
|
278
291
|
const stripedHtml = decodeEntities(html);
|
|
279
292
|
handleValidation?.(stripedHtml);
|
|
280
293
|
},
|
|
294
|
+
contentChanged: function (this: any) {
|
|
295
|
+
const editorEl = this.$el?.get?.(0) || this.el;
|
|
296
|
+
const images = editorEl?.querySelectorAll?.("img");
|
|
297
|
+
if (images) {
|
|
298
|
+
images.forEach((img: HTMLImageElement) => {
|
|
299
|
+
const hasFrDib = img.classList.contains("fr-dib");
|
|
300
|
+
const hasFrDii = img.classList.contains("fr-dii");
|
|
301
|
+
|
|
302
|
+
img.classList.remove("img-inline", "img-block");
|
|
303
|
+
|
|
304
|
+
if (hasFrDib) {
|
|
305
|
+
img.classList.add("img-block");
|
|
306
|
+
} else if (hasFrDii) {
|
|
307
|
+
img.classList.add("img-inline");
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
},
|
|
281
312
|
},
|
|
282
313
|
}),
|
|
283
314
|
[placeholder, inline, full, disabled, token, toggleModal, handleValidation, imageSite, uploadImage],
|
|
@@ -27,8 +27,10 @@ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
useEffect(() => {
|
|
30
|
+
let frame = 0;
|
|
31
|
+
|
|
30
32
|
const updateWidth = () => {
|
|
31
|
-
requestAnimationFrame(() => {
|
|
33
|
+
frame = requestAnimationFrame(() => {
|
|
32
34
|
if (rightPanelRef.current) {
|
|
33
35
|
setRwidth(rightPanelRef.current.offsetWidth);
|
|
34
36
|
}
|
|
@@ -38,7 +40,10 @@ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
|
|
|
38
40
|
updateWidth();
|
|
39
41
|
|
|
40
42
|
window.addEventListener("resize", updateWidth);
|
|
41
|
-
return () =>
|
|
43
|
+
return () => {
|
|
44
|
+
cancelAnimationFrame(frame);
|
|
45
|
+
window.removeEventListener("resize", updateWidth);
|
|
46
|
+
};
|
|
42
47
|
}, []);
|
|
43
48
|
|
|
44
49
|
useEffect(() => {
|