@griddo/ax 11.15.7 → 11.15.8-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.atoms.test.tsx +409 -0
- package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.helpers.test.tsx +418 -0
- package/src/__tests__/components/Fields/Wysiwyg/Wysiwyg.test.tsx +776 -29
- package/src/__tests__/components/FieldsBehavior/FieldsBehavior.test.tsx +368 -11
- package/src/__tests__/modules/Users/UserList/UserList.test.tsx +6 -6
- package/src/components/Fields/IntegrationsField/IntegrationItem/atoms.tsx +14 -5
- package/src/components/Fields/IntegrationsField/IntegrationItem/index.tsx +1 -8
- package/src/components/Fields/UrlField/index.tsx +5 -4
- package/src/components/Fields/Wysiwyg/atoms.tsx +140 -0
- package/src/components/Fields/Wysiwyg/config.tsx +190 -9
- package/src/components/Fields/Wysiwyg/helpers.tsx +54 -1
- package/src/components/Fields/Wysiwyg/index.tsx +210 -61
- package/src/components/Fields/Wysiwyg/style.tsx +32 -1
- package/src/components/Fields/Wysiwyg/vendors.tsx +0 -1
- package/src/components/FieldsBehavior/index.tsx +66 -31
- package/src/components/Icon/components/Sync.js +16 -0
- package/src/components/Icon/svgs/Sync.svg +3 -0
- package/src/components/RestoreModal/index.tsx +1 -3
- package/src/components/ScheduleModal/index.tsx +3 -3
- package/src/components/TableList/index.tsx +5 -3
- package/src/containers/FileDrive/actions.tsx +5 -5
- package/src/containers/Forms/actions.tsx +40 -38
- package/src/containers/Gallery/actions.tsx +27 -27
- package/src/containers/Integrations/actions.tsx +2 -2
- package/src/containers/Navigation/Defaults/actions.tsx +1 -1
- package/src/containers/PageEditor/actions.tsx +3 -5
- package/src/containers/Redirects/actions.tsx +2 -2
- package/src/containers/Sites/actions.tsx +15 -8
- package/src/containers/StructuredData/actions.tsx +5 -5
- package/src/containers/Users/actions.tsx +2 -2
- package/src/modules/Categories/CategoriesList/CategoryItem/index.tsx +36 -43
- package/src/modules/Categories/CategoriesList/CategoryPanel/index.tsx +15 -11
- package/src/modules/Categories/CategoriesList/atoms.tsx +42 -12
- package/src/modules/Categories/CategoriesList/index.tsx +8 -30
- package/src/modules/Content/PageItem/index.tsx +6 -5
- package/src/modules/Content/atoms.tsx +7 -3
- package/src/modules/Content/index.tsx +8 -16
- package/src/modules/FileDrive/FileModal/DetailPanel/index.tsx +14 -14
- package/src/modules/FileDrive/FolderItem/index.tsx +46 -54
- package/src/modules/FileDrive/GridItem/index.tsx +32 -37
- package/src/modules/FileDrive/ListItem/index.tsx +31 -37
- package/src/modules/FileDrive/atoms.tsx +59 -12
- package/src/modules/FileDrive/index.tsx +88 -98
- package/src/modules/Forms/FormCategoriesList/CategoryItem/index.tsx +42 -34
- package/src/modules/Forms/FormCategoriesList/atoms.tsx +17 -6
- package/src/modules/Forms/FormCategoriesList/index.tsx +30 -34
- package/src/modules/Forms/FormEditor/index.tsx +14 -6
- package/src/modules/Forms/FormList/FormItem/index.tsx +5 -1
- package/src/modules/Forms/FormList/index.tsx +15 -9
- package/src/modules/Forms/atoms.tsx +7 -4
- package/src/modules/GlobalEditor/atoms.tsx +77 -0
- package/src/modules/GlobalEditor/index.tsx +24 -7
- package/src/modules/MediaGallery/FolderItem/index.tsx +36 -52
- package/src/modules/MediaGallery/GridItem/index.tsx +27 -38
- package/src/modules/MediaGallery/ImageModal/DetailPanel/index.tsx +13 -13
- package/src/modules/MediaGallery/ImageModal/index.tsx +2 -16
- package/src/modules/MediaGallery/ListItem/index.tsx +25 -37
- package/src/modules/MediaGallery/atoms.tsx +84 -15
- package/src/modules/MediaGallery/index.tsx +62 -122
- package/src/modules/Navigation/Defaults/Item/atoms.tsx +2 -4
- package/src/modules/Navigation/Defaults/Item/index.tsx +80 -94
- package/src/modules/Navigation/Defaults/atoms.tsx +179 -7
- package/src/modules/Navigation/Defaults/index.tsx +51 -18
- package/src/modules/Navigation/Defaults/style.tsx +10 -7
- package/src/modules/Navigation/Menus/List/Table/Item/atoms.tsx +33 -19
- package/src/modules/Navigation/Menus/List/Table/Item/index.tsx +18 -19
- package/src/modules/Navigation/Menus/List/Table/index.tsx +15 -13
- package/src/modules/Navigation/Menus/List/index.tsx +1 -1
- package/src/modules/PageEditor/atoms.tsx +14 -11
- package/src/modules/PageEditor/index.tsx +11 -16
- package/src/modules/Redirects/RedirectItem/index.tsx +37 -39
- package/src/modules/Redirects/atoms.tsx +58 -12
- package/src/modules/Redirects/index.tsx +30 -58
- package/src/modules/Settings/Integrations/IntegrationItem/CopyModal/index.tsx +1 -1
- package/src/modules/Settings/Integrations/IntegrationItem/index.tsx +57 -69
- package/src/modules/Settings/Integrations/atoms.tsx +72 -8
- package/src/modules/Settings/Integrations/index.tsx +34 -48
- package/src/modules/StructuredData/Form/index.tsx +25 -8
- package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/atoms.tsx +26 -75
- package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +15 -33
- package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +63 -41
- package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/style.tsx +13 -8
- package/src/modules/StructuredData/StructuredDataList/index.tsx +11 -16
- package/src/modules/StructuredData/{StructuredDataList/atoms.tsx → atoms.tsx} +25 -28
- package/src/modules/Users/UserList/UserItem/index.tsx +18 -35
- package/src/modules/Users/UserList/atoms.tsx +61 -0
- package/src/modules/Users/UserList/index.tsx +12 -32
|
@@ -49,8 +49,8 @@ const getButtonsFull = () => ({
|
|
|
49
49
|
buttonsVisible: 6,
|
|
50
50
|
},
|
|
51
51
|
moreText2: {
|
|
52
|
-
buttons: ["
|
|
53
|
-
buttonsVisible:
|
|
52
|
+
buttons: ["insertLinkGriddo", "insertImageGriddo", "insertFileGriddo", "insertVideo", "clearFormatting", "fullscreen"],
|
|
53
|
+
buttonsVisible: 6,
|
|
54
54
|
},
|
|
55
55
|
moreMisc: {
|
|
56
56
|
buttons: getMiscButtons(),
|
|
@@ -66,7 +66,7 @@ const getButtons = () => {
|
|
|
66
66
|
"bold",
|
|
67
67
|
"italic",
|
|
68
68
|
"formatUL",
|
|
69
|
-
"
|
|
69
|
+
"insertLinkGriddo",
|
|
70
70
|
"paragraphFormat",
|
|
71
71
|
paragraphStyles ? "paragraphStyle" : undefined,
|
|
72
72
|
"langDropdown",
|
|
@@ -90,21 +90,20 @@ const getWysiwygConfig = () => ({
|
|
|
90
90
|
H4: "Heading 4",
|
|
91
91
|
},
|
|
92
92
|
imageEditButtons: [
|
|
93
|
-
"
|
|
93
|
+
"imageReplaceGriddo",
|
|
94
94
|
"imageAlign",
|
|
95
95
|
"imageCaption",
|
|
96
96
|
"imageRemove",
|
|
97
97
|
"|",
|
|
98
|
-
"
|
|
98
|
+
"imageLinkGriddo",
|
|
99
99
|
"linkOpen",
|
|
100
|
-
"linkEdit",
|
|
101
100
|
"linkRemove",
|
|
102
101
|
"-",
|
|
103
102
|
"imageDisplay",
|
|
104
103
|
"imageAlt",
|
|
105
104
|
],
|
|
106
105
|
imageResize: false,
|
|
107
|
-
linkEditButtons: ["linkOpen", "
|
|
106
|
+
linkEditButtons: ["linkOpen", "editLinkGriddo", "linkRemove"],
|
|
108
107
|
videoInsertButtons: ["videoBack", "|", "videoByURL", "videoEmbed"],
|
|
109
108
|
imageAllowedTypes: ["jpeg", "jpg", "png", "svg"],
|
|
110
109
|
imageMaxSize: 20 * 1024 * 1024,
|
|
@@ -121,9 +120,18 @@ FroalaEditor.DefineIcon("langIcon", {
|
|
|
121
120
|
template: "svg",
|
|
122
121
|
});
|
|
123
122
|
|
|
123
|
+
FroalaEditor.DefineIcon("griddoImageIcon", { SVG_KEY: "insertImage" });
|
|
124
|
+
FroalaEditor.DefineIcon("griddoReplaceIcon", { SVG_KEY: "replaceImage" });
|
|
125
|
+
FroalaEditor.DefineIcon("griddoFileIcon", {
|
|
126
|
+
PATH: "M8 16H16V18H8V16ZM8 12H16V14H8V12ZM14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.89 22 5.99 22H18C19.1 22 20 21.1 20 20V8L14 2ZM18 20H6V4H13V9H18V20Z",
|
|
127
|
+
template: "svg",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
FroalaEditor.DefineIcon("griddoLinkIcon", { SVG_KEY: "insertLink" });
|
|
131
|
+
|
|
124
132
|
Object.assign(FroalaEditor.POPUP_TEMPLATES, {
|
|
125
|
-
"image.uploading":
|
|
126
|
-
"image.uploadError":
|
|
133
|
+
"image.uploading": "[_CUSTOM_LAYER_]",
|
|
134
|
+
"image.uploadError": "[_CUSTOM_LAYER_]",
|
|
127
135
|
});
|
|
128
136
|
|
|
129
137
|
FroalaEditor.RegisterCommand("langDropdown", {
|
|
@@ -147,6 +155,179 @@ FroalaEditor.RegisterCommand("langDropdown", {
|
|
|
147
155
|
},
|
|
148
156
|
});
|
|
149
157
|
|
|
158
|
+
FroalaEditor.RegisterCommand("insertImageGriddo", {
|
|
159
|
+
title: "Insert Image",
|
|
160
|
+
icon: "griddoImageIcon",
|
|
161
|
+
undo: false,
|
|
162
|
+
focus: false,
|
|
163
|
+
callback: function (this: any) {
|
|
164
|
+
const el = this.$el?.get?.(0) || this.el;
|
|
165
|
+
el?.dispatchEvent(
|
|
166
|
+
new CustomEvent("griddo:openGallery", {
|
|
167
|
+
bubbles: true,
|
|
168
|
+
detail: { mode: "insert" },
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
FroalaEditor.RegisterCommand("insertFileGriddo", {
|
|
175
|
+
title: "Insert File",
|
|
176
|
+
icon: "griddoFileIcon",
|
|
177
|
+
undo: false,
|
|
178
|
+
focus: false,
|
|
179
|
+
callback: function (this: any) {
|
|
180
|
+
const el = this.$el?.get?.(0) || this.el;
|
|
181
|
+
el?.dispatchEvent(
|
|
182
|
+
new CustomEvent("griddo:openFileGallery", {
|
|
183
|
+
bubbles: true,
|
|
184
|
+
detail: { mode: "insert" },
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
FroalaEditor.RegisterCommand("insertLinkGriddo", {
|
|
191
|
+
title: "Insert Link",
|
|
192
|
+
icon: "griddoLinkIcon",
|
|
193
|
+
undo: false,
|
|
194
|
+
focus: false,
|
|
195
|
+
callback: function (this: any) {
|
|
196
|
+
let x = window.innerWidth / 2 - 200;
|
|
197
|
+
let y = 100;
|
|
198
|
+
|
|
199
|
+
// Get the editor's wrapper and find the button within it
|
|
200
|
+
const editorEl = this.$el?.get?.(0) || this.el;
|
|
201
|
+
if (editorEl) {
|
|
202
|
+
// Find the parent wrapper of this editor
|
|
203
|
+
const wrapper = editorEl.closest(".fr-box") || editorEl.parentElement;
|
|
204
|
+
if (wrapper) {
|
|
205
|
+
// Find the insertLinkGriddo button within this wrapper's toolbar
|
|
206
|
+
const linkBtn = wrapper.querySelector('[data-cmd="insertLinkGriddo"]') as HTMLElement;
|
|
207
|
+
if (linkBtn) {
|
|
208
|
+
const rect = linkBtn.getBoundingClientRect();
|
|
209
|
+
x = rect.left;
|
|
210
|
+
y = rect.bottom + 10;
|
|
211
|
+
} else {
|
|
212
|
+
// Fallback: use editor position
|
|
213
|
+
const editorRect = editorEl.getBoundingClientRect?.();
|
|
214
|
+
if (editorRect) {
|
|
215
|
+
x = editorRect.left + editorRect.width / 2 - 200;
|
|
216
|
+
y = editorRect.top + editorRect.height + 10;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const selectedText = this.selection.text();
|
|
222
|
+
this.selection.save();
|
|
223
|
+
editorEl.dispatchEvent(
|
|
224
|
+
new CustomEvent("griddo:openLinkPopover", {
|
|
225
|
+
bubbles: true,
|
|
226
|
+
detail: { x, y, selectedText },
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
FroalaEditor.RegisterCommand("editLinkGriddo", {
|
|
234
|
+
title: "Edit Link",
|
|
235
|
+
icon: "linkEdit",
|
|
236
|
+
undo: false,
|
|
237
|
+
focus: false,
|
|
238
|
+
callback: function (this: any) {
|
|
239
|
+
const linkElement = this.link.get();
|
|
240
|
+
if (!linkElement) return;
|
|
241
|
+
|
|
242
|
+
const linkRect = linkElement.getBoundingClientRect();
|
|
243
|
+
const x = linkRect.left;
|
|
244
|
+
const y = linkRect.bottom + 10;
|
|
245
|
+
|
|
246
|
+
const editorEl = this.$el?.get?.(0) || this.el;
|
|
247
|
+
if (editorEl) {
|
|
248
|
+
const linkUrl = linkElement.href || "";
|
|
249
|
+
const linkText = linkElement.textContent || "";
|
|
250
|
+
const isNewTab = linkElement.target === "_blank";
|
|
251
|
+
const isNoFollow = linkElement.rel?.includes("nofollow") || false;
|
|
252
|
+
const linkPageId = linkElement.getAttribute("data-pageId");
|
|
253
|
+
|
|
254
|
+
linkElement.setAttribute("data-griddo-editing", "true");
|
|
255
|
+
this.selection.save();
|
|
256
|
+
editorEl.dispatchEvent(
|
|
257
|
+
new CustomEvent("griddo:openLinkPopover", {
|
|
258
|
+
bubbles: true,
|
|
259
|
+
detail: { x, y, selectedText: linkText, linkUrl, isNewTab, isNoFollow, isEditing: true, linkPageId },
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
FroalaEditor.RegisterCommand("imageLinkGriddo", {
|
|
267
|
+
title: "Link",
|
|
268
|
+
icon: "griddoLinkIcon",
|
|
269
|
+
undo: false,
|
|
270
|
+
focus: false,
|
|
271
|
+
callback: function (this: any) {
|
|
272
|
+
const jqElement = this.image.get();
|
|
273
|
+
if (!jqElement) return;
|
|
274
|
+
|
|
275
|
+
const imgElement = jqElement[0] || jqElement;
|
|
276
|
+
if (!imgElement || typeof imgElement.getBoundingClientRect !== "function") return;
|
|
277
|
+
|
|
278
|
+
const imgRect = imgElement.getBoundingClientRect();
|
|
279
|
+
const x = imgRect.left;
|
|
280
|
+
const y = imgRect.bottom + 10;
|
|
281
|
+
|
|
282
|
+
const editorEl = this.$el?.get?.(0) || this.el;
|
|
283
|
+
if (editorEl) {
|
|
284
|
+
const currentLink = imgElement.parentElement?.tagName === "A" ? imgElement.parentElement : null;
|
|
285
|
+
const isEditing = !!currentLink;
|
|
286
|
+
|
|
287
|
+
this.selection.save();
|
|
288
|
+
|
|
289
|
+
if (isEditing) {
|
|
290
|
+
const linkUrl = currentLink.href || "";
|
|
291
|
+
const isNewTab = currentLink.target === "_blank";
|
|
292
|
+
const isNoFollow = currentLink.rel?.includes("nofollow") || false;
|
|
293
|
+
const linkPageId = currentLink.getAttribute("data-pageId");
|
|
294
|
+
|
|
295
|
+
currentLink.setAttribute("data-griddo-editing-image-link", "true");
|
|
296
|
+
editorEl.dispatchEvent(
|
|
297
|
+
new CustomEvent("griddo:openLinkPopover", {
|
|
298
|
+
bubbles: true,
|
|
299
|
+
detail: { x, y, selectedText: "", linkUrl, isNewTab, isNoFollow, isEditing: true, linkPageId, isImageLink: true },
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
} else {
|
|
303
|
+
imgElement.setAttribute("data-griddo-editing-image", "true");
|
|
304
|
+
editorEl.dispatchEvent(
|
|
305
|
+
new CustomEvent("griddo:openLinkPopover", {
|
|
306
|
+
bubbles: true,
|
|
307
|
+
detail: { x, y, selectedText: "", linkUrl: "", isNewTab: false, isNoFollow: false, isEditing: false, linkPageId: null, isImageLink: true },
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
FroalaEditor.RegisterCommand("imageReplaceGriddo", {
|
|
316
|
+
title: "Replace Image",
|
|
317
|
+
icon: "griddoReplaceIcon",
|
|
318
|
+
undo: true,
|
|
319
|
+
focus: false,
|
|
320
|
+
callback: function (this: any) {
|
|
321
|
+
const el = this.$el?.get?.(0) || this.el;
|
|
322
|
+
el?.dispatchEvent(
|
|
323
|
+
new CustomEvent("griddo:openGallery", {
|
|
324
|
+
bubbles: true,
|
|
325
|
+
detail: { mode: "replace", $img: this.image.get() },
|
|
326
|
+
}),
|
|
327
|
+
);
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
|
|
150
331
|
export {
|
|
151
332
|
getButtonsFull as buttonsFull,
|
|
152
333
|
getButtons as buttons,
|
|
@@ -85,4 +85,57 @@ const getLanguageMenuHtml = (languages: { name: string; iso: string; featured?:
|
|
|
85
85
|
);
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
const setLinkAttributes = (
|
|
89
|
+
element: HTMLElement | null,
|
|
90
|
+
url: string,
|
|
91
|
+
options?: { newTab?: boolean; noFollow?: boolean; linkTo?: number | null }
|
|
92
|
+
) => {
|
|
93
|
+
if (!element) return;
|
|
94
|
+
element.setAttribute("href", url);
|
|
95
|
+
if (options?.newTab) {
|
|
96
|
+
element.setAttribute("target", "_blank");
|
|
97
|
+
} else {
|
|
98
|
+
element.removeAttribute("target");
|
|
99
|
+
}
|
|
100
|
+
if (options?.noFollow) {
|
|
101
|
+
element.setAttribute("rel", "nofollow");
|
|
102
|
+
} else {
|
|
103
|
+
element.removeAttribute("rel");
|
|
104
|
+
}
|
|
105
|
+
if (options?.linkTo) {
|
|
106
|
+
element.setAttribute("data-pageId", String(options.linkTo));
|
|
107
|
+
} else {
|
|
108
|
+
element.removeAttribute("data-pageId");
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getPopupPosition = (editorEl: HTMLElement) => {
|
|
113
|
+
const editorRect = editorEl.getBoundingClientRect?.();
|
|
114
|
+
return {
|
|
115
|
+
x: editorRect ? editorRect.left + editorRect.width / 2 - 100 : window.innerWidth / 2 - 100,
|
|
116
|
+
y: editorRect ? editorRect.top + 50 : 50,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const createPopupFunctions = (editor: any, editorEl: HTMLElement) => {
|
|
121
|
+
const showPopup = (name: string, template: any, keepVisible = false) => {
|
|
122
|
+
editor.popups.create(name, template);
|
|
123
|
+
if (!keepVisible) {
|
|
124
|
+
editor.popups.onHide(name, () => false);
|
|
125
|
+
}
|
|
126
|
+
const pos = getPopupPosition(editorEl);
|
|
127
|
+
editor.popups.show(name, pos.x, pos.y, 50);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const showErrorPopup = () => {
|
|
131
|
+
const errorPopupTemplate = {
|
|
132
|
+
custom_layer: '<div style="padding: 15px 30px; text-align: center; color: #d32f2f;">Upload failed</div>',
|
|
133
|
+
};
|
|
134
|
+
showPopup("image.uploadError", errorPopupTemplate);
|
|
135
|
+
setTimeout(() => editor.popups.hide("image.uploadError"), 3000);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return { showPopup, showErrorPopup };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export { getRichTextConfig, parseClassNames, getLanguageMenuHtml, setLinkAttributes, getPopupPosition, createPopupFunctions, type IRichTextConfig };
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import FroalaEditorComponent from "react-froala-wysiwyg";
|
|
3
3
|
import { connect } from "react-redux";
|
|
4
4
|
|
|
5
|
+
import { FileGallery, Gallery, Modal } from "@ax/components";
|
|
5
6
|
import { galleryActions } from "@ax/containers/Gallery";
|
|
6
7
|
import { decodeEntities } from "@ax/helpers";
|
|
7
|
-
import type { IImage, IRootState, ISite } from "@ax/types";
|
|
8
|
+
import type { IFile, IImage, IRootState, ISite } from "@ax/types";
|
|
8
9
|
|
|
9
10
|
import FroalaEditor from "froala-editor";
|
|
10
11
|
|
|
12
|
+
import { LinkPopover } from "./atoms";
|
|
11
13
|
import { buttons, buttonsFull, inlineToolbar, wysiwygConfig } from "./config";
|
|
14
|
+
import { createPopupFunctions, getPopupPosition, setLinkAttributes } from "./helpers";
|
|
12
15
|
import "./vendors";
|
|
13
16
|
|
|
17
|
+
import { useModals } from "@ax/hooks";
|
|
18
|
+
|
|
14
19
|
import * as S from "./style";
|
|
15
20
|
|
|
16
21
|
const Wysiwyg = (props: IWysiwygProps) => {
|
|
@@ -33,12 +38,40 @@ const Wysiwyg = (props: IWysiwygProps) => {
|
|
|
33
38
|
const errorRef = useRef(error);
|
|
34
39
|
errorRef.current = error;
|
|
35
40
|
|
|
41
|
+
const { isOpen, toggleModal } = useModals(["image", "file"]);
|
|
42
|
+
const [linkPopoverOpen, setLinkPopoverOpen] = useState(false);
|
|
43
|
+
const [linkPopoverPos, setLinkPopoverPos] = useState({ x: 0, y: 0 });
|
|
44
|
+
const [selectedText, setSelectedText] = useState("");
|
|
45
|
+
const [linkData, setLinkData] = useState<{
|
|
46
|
+
url: string;
|
|
47
|
+
newTab: boolean;
|
|
48
|
+
noFollow: boolean;
|
|
49
|
+
isEditing: boolean;
|
|
50
|
+
linkPageId?: number | null;
|
|
51
|
+
isImageLink?: boolean;
|
|
52
|
+
} | null>(null);
|
|
53
|
+
const replaceImgRef = useRef<any>(null);
|
|
54
|
+
const lastValueRef = useRef(value);
|
|
55
|
+
|
|
36
56
|
useEffect(() => {
|
|
37
57
|
if (!editorRef.current) return;
|
|
38
58
|
disabled ? editorRef.current.edit.off() : editorRef.current.edit.on();
|
|
39
59
|
}, [disabled]);
|
|
40
60
|
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
lastValueRef.current = value;
|
|
63
|
+
}, [value]);
|
|
64
|
+
|
|
41
65
|
const handleChange = (model: string) => {
|
|
66
|
+
// Ignore if only difference is Froala's automatic empty paragraph
|
|
67
|
+
const cleanCurrent = model.replace(/<p><br><\/p>$/, "").trim();
|
|
68
|
+
const cleanLast = (lastValueRef.current || "").replace(/<p><br><\/p>$/, "").trim();
|
|
69
|
+
|
|
70
|
+
if (cleanCurrent === cleanLast) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
lastValueRef.current = model;
|
|
42
75
|
onChange(model);
|
|
43
76
|
const stripedHtml = decodeEntities(model);
|
|
44
77
|
setTimeout(() => {
|
|
@@ -46,53 +79,150 @@ const Wysiwyg = (props: IWysiwygProps) => {
|
|
|
46
79
|
}, 300);
|
|
47
80
|
};
|
|
48
81
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.edit.off();
|
|
82
|
+
const handleFileSelected = (file: IFile) => {
|
|
83
|
+
if (file?.url && editorRef.current) {
|
|
84
|
+
editorRef.current.selection.restore();
|
|
85
|
+
editorRef.current.link.insert(file.url, file.fileName, { target: "_blank" });
|
|
86
|
+
const updatedHtml = editorRef.current.html.get();
|
|
87
|
+
handleChange(updatedHtml);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleImageSelected = (img: IImage | null) => {
|
|
92
|
+
if (img?.url && editorRef.current) {
|
|
93
|
+
if (replaceImgRef.current && replaceImgRef.current.length > 0) {
|
|
94
|
+
const imgElement = replaceImgRef.current[0];
|
|
95
|
+
imgElement.src = img.url;
|
|
96
|
+
if (img.alt) {
|
|
97
|
+
imgElement.alt = img.alt;
|
|
66
98
|
}
|
|
99
|
+
} else {
|
|
100
|
+
editorRef.current.selection.restore();
|
|
101
|
+
editorRef.current.image.insert(img.url, true, { alt: img.alt || "" }, null, null);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
replaceImgRef.current = null;
|
|
105
|
+
const updatedHtml = editorRef.current.html.get();
|
|
106
|
+
handleChange(updatedHtml);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const handleInsertLink = (
|
|
111
|
+
url: string,
|
|
112
|
+
text: string,
|
|
113
|
+
options?: { newTab: boolean; noFollow: boolean; linkTo?: number | null },
|
|
114
|
+
) => {
|
|
115
|
+
if (url && editorRef.current) {
|
|
116
|
+
const linkOptions: any = {};
|
|
117
|
+
if (options?.newTab) {
|
|
118
|
+
linkOptions.target = "_blank";
|
|
119
|
+
}
|
|
120
|
+
if (options?.noFollow) {
|
|
121
|
+
linkOptions.rel = "nofollow";
|
|
122
|
+
}
|
|
123
|
+
if (options?.linkTo) {
|
|
124
|
+
linkOptions["data-pageId"] = options.linkTo;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (linkData?.isImageLink) {
|
|
128
|
+
const editingImageLink = editorRef.current.$el
|
|
129
|
+
?.get?.(0)
|
|
130
|
+
?.querySelector('[data-griddo-editing-image-link="true"]');
|
|
131
|
+
const editingImage = editorRef.current.$el?.get?.(0)?.querySelector('[data-griddo-editing-image="true"]');
|
|
132
|
+
|
|
133
|
+
if (editingImageLink) {
|
|
134
|
+
setLinkAttributes(editingImageLink, url, options);
|
|
135
|
+
editingImageLink.removeAttribute("data-griddo-editing-image-link");
|
|
136
|
+
} else if (editingImage) {
|
|
137
|
+
let linkElement = editingImage.parentElement?.tagName === "A" ? editingImage.parentElement : null;
|
|
138
|
+
|
|
139
|
+
if (!linkElement) {
|
|
140
|
+
linkElement = document.createElement("a");
|
|
141
|
+
editingImage.parentElement?.insertBefore(linkElement, editingImage);
|
|
142
|
+
linkElement.appendChild(editingImage);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setLinkAttributes(linkElement, url, options);
|
|
146
|
+
editingImage.removeAttribute("data-griddo-editing-image");
|
|
147
|
+
}
|
|
148
|
+
} else if (linkData?.isEditing) {
|
|
149
|
+
const editingLink = editorRef.current.$el?.get?.(0)?.querySelector('[data-griddo-editing="true"]');
|
|
150
|
+
if (editingLink) {
|
|
151
|
+
setLinkAttributes(editingLink, url, options);
|
|
152
|
+
editingLink.textContent = text || url;
|
|
153
|
+
editingLink.removeAttribute("data-griddo-editing");
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
editorRef.current.selection.restore();
|
|
157
|
+
editorRef.current.link.insert(url, text || url, linkOptions);
|
|
158
|
+
}
|
|
159
|
+
setLinkPopoverOpen(false);
|
|
160
|
+
const updatedHtml = editorRef.current.html.get();
|
|
161
|
+
handleChange(updatedHtml);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const handleCloseLinkPopover = () => {
|
|
166
|
+
setLinkPopoverOpen(false);
|
|
167
|
+
setLinkData(null);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const dynamicConfig = useMemo(
|
|
171
|
+
() => ({
|
|
172
|
+
placeholderText: placeholder,
|
|
173
|
+
toolbarButtons: inline ? inlineToolbar() : full ? buttonsFull() : buttons(),
|
|
174
|
+
toolbarInline: inline,
|
|
175
|
+
charCounterCount: !inline,
|
|
176
|
+
imageUpload: false,
|
|
177
|
+
multiLine: !inline,
|
|
178
|
+
enter: inline ? FroalaEditor.ENTER_BR : FroalaEditor.ENTER_P,
|
|
179
|
+
requestHeaders: {
|
|
180
|
+
Authorization: `bearer ${token}`,
|
|
181
|
+
},
|
|
182
|
+
events: {
|
|
183
|
+
initialized(this: any) {
|
|
184
|
+
editorRef.current = this;
|
|
185
|
+
|
|
186
|
+
if (disabled) {
|
|
187
|
+
this.edit.off();
|
|
188
|
+
}
|
|
67
189
|
|
|
68
|
-
if (full && !inline) {
|
|
69
190
|
const editorEl = this.$el?.get?.(0) || this.el;
|
|
70
|
-
if (editorEl)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
191
|
+
if (!editorEl) return;
|
|
192
|
+
|
|
193
|
+
editorEl.addEventListener("griddo:openLinkPopover", (e: Event) => {
|
|
194
|
+
const detail = (e as CustomEvent).detail;
|
|
195
|
+
setLinkPopoverOpen(true);
|
|
196
|
+
setLinkPopoverPos({ x: detail.x, y: detail.y });
|
|
197
|
+
setSelectedText(detail.selectedText || "");
|
|
198
|
+
setLinkData(
|
|
199
|
+
detail.isEditing || detail.isImageLink
|
|
200
|
+
? {
|
|
201
|
+
url: detail.linkUrl || "",
|
|
202
|
+
newTab: detail.isNewTab || false,
|
|
203
|
+
noFollow: detail.isNoFollow || false,
|
|
204
|
+
isEditing: detail.isEditing || false,
|
|
205
|
+
linkPageId: detail.linkPageId || null,
|
|
206
|
+
isImageLink: detail.isImageLink || false,
|
|
207
|
+
}
|
|
208
|
+
: null,
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (full && !inline) {
|
|
213
|
+
const { showErrorPopup } = createPopupFunctions(this, editorEl);
|
|
214
|
+
|
|
215
|
+
editorEl.addEventListener("griddo:openGallery", (e: Event) => {
|
|
216
|
+
const detail = (e as CustomEvent).detail;
|
|
217
|
+
replaceImgRef.current = detail?.mode === "replace" ? detail.$img : null;
|
|
218
|
+
editorRef.current?.selection.save();
|
|
219
|
+
toggleModal("image");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
editorEl.addEventListener("griddo:openFileGallery", () => {
|
|
223
|
+
editorRef.current?.selection.save();
|
|
224
|
+
toggleModal("file");
|
|
225
|
+
});
|
|
96
226
|
|
|
97
227
|
editorEl.addEventListener("drop", async (e: DragEvent) => {
|
|
98
228
|
e.preventDefault();
|
|
@@ -110,13 +240,13 @@ const Wysiwyg = (props: IWysiwygProps) => {
|
|
|
110
240
|
if (isUploading) {
|
|
111
241
|
setTimeout(() => {
|
|
112
242
|
if (isUploading) {
|
|
113
|
-
const pos = getPopupPosition();
|
|
243
|
+
const pos = getPopupPosition(editorEl);
|
|
114
244
|
this.popups.show("image.uploading", pos.x, pos.y, 50);
|
|
115
245
|
}
|
|
116
246
|
}, 0);
|
|
117
247
|
}
|
|
118
248
|
});
|
|
119
|
-
const pos = getPopupPosition();
|
|
249
|
+
const pos = getPopupPosition(editorEl);
|
|
120
250
|
this.popups.show("image.uploading", pos.x, pos.y, 50);
|
|
121
251
|
|
|
122
252
|
try {
|
|
@@ -138,22 +268,41 @@ const Wysiwyg = (props: IWysiwygProps) => {
|
|
|
138
268
|
}
|
|
139
269
|
});
|
|
140
270
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
271
|
+
},
|
|
272
|
+
blur: function (this: any) {
|
|
273
|
+
const html = this.html.get();
|
|
274
|
+
const stripedHtml = decodeEntities(html);
|
|
275
|
+
handleValidation?.(stripedHtml);
|
|
276
|
+
},
|
|
147
277
|
},
|
|
148
|
-
},
|
|
149
|
-
|
|
278
|
+
}),
|
|
279
|
+
[placeholder, inline, full, disabled, token, toggleModal, handleValidation, imageSite, uploadImage],
|
|
280
|
+
);
|
|
150
281
|
|
|
151
|
-
const config = { ...wysiwygConfig(), ...dynamicConfig };
|
|
282
|
+
const config = useMemo(() => ({ ...wysiwygConfig(), ...dynamicConfig }), [dynamicConfig]);
|
|
152
283
|
|
|
153
284
|
return (
|
|
154
|
-
|
|
155
|
-
<
|
|
156
|
-
|
|
285
|
+
<>
|
|
286
|
+
<S.EditorWrapper error={error} disabled={disabled} data-testid="wysiwyg-wrapper">
|
|
287
|
+
<FroalaEditorComponent tag="textarea" model={value} config={config} onModelChange={handleChange} />
|
|
288
|
+
</S.EditorWrapper>
|
|
289
|
+
<Modal isOpen={isOpen("image")} hide={() => toggleModal("image")} size="XL" title="Select image">
|
|
290
|
+
<Gallery getImageSelected={handleImageSelected} toggleModal={() => toggleModal("image")} site={site} />
|
|
291
|
+
</Modal>
|
|
292
|
+
<Modal isOpen={isOpen("file")} hide={() => toggleModal("file")} size="XL" title="Select file">
|
|
293
|
+
<FileGallery addFile={handleFileSelected} toggleModal={() => toggleModal("file")} site={site} />
|
|
294
|
+
</Modal>
|
|
295
|
+
{linkPopoverOpen && (
|
|
296
|
+
<LinkPopover
|
|
297
|
+
x={linkPopoverPos.x}
|
|
298
|
+
y={linkPopoverPos.y}
|
|
299
|
+
selectedText={selectedText}
|
|
300
|
+
linkData={linkData}
|
|
301
|
+
onInsert={handleInsertLink}
|
|
302
|
+
onClose={handleCloseLinkPopover}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
</>
|
|
157
306
|
);
|
|
158
307
|
};
|
|
159
308
|
|
|
@@ -166,7 +315,7 @@ interface IProps {
|
|
|
166
315
|
onChange: (value: string) => void;
|
|
167
316
|
disabled?: boolean;
|
|
168
317
|
handleValidation?: (value: string) => void;
|
|
169
|
-
site: ISite;
|
|
318
|
+
site: ISite | null;
|
|
170
319
|
inline?: boolean;
|
|
171
320
|
}
|
|
172
321
|
|
|
@@ -1,6 +1,37 @@
|
|
|
1
|
-
import styled from "styled-components";
|
|
2
1
|
import { Wrapper } from "@ax/components/FieldsBehavior/style";
|
|
3
2
|
|
|
3
|
+
import styled from "styled-components";
|
|
4
|
+
|
|
5
|
+
export const PopoverOverlay = styled.div`
|
|
6
|
+
position: fixed;
|
|
7
|
+
top: 0;
|
|
8
|
+
left: 0;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 100%;
|
|
11
|
+
z-index: 999;
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
export const PopoverContainer = styled.div<{ x: number; y: number }>`
|
|
15
|
+
position: fixed;
|
|
16
|
+
top: ${(p) => p.y}px;
|
|
17
|
+
left: ${(p) => p.x}px;
|
|
18
|
+
z-index: 1000;
|
|
19
|
+
background: ${(p) => p.theme.color.uiBackground02};
|
|
20
|
+
border-radius: 4px;
|
|
21
|
+
box-shadow: ${(p) => p.theme.shadow.shadowL};
|
|
22
|
+
padding: ${(p) => p.theme.spacing.s};
|
|
23
|
+
width: 320px;
|
|
24
|
+
max-height: 80vh;
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
export const PopoverButtonWrapper = styled.div`
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: ${(p) => p.theme.spacing.s};
|
|
31
|
+
justify-content: flex-end;
|
|
32
|
+
margin-top: ${(p) => p.theme.spacing.m};
|
|
33
|
+
`;
|
|
34
|
+
|
|
4
35
|
export const EditorWrapper = styled.div<{ error: boolean | undefined; disabled?: boolean }>`
|
|
5
36
|
margin-bottom: ${(p) => p.theme.spacing.xxs};
|
|
6
37
|
.fr-box.fr-basic {
|
|
@@ -8,7 +8,6 @@ import "froala-editor/js/plugins/lists.min.js";
|
|
|
8
8
|
import "froala-editor/js/plugins/code_view.min.js";
|
|
9
9
|
import "froala-editor/js/plugins/code_beautifier.min.js";
|
|
10
10
|
import "froala-editor/js/plugins/image.min.js";
|
|
11
|
-
import "froala-editor/js/plugins/image_manager.min.js";
|
|
12
11
|
import "froala-editor/js/plugins/link.min.js";
|
|
13
12
|
import "froala-editor/js/plugins/video.min.js";
|
|
14
13
|
import "froala-editor/js/plugins/quote.min.js";
|