@capillarytech/creatives-library 8.0.213 → 8.0.214-beta.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/HOW_BEE_EDITOR_WORKS.md +375 -0
- package/constants/unified.js +1 -0
- package/package.json +1 -1
- package/services/api.js +5 -0
- package/utils/common.js +6 -1
- package/v2Components/CapTagList/index.js +2 -1
- package/v2Components/CapTagListWithInput/index.js +5 -1
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/ErrorInfoNote/style.scss +1 -1
- package/v2Components/HtmlEditor/HTMLEditor.js +86 -14
- package/v2Components/HtmlEditor/_htmlEditor.scss +4 -4
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +107 -96
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +68 -92
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Containers/CreativesContainer/SlideBoxContent.js +85 -35
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
- package/v2Containers/CreativesContainer/index.js +107 -35
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/Email/actions.js +7 -0
- package/v2Containers/Email/constants.js +5 -1
- package/v2Containers/Email/index.js +13 -0
- package/v2Containers/Email/messages.js +32 -0
- package/v2Containers/Email/reducer.js +12 -1
- package/v2Containers/Email/sagas.js +17 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1005 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
- package/v2Containers/EmailWrapper/constants.js +2 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +470 -71
- package/v2Containers/EmailWrapper/index.js +102 -23
- package/v2Containers/EmailWrapper/messages.js +61 -1
- package/v2Containers/EmailWrapper/tests/EmailHTMLEditor.test.js +177 -0
- package/v2Containers/EmailWrapper/tests/EmailHTMLEditorValidation.test.js +90 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +49 -49
- package/v2Containers/TagList/index.js +2 -0
- package/v2Containers/Templates/index.js +5 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
Tracing how the BEE editor is loaded in the Email create and edit flows. Searching the codebase for relevant code.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
[5 tools called]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Reviewing key files to understand the flow:
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
[4 tools called]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Reviewing how BEE editor gets initialized with token and JSON data:
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[4 tools called]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Reviewing the edit flow initialization:
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
[1 tool called]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## How BEE Editor is Loaded in Email Create and Edit Flows
|
|
26
|
+
|
|
27
|
+
### Overview
|
|
28
|
+
The BEE editor loads via a multi-step process that fetches CMS settings (token and JSON) and renders the editor component when conditions are met.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## CREATE FLOW
|
|
33
|
+
|
|
34
|
+
### 1. Initial Setup (EmailWrapper)
|
|
35
|
+
Location: `app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js`
|
|
36
|
+
|
|
37
|
+
- User selects "Use Editor" mode
|
|
38
|
+
- When step is `TEMPLATE_SELECTION` and templates are needed:
|
|
39
|
+
```223:230:app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js
|
|
40
|
+
case STEPS.TEMPLATE_SELECTION:
|
|
41
|
+
const needsTemplates = emailCreateMode === EMAIL_CREATE_MODES.EDITOR
|
|
42
|
+
&& !CmsTemplates
|
|
43
|
+
&& !getCmsTemplatesInProgress;
|
|
44
|
+
|
|
45
|
+
if (needsTemplates) {
|
|
46
|
+
templatesActions.getDefaultBeeTemplates();
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
- User selects a template → `handleEdmDefaultTemplateSelection` is called
|
|
50
|
+
- Sets `selectedCreateMode` to `EDITOR` and stores the selected template
|
|
51
|
+
|
|
52
|
+
### 2. Email Component Initialization
|
|
53
|
+
Location: `app/v2Containers/Email/index.js`
|
|
54
|
+
|
|
55
|
+
In `componentWillMount`:
|
|
56
|
+
```208:219:app/v2Containers/Email/index.js
|
|
57
|
+
const isBEESupport = (this.props.location.query.isBEESupport !== "false") || false;
|
|
58
|
+
const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
|
|
59
|
+
if (!_.isEmpty(this.props.Templates.BEETemplate)) {
|
|
60
|
+
if (this.props.Templates.BEETemplate.versions.base.is_drag_drop && isBEEAppEnable ) {
|
|
61
|
+
this.setState({isDragDrop: true});
|
|
62
|
+
}
|
|
63
|
+
if (this.props.params.id) {
|
|
64
|
+
this.props.actions.getCmsSetting(BEE_PLUGIN, _.get(this.props.Templates.BEETemplate, 'versions.base.drag_drop_id', this.props.Templates.BEETemplate?._id), 'open', undefined, isBEESupport, isBEEAppEnable);
|
|
65
|
+
} else if (this.props.location.query.module !== "library" || (this.props.location.query.module === "library" && !this.props.templateData)) {
|
|
66
|
+
this.props.actions.getCmsSetting(BEE_PLUGIN, _.get(this.props.Templates.BEETemplate, 'versions.base.drag_drop_id', this.props.Templates.BEETemplate?._id), 'create', undefined, isBEESupport, isBEEAppEnable);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- For create flow: calls `getCmsSetting` with `cmsMode: 'create'`
|
|
72
|
+
- This fetches the BEE token and settings from the API
|
|
73
|
+
|
|
74
|
+
### 3. Receiving CMS Settings
|
|
75
|
+
Location: `app/v2Containers/Email/index.js` - `componentWillReceiveProps`
|
|
76
|
+
|
|
77
|
+
When `CmsSettings` are received:
|
|
78
|
+
```379:419:app/v2Containers/Email/index.js
|
|
79
|
+
if (!_.isEmpty(nextProps.Email.CmsSettings) && !_.isEqual(this.props.Email.CmsSettings, nextProps.Email.CmsSettings) && !_.isEmpty(this.state.schema)) {
|
|
80
|
+
const apiLangId = nextProps.Email.CmsSettings.langId;
|
|
81
|
+
const langId = nextProps.Email.CmsSettings.langId !== "undefined" ? nextProps.Email.CmsSettings.langId : nextProps.currentOrgDetails.basic_details.base_language;
|
|
82
|
+
|
|
83
|
+
const formData = _.cloneDeep(this.state.formData);
|
|
84
|
+
|
|
85
|
+
const schema = _.cloneDeep(this.state.schema);
|
|
86
|
+
const langIndex = formData[this.state.currentTab - 1].selectedLanguages.indexOf(langId);
|
|
87
|
+
|
|
88
|
+
const temp = (schema.containers || {})[this.state.currentTab - 1];
|
|
89
|
+
|
|
90
|
+
const { currentTab } = this.state;
|
|
91
|
+
if (nextProps.Email.CmsSettings.isDragDrop && this.checkBeeEditorAllowedForLibrary()) {
|
|
92
|
+
const beeJson = `BEEeditor${currentTab > 1 ? currentTab : ''}json`;
|
|
93
|
+
const beeToken = `BEEeditor${currentTab > 1 ? currentTab : ''}token`;
|
|
94
|
+
let beeJsonValue = _.get(nextProps, 'Templates.BEETemplate.versions.base.json-content', '');
|
|
95
|
+
const selectedId = _.get(this.props, 'Email.templateDetails._id', '') || _.get(this.props, 'Templates.BEETemplate.versions.base.drag_drop_id', '');
|
|
96
|
+
if (this.state.isEdit) {
|
|
97
|
+
if (this.props.location.query.module === "library") {
|
|
98
|
+
beeJsonValue = _.get(this.state, `formData[${currentTab - 1}][${langId}].json-content`, '');
|
|
99
|
+
} else {
|
|
100
|
+
beeJsonValue = _.get(nextProps, `Email.templateDetails.versions.base[${langId}].json-content`, '');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
formData[`${currentTab - 1}`][langId] = {
|
|
104
|
+
...formData[`${currentTab - 1}`][langId],
|
|
105
|
+
is_drag_drop: true,
|
|
106
|
+
[beeJson]: beeJsonValue,
|
|
107
|
+
[beeToken]: nextProps.Email.CmsSettings.tokenData,
|
|
108
|
+
id: selectedId,
|
|
109
|
+
};
|
|
110
|
+
_.forEach(temp.panes, (pane, index) => {
|
|
111
|
+
const tempPane = pane;
|
|
112
|
+
//
|
|
113
|
+
if (parseInt(index, 10) === parseInt(langIndex, 10)) {
|
|
114
|
+
//
|
|
115
|
+
tempPane.sections[0].inputFields[0].cols[1].colStyle = {display: ""};
|
|
116
|
+
tempPane.sections[0].inputFields[0].cols[0].colStyle = {display: "none"};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
this.setState({schema, isSchemaChanged: true, loadingStatus: this.state.loadingStatus + 1});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- Stores `tokenData` and `json-content` in formData
|
|
123
|
+
- Updates schema to show BEE editor (hide CKEditor)
|
|
124
|
+
- Sets `is_drag_drop: true`
|
|
125
|
+
|
|
126
|
+
### 4. FormBuilder Renders BEE Editor
|
|
127
|
+
Location: `app/v2Components/FormBuilder/index.js`
|
|
128
|
+
|
|
129
|
+
When schema type is `"BEEeditor"`:
|
|
130
|
+
```3976:4027:app/v2Components/FormBuilder/index.js
|
|
131
|
+
case "BEEeditor": {
|
|
132
|
+
let langTab = 1;
|
|
133
|
+
if (val.id.match(/BEEeditor/g)) {
|
|
134
|
+
if (val.id.length > 9 && val.id.charAt(9)) {
|
|
135
|
+
langTab = val.id.charAt(9);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const { currentTab, formData } = this.state;
|
|
139
|
+
const supportedLanguages = (formData[currentTab - 1] || {}).selectedLanguages || {};
|
|
140
|
+
const currentLang = supportedLanguages[langTab - 1];
|
|
141
|
+
|
|
142
|
+
let beeJson = '',
|
|
143
|
+
beeToken = '',
|
|
144
|
+
uuid = '';
|
|
145
|
+
const data = formData[`${currentTab - 1}`][currentLang];
|
|
146
|
+
if (data) {
|
|
147
|
+
beeJson = data[`BEEeditor${currentTab > 1 ? currentTab : ''}json`];
|
|
148
|
+
beeToken = data[`BEEeditor${currentTab > 1 ? currentTab : ''}token`];
|
|
149
|
+
uuid = this.props.uuid;
|
|
150
|
+
}
|
|
151
|
+
if (!beeJson || !beeToken) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
let isModuleFilterEnabled = _.get(this.props.location, 'query.type', '') !== 'embedded';
|
|
155
|
+
const schemaChannel = _.get(this.props.schema, 'channel', "");
|
|
156
|
+
if (schemaChannel === 'EMAIL') {
|
|
157
|
+
isModuleFilterEnabled = this.props.isFullMode;
|
|
158
|
+
}
|
|
159
|
+
columns.push(
|
|
160
|
+
<CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow() && "error-boundary"}`}>
|
|
161
|
+
<BeeEditor
|
|
162
|
+
uid={uuid}
|
|
163
|
+
tokenData={beeToken}
|
|
164
|
+
id={val.id}
|
|
165
|
+
beeJson={beeJson}
|
|
166
|
+
showTagsPopover={this.showTagsPopover}
|
|
167
|
+
location={this.props.location}
|
|
168
|
+
label={val.label || ''}
|
|
169
|
+
className={val.className || ''}
|
|
170
|
+
userLocale={this.props.userLocale}
|
|
171
|
+
selectedOfferDetails={this.props.selectedOfferDetails}
|
|
172
|
+
tags={this.props.tags || []}
|
|
173
|
+
injectedTags={this.props.injectedTags ? this.props.injectedTags : {}}
|
|
174
|
+
saveBeeInstance={this.props.saveBeeInstance}
|
|
175
|
+
saveBeeData={this.props.saveBeeData}
|
|
176
|
+
onContextChange={this.props.onContextChange}
|
|
177
|
+
moduleFilterEnabled={isModuleFilterEnabled}
|
|
178
|
+
eventContextTags={this.props?.eventContextTags}
|
|
179
|
+
/>
|
|
180
|
+
</CapColumn>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- Extracts `beeJson` and `beeToken` from formData
|
|
186
|
+
- Renders `<BeeEditor>` with these props
|
|
187
|
+
|
|
188
|
+
### 5. BeeEditor Component Initialization
|
|
189
|
+
Location: `app/v2Containers/BeeEditor/index.js`
|
|
190
|
+
|
|
191
|
+
In `useEffect`:
|
|
192
|
+
```107:197:app/v2Containers/BeeEditor/index.js
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
const beeConfig = {
|
|
195
|
+
uid,
|
|
196
|
+
container: 'bee-plugin-container',
|
|
197
|
+
translationsUrl: defaultFormattedUrl,
|
|
198
|
+
rowsConfiguration: {
|
|
199
|
+
emptyRows: true,
|
|
200
|
+
defaultRows: true,
|
|
201
|
+
externalContentURLs: getExternalContentURLs(),
|
|
202
|
+
},
|
|
203
|
+
contentDialog: {
|
|
204
|
+
specialLinks: {
|
|
205
|
+
label: UNSUBSCRIBE,
|
|
206
|
+
handler(resolve) {
|
|
207
|
+
resolve({
|
|
208
|
+
type: 'custom',
|
|
209
|
+
label: UNSUBSCRIBE,
|
|
210
|
+
link: `{{${UNSUBSCRIBE}}}`,
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
mergeTags: {
|
|
215
|
+
label: formatMessage(messages.addLabel),
|
|
216
|
+
handler: async (resolve, reject) => {
|
|
217
|
+
// this will open tag modal
|
|
218
|
+
await setVisibleTaglist(true);
|
|
219
|
+
// until tag modal will not open promise will not execute
|
|
220
|
+
// once tag modal is opened it will start 2 sec interval to wait use has selected any tag or cancel the tag selection
|
|
221
|
+
const promise = new Promise((resolveP) => {
|
|
222
|
+
intervalTimer = setInterval(() => {
|
|
223
|
+
// this will execute, if user cancel the tag selection
|
|
224
|
+
if ((savedCallback.current || {}).close === true) {
|
|
225
|
+
reject();
|
|
226
|
+
clearInterval(intervalTimer);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// this block is checking use has selected any tag or not
|
|
230
|
+
if (Object.keys(savedCallback.current || {}).length > 0) {
|
|
231
|
+
resolveP(savedCallback.current);
|
|
232
|
+
setSelectedTag({});
|
|
233
|
+
clearInterval(intervalTimer);
|
|
234
|
+
}
|
|
235
|
+
}, 2000);
|
|
236
|
+
});
|
|
237
|
+
// once prmise will resolve , pass the resolve data to handler to show tags in bee edior
|
|
238
|
+
const result = await promise;
|
|
239
|
+
resolve(result);
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
saveRow: {
|
|
243
|
+
handler: async (resolve, reject) => {
|
|
244
|
+
await setRowMetaModal(true);
|
|
245
|
+
const promise = new Promise((resolveP) => {
|
|
246
|
+
intervalTimer = setInterval(() => {
|
|
247
|
+
if ((savedCallback.current || {}).close === true) {
|
|
248
|
+
reject();
|
|
249
|
+
clearInterval(intervalTimer);
|
|
250
|
+
setRowMetaInfo({});
|
|
251
|
+
setRowName('');
|
|
252
|
+
setRowCategory('');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (Object.keys(savedCallback.current || {}).length > 0) {
|
|
256
|
+
resolveP(savedCallback.current);
|
|
257
|
+
}
|
|
258
|
+
}, 2000);
|
|
259
|
+
});
|
|
260
|
+
const result = await promise;
|
|
261
|
+
resolve(result); // "done!"
|
|
262
|
+
setRowMetaInfo({});
|
|
263
|
+
setRowName('');
|
|
264
|
+
setRowCategory('');
|
|
265
|
+
clearInterval(intervalTimer);
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
onSave: (jsonFile, htmlFile) => {
|
|
270
|
+
saveBeeData(jsonFile, htmlFile);
|
|
271
|
+
},
|
|
272
|
+
onSaveRow: (rowJSON) => {
|
|
273
|
+
actions.createCustomRow(JSON.parse(rowJSON), callbackSaveRow);
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
window.BeePlugin.create(tokenData, beeConfig, (instance) => {
|
|
277
|
+
beePluginInstance = instance;
|
|
278
|
+
const parseJson = JSON.parse(beeJson);
|
|
279
|
+
beePluginInstance.start(parseJson);
|
|
280
|
+
saveBeeInstance(beePluginInstance);
|
|
281
|
+
});
|
|
282
|
+
return () => clearInterval(intervalTimer);
|
|
283
|
+
}, []);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
- Creates BEE plugin instance with `tokenData`
|
|
287
|
+
- Starts editor with parsed `beeJson`
|
|
288
|
+
- Saves instance reference
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## EDIT FLOW
|
|
293
|
+
|
|
294
|
+
### 1. Component Initialization
|
|
295
|
+
Location: `app/v2Containers/Email/index.js`
|
|
296
|
+
|
|
297
|
+
In `componentWillMount`:
|
|
298
|
+
```193:199:app/v2Containers/Email/index.js
|
|
299
|
+
if (this.props.params.id || (this.props.location.query.module === "library" && this.props.templateData && !_.isEmpty(this.props.templateData))) {
|
|
300
|
+
this.setState({isEdit: true}, () => {
|
|
301
|
+
if (this.props.params.id) {
|
|
302
|
+
this.props.actions.getTemplateDetails(this.props.params.id, 'email');
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
- Sets `isEdit: true`
|
|
309
|
+
- Fetches template details if `params.id` exists
|
|
310
|
+
|
|
311
|
+
### 2. Setting Edit Data
|
|
312
|
+
Location: `app/v2Containers/Email/index.js` - `setEditData`
|
|
313
|
+
|
|
314
|
+
After template details load:
|
|
315
|
+
```992:1003:app/v2Containers/Email/index.js
|
|
316
|
+
const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
|
|
317
|
+
_.forEach((editData.versions.base.selectedLanguages), (language) => {
|
|
318
|
+
// if (formData[this.state.currentTab - 1].tabKey === formData[this.state.currentTab - 1][language].tabKey) {
|
|
319
|
+
// formData[this.state.currentTab - 1].activeTab = language;
|
|
320
|
+
// }
|
|
321
|
+
// if (formData.base.tabKey === formData.base[language].tabKey) {
|
|
322
|
+
// formData.base.activeTab = language;
|
|
323
|
+
// }
|
|
324
|
+
if (language && editData.versions.base[language].is_drag_drop && isBEEAppEnable) {
|
|
325
|
+
this.props.actions.getCmsSetting(BEE_PLUGIN, editData._id, 'open', language, isBEESupport, isBEEAppEnable);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
- For each language with `is_drag_drop: true`, calls `getCmsSetting` with `cmsMode: 'open'` and the template `_id`
|
|
331
|
+
|
|
332
|
+
### 3. Receiving CMS Settings (Same as Create)
|
|
333
|
+
When `CmsSettings` are received, the same logic applies:
|
|
334
|
+
- Stores token and JSON in formData
|
|
335
|
+
- Updates schema to show BEE editor
|
|
336
|
+
- For edit, uses existing `json-content` from template data:
|
|
337
|
+
```396:401:app/v2Containers/Email/index.js
|
|
338
|
+
if (this.state.isEdit) {
|
|
339
|
+
if (this.props.location.query.module === "library") {
|
|
340
|
+
beeJsonValue = _.get(this.state, `formData[${currentTab - 1}][${langId}].json-content`, '');
|
|
341
|
+
} else {
|
|
342
|
+
beeJsonValue = _.get(nextProps, `Email.templateDetails.versions.base[${langId}].json-content`, '');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 4. FormBuilder Renders BEE Editor (Same as Create)
|
|
348
|
+
Same rendering logic as create flow.
|
|
349
|
+
|
|
350
|
+
### 5. BeeEditor Component Initialization (Same as Create)
|
|
351
|
+
Same initialization logic as create flow.
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Key Differences: Create vs Edit
|
|
356
|
+
|
|
357
|
+
| Aspect | Create Flow | Edit Flow |
|
|
358
|
+
|--------|-------------|-----------|
|
|
359
|
+
| CMS Mode | `'create'` | `'open'` |
|
|
360
|
+
| Template ID | From `BEETemplate.versions.base.drag_drop_id` | From `editData._id` |
|
|
361
|
+
| JSON Content | From `BEETemplate.versions.base.json-content` (template default) | From `templateDetails.versions.base[langId].json-content` (saved template) |
|
|
362
|
+
| Trigger | User selects template in EmailWrapper | Template details loaded in `setEditData` |
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## API Flow
|
|
367
|
+
|
|
368
|
+
1. `getCmsSetting` action → calls `getCmsTemplateSettingsV2` API
|
|
369
|
+
2. API returns: `tokenData`, `isDragDrop`, `uuid`, `langId`
|
|
370
|
+
3. Redux stores result in `Email.CmsSettings`
|
|
371
|
+
4. Component receives props → updates formData and schema
|
|
372
|
+
5. FormBuilder renders BeeEditor with token and JSON
|
|
373
|
+
6. BeeEditor initializes BEE plugin instance
|
|
374
|
+
|
|
375
|
+
This flow ensures the BEE editor loads with the correct authentication token and template content for both create and edit scenarios.
|
package/constants/unified.js
CHANGED
|
@@ -45,6 +45,7 @@ export const GIFT_CARDS = 'GIFT_CARDS';
|
|
|
45
45
|
export const PROMO_ENGINE = 'PROMO_ENGINE';
|
|
46
46
|
export const LIQUID_SUPPORT = 'ENABLE_LIQUID_SUPPORT';
|
|
47
47
|
export const ENABLE_NEW_MPUSH = 'ENABLE_NEW_MPUSH';
|
|
48
|
+
export const SUPPORT_CK_EDITOR = 'SUPPORT_CK_EDITOR';
|
|
48
49
|
export const CUSTOM_TAG = 'CustomTagMessage';
|
|
49
50
|
export const CUSTOMER_EXTENDED_FIELD = 'Customer extended fields';
|
|
50
51
|
export const EXTENDED_TAG = 'ExtendedTagMessage';
|
package/package.json
CHANGED
package/services/api.js
CHANGED
|
@@ -465,6 +465,11 @@ export const getCmsTemplateSettingsV2 = (cmsType, projectId, cmsMode, langId, is
|
|
|
465
465
|
return API.get(url);
|
|
466
466
|
};
|
|
467
467
|
|
|
468
|
+
export const getCmsAccounts = (cmsType) => {
|
|
469
|
+
const url = `${API_ENDPOINT}/cms/accounts?type=${cmsType}`;
|
|
470
|
+
return API.get(url);
|
|
471
|
+
};
|
|
472
|
+
|
|
468
473
|
export const getCmsTemplateData = (cmsType, projectId, langId) => {
|
|
469
474
|
const url = `${API_ENDPOINT}/cms/getContent?type=${cmsType}&projectId=${projectId}&langId=${langId}`;
|
|
470
475
|
return API.get(url);
|
package/utils/common.js
CHANGED
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
BADGES_ISSUE,
|
|
23
23
|
ENABLE_WECHAT,
|
|
24
24
|
LIQUID_SUPPORT,
|
|
25
|
-
ENABLE_NEW_MPUSH
|
|
25
|
+
ENABLE_NEW_MPUSH,
|
|
26
|
+
SUPPORT_CK_EDITOR
|
|
26
27
|
} from '../constants/unified';
|
|
27
28
|
import { apiMessageFormatHandler } from './commonUtils';
|
|
28
29
|
|
|
@@ -95,6 +96,10 @@ export const hasLiquidSupportFeature = Auth.hasFeatureAccess.bind(
|
|
|
95
96
|
LIQUID_SUPPORT,
|
|
96
97
|
);
|
|
97
98
|
|
|
99
|
+
export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
|
|
100
|
+
null,
|
|
101
|
+
SUPPORT_CK_EDITOR,
|
|
102
|
+
);
|
|
98
103
|
|
|
99
104
|
export const hasGiftVoucherFeature = Auth.hasFeatureAccess.bind(
|
|
100
105
|
null,
|
|
@@ -468,7 +468,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
468
468
|
onVisibleChange={this.togglePopoverVisibility}
|
|
469
469
|
content={contentSection}
|
|
470
470
|
trigger="click"
|
|
471
|
-
placement={channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop"}
|
|
471
|
+
placement={this.props.popoverPlacement || (channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop")}
|
|
472
472
|
>
|
|
473
473
|
<CapTooltip
|
|
474
474
|
title={
|
|
@@ -535,6 +535,7 @@ CapTagList.propTypes = {
|
|
|
535
535
|
channel: PropTypes.string,
|
|
536
536
|
disabled: PropTypes.bool,
|
|
537
537
|
fetchingSchemaError: PropTypes.bool,
|
|
538
|
+
popoverPlacement: PropTypes.string,
|
|
538
539
|
};
|
|
539
540
|
|
|
540
541
|
CapTagList.defaultValue = {
|
|
@@ -48,13 +48,14 @@ export const CapTagListWithInput = (props) => {
|
|
|
48
48
|
showTagList = true,
|
|
49
49
|
showInput = true,
|
|
50
50
|
inputProps = {},
|
|
51
|
+
popoverPlacement,
|
|
51
52
|
} = props;
|
|
52
53
|
|
|
53
54
|
const { formatMessage } = intl;
|
|
54
55
|
|
|
55
56
|
return (
|
|
56
57
|
<CapColumn style={containerStyle}>
|
|
57
|
-
<CapRow
|
|
58
|
+
<CapRow align="middle" type="flex">
|
|
58
59
|
{showHeading && headingText && (
|
|
59
60
|
<CapHeading type={headingType} style={headingStyle}>
|
|
60
61
|
{headingText}
|
|
@@ -76,6 +77,7 @@ export const CapTagListWithInput = (props) => {
|
|
|
76
77
|
selectedOfferDetails={selectedOfferDetails}
|
|
77
78
|
eventContextTags={eventContextTags}
|
|
78
79
|
style={tagListStyle}
|
|
80
|
+
popoverPlacement={popoverPlacement}
|
|
79
81
|
/>
|
|
80
82
|
)}
|
|
81
83
|
</CapRow>
|
|
@@ -136,6 +138,7 @@ CapTagListWithInput.propTypes = {
|
|
|
136
138
|
showHeading: PropTypes.bool,
|
|
137
139
|
showTagList: PropTypes.bool,
|
|
138
140
|
showInput: PropTypes.bool,
|
|
141
|
+
popoverPlacement: PropTypes.string,
|
|
139
142
|
};
|
|
140
143
|
|
|
141
144
|
CapTagListWithInput.defaultProps = {
|
|
@@ -164,6 +167,7 @@ CapTagListWithInput.defaultProps = {
|
|
|
164
167
|
showTagList: true,
|
|
165
168
|
showInput: true,
|
|
166
169
|
inputProps: {},
|
|
170
|
+
popoverPlacement: undefined,
|
|
167
171
|
};
|
|
168
172
|
|
|
169
173
|
export default injectIntl(CapTagListWithInput);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Note: Uses injectIntl with forwardRef to provide direct access to CodeEditorPane via ref
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import React, { useRef, useCallback, useMemo, useState } from 'react';
|
|
15
|
+
import React, { useRef, useCallback, useMemo, useState, useEffect } from 'react';
|
|
16
16
|
import PropTypes from 'prop-types';
|
|
17
17
|
import { Layout } from 'antd'; // Fallback - no Cap UI equivalent
|
|
18
18
|
import { injectIntl, intlShape } from 'react-intl';
|
|
@@ -61,6 +61,16 @@ const HTMLEditor = ({
|
|
|
61
61
|
showFullscreenButton = true,
|
|
62
62
|
autoSave = true,
|
|
63
63
|
autoSaveInterval = 30000, // 30 seconds
|
|
64
|
+
// Tag-related props - tags are fetched and managed by parent component (EmailHTMLEditor, INAPP, etc.)
|
|
65
|
+
tags = [],
|
|
66
|
+
injectedTags = {},
|
|
67
|
+
location,
|
|
68
|
+
eventContextTags = [],
|
|
69
|
+
selectedOfferDetails = [],
|
|
70
|
+
channel,
|
|
71
|
+
userLocale = 'en',
|
|
72
|
+
moduleFilterEnabled = true,
|
|
73
|
+
onTagContextChange, // Parent component handles tag fetching
|
|
64
74
|
...props
|
|
65
75
|
}) => {
|
|
66
76
|
// Separate refs for main and modal editors to avoid conflicts
|
|
@@ -117,6 +127,29 @@ const HTMLEditor = ({
|
|
|
117
127
|
// Use appropriate content hook based on variant
|
|
118
128
|
const content = variant === HTML_EDITOR_VARIANTS.EMAIL ? emailContent : inAppContent;
|
|
119
129
|
|
|
130
|
+
// Update content when initialContent prop changes (for edit mode)
|
|
131
|
+
// This ensures the editor updates when template data loads
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (isEmailVariant && emailContent && initialContent !== undefined && initialContent !== null) {
|
|
134
|
+
// Only update if content is different to avoid unnecessary updates
|
|
135
|
+
if (emailContent.content !== initialContent) {
|
|
136
|
+
emailContent.updateContent(initialContent, true); // immediate update
|
|
137
|
+
}
|
|
138
|
+
} else if (isInAppVariant && inAppContent && initialContent !== undefined && initialContent !== null) {
|
|
139
|
+
// Handle InApp variant updates
|
|
140
|
+
const contentToUpdate = typeof initialContent === 'string'
|
|
141
|
+
? { [DEVICE_TYPES.ANDROID]: initialContent, [DEVICE_TYPES.IOS]: initialContent }
|
|
142
|
+
: initialContent;
|
|
143
|
+
if (inAppContent.updateContent) {
|
|
144
|
+
const currentContent = inAppContent.getDeviceContent?.(inAppContent.activeDevice);
|
|
145
|
+
const newContent = contentToUpdate[inAppContent.activeDevice] || contentToUpdate[DEVICE_TYPES.ANDROID] || '';
|
|
146
|
+
if (currentContent !== newContent) {
|
|
147
|
+
inAppContent.updateContent(newContent, true);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [initialContent, isEmailVariant, isInAppVariant]);
|
|
152
|
+
|
|
120
153
|
// Destructure content properties for cleaner access throughout component
|
|
121
154
|
const {
|
|
122
155
|
activeDevice,
|
|
@@ -387,6 +420,15 @@ const HTMLEditor = ({
|
|
|
387
420
|
ref={mainEditorRef}
|
|
388
421
|
readOnly={readOnly}
|
|
389
422
|
onLabelInsert={handleLabelInsert}
|
|
423
|
+
tags={tags}
|
|
424
|
+
injectedTags={injectedTags}
|
|
425
|
+
location={location}
|
|
426
|
+
eventContextTags={eventContextTags}
|
|
427
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
428
|
+
channel={channel}
|
|
429
|
+
userLocale={userLocale}
|
|
430
|
+
moduleFilterEnabled={moduleFilterEnabled}
|
|
431
|
+
onTagContextChange={onTagContextChange}
|
|
390
432
|
/>
|
|
391
433
|
|
|
392
434
|
{/* Preview Pane */}
|
|
@@ -454,19 +496,28 @@ const HTMLEditor = ({
|
|
|
454
496
|
readOnly={readOnly}
|
|
455
497
|
isFullscreenMode={true}
|
|
456
498
|
onLabelInsert={handleLabelInsert}
|
|
499
|
+
tags={tags}
|
|
500
|
+
injectedTags={injectedTags}
|
|
501
|
+
location={location}
|
|
502
|
+
eventContextTags={eventContextTags}
|
|
503
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
504
|
+
channel={channel}
|
|
505
|
+
userLocale={userLocale}
|
|
506
|
+
moduleFilterEnabled={moduleFilterEnabled}
|
|
507
|
+
onTagContextChange={onTagContextChange}
|
|
457
508
|
/>
|
|
458
509
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
510
|
+
{/* Preview Pane */}
|
|
511
|
+
<PreviewPane isFullscreenMode={true} isModalContext={true} />
|
|
512
|
+
</SplitContainer>
|
|
462
513
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
514
|
+
{/* Validation Display in Modal */}
|
|
515
|
+
<ValidationErrorDisplay
|
|
516
|
+
validation={validation}
|
|
517
|
+
onErrorClick={handleValidationErrorClick}
|
|
518
|
+
variant={variant}
|
|
519
|
+
className="html-editor-validation"
|
|
520
|
+
/>
|
|
470
521
|
</CapRow>
|
|
471
522
|
</CapRow>
|
|
472
523
|
</CapModal>
|
|
@@ -481,7 +532,7 @@ HTMLEditor.propTypes = {
|
|
|
481
532
|
layoutType: PropTypes.string, // Layout type for InApp variant
|
|
482
533
|
initialContent: PropTypes.oneOfType([
|
|
483
534
|
PropTypes.string,
|
|
484
|
-
PropTypes.objectOf(PropTypes.string) // Per-device content for INAPP variant
|
|
535
|
+
PropTypes.objectOf(PropTypes.string), // Per-device content for INAPP variant
|
|
485
536
|
]),
|
|
486
537
|
onSave: PropTypes.func,
|
|
487
538
|
onContentChange: PropTypes.func,
|
|
@@ -489,11 +540,22 @@ HTMLEditor.propTypes = {
|
|
|
489
540
|
readOnly: PropTypes.bool,
|
|
490
541
|
showFullscreenButton: PropTypes.bool,
|
|
491
542
|
autoSave: PropTypes.bool,
|
|
492
|
-
autoSaveInterval: PropTypes.number
|
|
543
|
+
autoSaveInterval: PropTypes.number,
|
|
544
|
+
// Tag-related props - tags are fetched and managed by parent component
|
|
545
|
+
tags: PropTypes.array,
|
|
546
|
+
injectedTags: PropTypes.object,
|
|
547
|
+
location: PropTypes.object,
|
|
548
|
+
eventContextTags: PropTypes.array,
|
|
549
|
+
selectedOfferDetails: PropTypes.array,
|
|
550
|
+
channel: PropTypes.string,
|
|
551
|
+
userLocale: PropTypes.string,
|
|
552
|
+
moduleFilterEnabled: PropTypes.bool,
|
|
553
|
+
onTagContextChange: PropTypes.func, // Required - parent must handle tag fetching
|
|
493
554
|
};
|
|
494
555
|
|
|
495
556
|
HTMLEditor.defaultProps = {
|
|
496
557
|
variant: HTML_EDITOR_VARIANTS.EMAIL, // Default to email variant
|
|
558
|
+
layoutType: null,
|
|
497
559
|
initialContent: null, // Will use default from useEditorContent hook
|
|
498
560
|
onSave: null,
|
|
499
561
|
onContentChange: null,
|
|
@@ -501,7 +563,17 @@ HTMLEditor.defaultProps = {
|
|
|
501
563
|
readOnly: false,
|
|
502
564
|
showFullscreenButton: true,
|
|
503
565
|
autoSave: true,
|
|
504
|
-
autoSaveInterval: 30000
|
|
566
|
+
autoSaveInterval: 30000,
|
|
567
|
+
// Tag-related defaults - tags are fetched and managed by parent component
|
|
568
|
+
tags: [],
|
|
569
|
+
injectedTags: {},
|
|
570
|
+
location: null,
|
|
571
|
+
eventContextTags: [],
|
|
572
|
+
selectedOfferDetails: [],
|
|
573
|
+
channel: null,
|
|
574
|
+
userLocale: 'en',
|
|
575
|
+
moduleFilterEnabled: true,
|
|
576
|
+
onTagContextChange: null, // Parent component should provide this
|
|
505
577
|
};
|
|
506
578
|
|
|
507
579
|
// Export with forwardRef to allow direct access to CodeEditorPane via ref
|
|
@@ -267,10 +267,10 @@
|
|
|
267
267
|
|
|
268
268
|
// Focus states and accessibility
|
|
269
269
|
.html-editor {
|
|
270
|
-
&:focus-within {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
270
|
+
// &:focus-within {
|
|
271
|
+
// outline: 0.125rem solid map-get($CAP_PRIMARY, base); // 2px = 0.125rem
|
|
272
|
+
// outline-offset: -0.125rem; // -2px = -0.125rem
|
|
273
|
+
// }
|
|
274
274
|
|
|
275
275
|
// High contrast mode support
|
|
276
276
|
@media (prefers-contrast: high) {
|