@capillarytech/creatives-library 8.0.285-alpha.1 → 8.0.286
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/constants/unified.js +1 -0
- package/initialState.js +2 -0
- package/package.json +1 -1
- package/utils/common.js +8 -5
- package/utils/commonUtils.js +83 -2
- package/utils/tagValidations.js +222 -84
- package/utils/tests/commonUtil.test.js +118 -147
- package/utils/tests/tagValidations.test.js +358 -280
- package/v2Components/ErrorInfoNote/index.js +5 -2
- package/v2Components/FormBuilder/index.js +158 -64
- package/v2Components/FormBuilder/messages.js +8 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
- package/v2Containers/Cap/mockData.js +14 -0
- package/v2Containers/Cap/reducer.js +55 -3
- package/v2Containers/Cap/tests/reducer.test.js +102 -0
- package/v2Containers/CreativesContainer/index.js +1 -0
- package/v2Containers/Email/index.js +5 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +62 -10
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +115 -12
- package/v2Containers/FTP/index.js +51 -2
- package/v2Containers/FTP/messages.js +4 -0
- package/v2Containers/InApp/index.js +96 -1
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +103 -2
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePushNew/index.js +33 -2
- package/v2Containers/Rcs/index.js +37 -12
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +18 -4
- package/v2Containers/SmsTrai/Create/index.scss +1 -1
- package/v2Containers/SmsTrai/Edit/index.js +47 -6
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/Viber/index.js +1 -0
- package/v2Containers/Viber/index.scss +1 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +9 -18
- package/v2Containers/WebPush/Create/utils/validation.test.js +24 -0
- package/v2Containers/Whatsapp/index.js +17 -9
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +624 -248
- package/v2Containers/Zalo/index.js +11 -3
|
@@ -186,6 +186,7 @@ export const ErrorInfoNote = (props) => {
|
|
|
186
186
|
errorMessages,
|
|
187
187
|
onErrorClick,
|
|
188
188
|
onClose,
|
|
189
|
+
isLiquidEnabled = true,
|
|
189
190
|
intl,
|
|
190
191
|
useLegacyDisplay = false, // Use simple list display instead of tabs (for BEE Editor)
|
|
191
192
|
} = props;
|
|
@@ -229,7 +230,7 @@ export const ErrorInfoNote = (props) => {
|
|
|
229
230
|
const standardErrors = Array.isArray(rawStandardErrors) ? rawStandardErrors : [];
|
|
230
231
|
const liquidErrors = Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [];
|
|
231
232
|
const hasStandardErrors = standardErrors.length > 0;
|
|
232
|
-
const hasLiquidErrors = liquidErrors.length > 0;
|
|
233
|
+
const hasLiquidErrors = liquidErrors.length > 0 && isLiquidEnabled;
|
|
233
234
|
|
|
234
235
|
if (!hasStandardErrors && !hasLiquidErrors) {
|
|
235
236
|
return null;
|
|
@@ -356,7 +357,7 @@ export const ErrorInfoNote = (props) => {
|
|
|
356
357
|
className="error-info-note__tabs"
|
|
357
358
|
/>
|
|
358
359
|
<CapRow className="error-info-note__actions">
|
|
359
|
-
{hasLiquidErrors && (
|
|
360
|
+
{hasLiquidErrors && isLiquidEnabled && (
|
|
360
361
|
<CapButton
|
|
361
362
|
type="flat"
|
|
362
363
|
className="error-info-note__liquid-doc"
|
|
@@ -451,6 +452,7 @@ ErrorInfoNote.defaultProps = {
|
|
|
451
452
|
},
|
|
452
453
|
onErrorClick: null,
|
|
453
454
|
onClose: null,
|
|
455
|
+
isLiquidEnabled: true,
|
|
454
456
|
intl: null,
|
|
455
457
|
useLegacyDisplay: false, // Use simple list display for BEE Editor
|
|
456
458
|
};
|
|
@@ -477,6 +479,7 @@ ErrorInfoNote.propTypes = {
|
|
|
477
479
|
}),
|
|
478
480
|
onErrorClick: PropTypes.func,
|
|
479
481
|
onClose: PropTypes.func,
|
|
482
|
+
isLiquidEnabled: PropTypes.bool,
|
|
480
483
|
intl: PropTypes.object,
|
|
481
484
|
useLegacyDisplay: PropTypes.bool, // Use simple list display for BEE Editor
|
|
482
485
|
};
|
|
@@ -50,7 +50,7 @@ import { makeSelectMetaEntities, selectCurrentOrgDetails, selectLiquidStateDetai
|
|
|
50
50
|
import * as actions from "../../v2Containers/Cap/actions";
|
|
51
51
|
import './_formBuilder.scss';
|
|
52
52
|
import {updateCharCount, checkUnicode} from "../../utils/smsCharCountV2";
|
|
53
|
-
import { preprocessHtml,
|
|
53
|
+
import { checkSupport, extractNames, preprocessHtml, validateIfTagClosed, isInsideLiquidBlock} from '../../utils/tagValidations';
|
|
54
54
|
import { containsBase64Images } from '../../utils/content';
|
|
55
55
|
import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,AI_CONTENT_BOT_DISABLED, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
|
|
56
56
|
import globalMessages from '../../v2Containers/Cap/messages';
|
|
@@ -60,7 +60,7 @@ import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
|
|
|
60
60
|
import moment from 'moment';
|
|
61
61
|
import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS} from '../../constants/unified';
|
|
62
62
|
import { REQUEST } from '../../v2Containers/Cap/constants'
|
|
63
|
-
import {
|
|
63
|
+
import { hasLiquidSupportFeature, isEmailUnsubscribeTagMandatory } from '../../utils/common';
|
|
64
64
|
import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
|
|
65
65
|
import { bindActionCreators } from 'redux';
|
|
66
66
|
import { getChannelData, validateLiquidTemplateContent, validateMobilePushContent } from '../../utils/commonUtils';
|
|
@@ -72,8 +72,10 @@ const {CapRadioGroup} = CapRadio;
|
|
|
72
72
|
|
|
73
73
|
const tagsTypes = {
|
|
74
74
|
MISSING_TAGS: 'missingTags',
|
|
75
|
+
UNSUPPORTED_TAGS: 'unsupportedTags',
|
|
75
76
|
};
|
|
76
77
|
const errorMessageForTags = {
|
|
78
|
+
UNSUPPORTED_TAG_ERROR: 'unsupportedTagsError',
|
|
77
79
|
MISSING_TAG_ERROR: 'missingTagsError',
|
|
78
80
|
GENERIC_VALIDATION_ERROR: 'genericValidationError',
|
|
79
81
|
TAG_BRACKET_COUNT_MISMATCH_ERROR: 'tagBracketCountMismatchError'
|
|
@@ -136,7 +138,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
136
138
|
this.handleSetRadioValue = this.handleSetRadioValue.bind(this);
|
|
137
139
|
this.formElements = [];
|
|
138
140
|
// Check if the liquid flow feature is supported and the channel is in the supported list.
|
|
139
|
-
this.
|
|
141
|
+
this.liquidFlow = this.isLiquidFlowSupported.bind(this);
|
|
140
142
|
this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
|
|
141
143
|
|
|
142
144
|
// Performance optimization: Debounced functions for high-frequency updates
|
|
@@ -328,8 +330,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
328
330
|
return updatedFormData;
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
|
|
332
|
-
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()));
|
|
333
|
+
isLiquidFlowSupported = () => {
|
|
334
|
+
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()) && hasLiquidSupportFeature());
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
componentWillMount() {
|
|
@@ -724,19 +726,17 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
724
726
|
|
|
725
727
|
let tagValidationResponse = false;
|
|
726
728
|
if (content) {
|
|
727
|
-
tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
|
|
729
|
+
tagValidationResponse = this.validateTags(content, tags, injectedTags, false, this.props?.isFullMode);
|
|
728
730
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
? tagValidationResponse
|
|
732
|
-
: { valid: false, missingTags: [], isBraceError: false };
|
|
733
|
-
if (tagResult.valid) {
|
|
731
|
+
|
|
732
|
+
if (tagValidationResponse.valid) {
|
|
734
733
|
errorData[count][`sms-editor${index > 1 ? index : ''}`] = false;
|
|
735
734
|
} else {
|
|
736
|
-
|
|
737
|
-
const {
|
|
738
|
-
|
|
739
|
-
errorData[count][`
|
|
735
|
+
errorData[count]['invalid-tags'] = tagValidationResponse.unsupportedTags;
|
|
736
|
+
const { MISSING_TAG_ERROR, UNSUPPORTED_TAG_ERROR, GENERIC_VALIDATION_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags || {};
|
|
737
|
+
const { missingTags, unsupportedTags, isBraceError} = tagValidationResponse;
|
|
738
|
+
errorData[count][`sms-editor${index > 1 ? index : ''}`] = missingTags && missingTags.length ? MISSING_TAG_ERROR : ( unsupportedTags && unsupportedTags.length ? UNSUPPORTED_TAG_ERROR : (isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : GENERIC_VALIDATION_ERROR));
|
|
739
|
+
errorData[count][`bracket-error`] = tagValidationResponse.isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
740
740
|
isValid = false;
|
|
741
741
|
}
|
|
742
742
|
if(content !== '' && (ifUnicode && !unicodeCheck)) {
|
|
@@ -764,7 +764,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
764
764
|
if (this.state.formData['message-editor'] !== undefined ) {
|
|
765
765
|
const content = this.state.formData['0']['message-editor'] || '';
|
|
766
766
|
|
|
767
|
-
const tagValidationResponse = this.validateTags((content), tags, false, this.props?.isFullMode);
|
|
767
|
+
const tagValidationResponse = this.validateTags((content), tags, injectedTags, false, this.props?.isFullMode);
|
|
768
768
|
|
|
769
769
|
if (tagValidationResponse.valid) {
|
|
770
770
|
errorData = {
|
|
@@ -847,7 +847,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
847
847
|
errorData[index] = true;
|
|
848
848
|
isValid = false;
|
|
849
849
|
} else {
|
|
850
|
-
const tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
|
|
850
|
+
const tagValidationResponse = this.validateTags(content, tags, injectedTags, false, this.props?.isFullMode);
|
|
851
851
|
|
|
852
852
|
if (tagValidationResponse.valid) {
|
|
853
853
|
errorData[index] = false;
|
|
@@ -910,13 +910,14 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
910
910
|
isCurrentTabValid = false;
|
|
911
911
|
} else {
|
|
912
912
|
errorData[parseInt(index)][`message-editor${selector}`] = false;
|
|
913
|
-
const tagValidationResponse = this.validateTags(message, tags, false, this.props?.isFullMode);
|
|
913
|
+
const tagValidationResponse = this.validateTags(message, tags, injectedTags, false, this.props?.isFullMode);
|
|
914
914
|
|
|
915
915
|
if (tagValidationResponse.valid) {
|
|
916
916
|
errorData[parseInt(index)][`message-editor${selector}`] = false;
|
|
917
917
|
} else {
|
|
918
|
-
const {
|
|
918
|
+
const {isBraceError} = tagValidationResponse;
|
|
919
919
|
errorData[parseInt(index)][`message-editor${selector}`] = isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : true;
|
|
920
|
+
errorData[parseInt(index)]['invalid-tags'] = tagValidationResponse.unsupportedTags;
|
|
920
921
|
errorData[parseInt(index)][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
921
922
|
isValid = false;
|
|
922
923
|
}
|
|
@@ -938,7 +939,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
938
939
|
isCurrentTabValid = false;
|
|
939
940
|
} else {
|
|
940
941
|
errorData[parseInt(index)][`message-title${selector}`] = false;
|
|
941
|
-
const tagValidationResponse = this.validateTags(title, tags, false, this.props?.isFullMode);
|
|
942
|
+
const tagValidationResponse = this.validateTags(title, tags, injectedTags, false, this.props?.isFullMode);
|
|
942
943
|
|
|
943
944
|
if (tagValidationResponse.valid) {
|
|
944
945
|
errorData[parseInt(index)][`message-title${selector}`] = false;
|
|
@@ -1192,7 +1193,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1192
1193
|
if (!content) {
|
|
1193
1194
|
return false;
|
|
1194
1195
|
}
|
|
1195
|
-
const tagValidationResponse = this.validateTags(content, tags, isEmail, this.props?.isFullMode);
|
|
1196
|
+
const tagValidationResponse = this.validateTags(content, tags, injectedTags, isEmail, this.props?.isFullMode);
|
|
1197
|
+
|
|
1196
1198
|
// Check for base64 images in email content
|
|
1197
1199
|
isEmail && containsBase64Images({content, callback:()=>{
|
|
1198
1200
|
tagValidationResponse.valid = false;
|
|
@@ -1208,20 +1210,23 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1208
1210
|
errorData[index][currentLang]['template-content'] = true;
|
|
1209
1211
|
isValid = false;
|
|
1210
1212
|
isLiquidValid = false;
|
|
1211
|
-
if ((showMessages && !isNaN(index)) || this.
|
|
1212
|
-
if (tagValidationResponse?.missingTags?.length > 0) {
|
|
1213
|
+
if ((showMessages && !isNaN(index)) || this.liquidFlow()) {
|
|
1214
|
+
if (tagValidationResponse?.missingTags?.length > 0 || tagValidationResponse?.unsupportedTags?.length > 0) {
|
|
1213
1215
|
errorString += `${this.props.intl.formatMessage(messages.contentNotValidLanguage)} ${currentLang}\n`;
|
|
1214
1216
|
}
|
|
1215
1217
|
if (tagValidationResponse?.missingTags?.length > 0) {
|
|
1216
1218
|
errorString += `${this.props.intl.formatMessage(messages.missingTags)} ${tagValidationResponse.missingTags.toString()}\n`;
|
|
1217
1219
|
}
|
|
1220
|
+
if (tagValidationResponse?.unsupportedTags?.length > 0) {
|
|
1221
|
+
errorString += `${this.props.intl.formatMessage(messages.unsupportedTags)} ${tagValidationResponse.unsupportedTags.toString()}\n`;
|
|
1222
|
+
}
|
|
1218
1223
|
if (tagValidationResponse?.isBraceError){
|
|
1219
1224
|
errorString += this.props.intl.formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
1220
1225
|
}
|
|
1221
1226
|
if (tagValidationResponse?.isContentEmpty) {
|
|
1222
1227
|
errorString += this.props.intl.formatMessage(messages.emailBodyEmptyError);
|
|
1223
1228
|
// Adds a bypass for cases where content is initially empty in the creation flow.
|
|
1224
|
-
if(this.
|
|
1229
|
+
if(this.liquidFlow()){
|
|
1225
1230
|
errorString = "";
|
|
1226
1231
|
isLiquidValid = true;
|
|
1227
1232
|
}
|
|
@@ -1233,7 +1238,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1233
1238
|
}
|
|
1234
1239
|
}
|
|
1235
1240
|
if (errorString) {
|
|
1236
|
-
if (this.
|
|
1241
|
+
if (this.liquidFlow()) {
|
|
1237
1242
|
this.setState(
|
|
1238
1243
|
(prevState) => ({
|
|
1239
1244
|
liquidErrorMessage: {
|
|
@@ -1260,7 +1265,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1260
1265
|
});
|
|
1261
1266
|
}
|
|
1262
1267
|
|
|
1263
|
-
const isTemplateValid = this.
|
|
1268
|
+
const isTemplateValid = this.liquidFlow() ? isLiquidValid : isValid;
|
|
1264
1269
|
//Updating the state with the error data
|
|
1265
1270
|
this.setState((prevState) => ({
|
|
1266
1271
|
isFormValid: isTemplateValid,
|
|
@@ -1319,7 +1324,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1319
1324
|
}
|
|
1320
1325
|
onSubmitWrapper = (args) => {
|
|
1321
1326
|
const {singleTab = null} = args || {};
|
|
1322
|
-
if (this.
|
|
1327
|
+
if (this.liquidFlow()) {
|
|
1323
1328
|
// For MPUSH, we need to validate both Android and iOS content separately
|
|
1324
1329
|
if (this.props.channel === MOBILE_PUSH || this.props?.schema?.channel?.toUpperCase() === MOBILE_PUSH) {
|
|
1325
1330
|
this.validateFormBuilderMPush(this.state.formData, singleTab);
|
|
@@ -1363,6 +1368,10 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1363
1368
|
messages,
|
|
1364
1369
|
onError,
|
|
1365
1370
|
onSuccess,
|
|
1371
|
+
tagLookupMap: this.props?.metaEntities?.tagLookupMap,
|
|
1372
|
+
eventContextTags: this.props?.eventContextTags,
|
|
1373
|
+
isLiquidFlow: this.liquidFlow(),
|
|
1374
|
+
forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
|
|
1366
1375
|
skipTags: this.skipTags.bind(this)
|
|
1367
1376
|
});
|
|
1368
1377
|
} else {
|
|
@@ -1431,6 +1440,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1431
1440
|
getLiquidTags: this.props.actions.getLiquidTags,
|
|
1432
1441
|
formatMessage: this.props.intl.formatMessage,
|
|
1433
1442
|
messages: messages,
|
|
1443
|
+
tagLookupMap: this.props?.metaEntities?.tagLookupMap,
|
|
1444
|
+
eventContextTags: this.props?.eventContextTags,
|
|
1445
|
+
isLiquidFlow: this.liquidFlow(), // Use the method instead of props
|
|
1446
|
+
forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
|
|
1447
|
+
skipTags: this.skipTags.bind(this),
|
|
1448
|
+
extractNames,
|
|
1449
|
+
checkSupport,
|
|
1434
1450
|
singleTab: singleTab?.toUpperCase(),
|
|
1435
1451
|
});
|
|
1436
1452
|
}
|
|
@@ -1485,41 +1501,116 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1485
1501
|
});
|
|
1486
1502
|
}
|
|
1487
1503
|
|
|
1488
|
-
validateTags(content, tagsParam, isEmail = false, isFullMode = this.props?.isFullMode) {
|
|
1504
|
+
validateTags(content, tagsParam, injectedTagsParams, isEmail = false, isFullMode = this.props?.isFullMode) {
|
|
1505
|
+
const type = (this.props.location && this.props.location.query.type) ? this.props.location.query.type : 'full';
|
|
1489
1506
|
let currentModule = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
1490
1507
|
if (this.props.tagModule) {
|
|
1491
1508
|
currentModule = this.props.tagModule;
|
|
1492
1509
|
}
|
|
1493
1510
|
const tags = tagsParam ? tagsParam : this.props.tags;
|
|
1494
|
-
const
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1511
|
+
const injectedTags = this.transformInjectedTags(injectedTagsParams ? injectedTagsParams : this.props.injectedTags);
|
|
1512
|
+
const excludedTags = ['user_id_b64', 'outbox_id_b64'];
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
const response = {};
|
|
1516
|
+
response.valid = true;
|
|
1517
|
+
response.missingTags = [];
|
|
1518
|
+
response.unsupportedTags = [];
|
|
1519
|
+
response.isBraceError = false;
|
|
1520
|
+
response.isContentEmpty = false;
|
|
1521
|
+
const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content ;
|
|
1522
|
+
const isModuleTypeOutbound = (this.props?.moduleType || '').toUpperCase() === OUTBOUND;
|
|
1523
|
+
// Run tag validation (missing + unsupported): library mode, or full mode with liquid support, or
|
|
1524
|
+
// legacy Email (CK Editor) when unsubscribe is required (EMAIL_UNSUBSCRIBE_TAG_MANDATORY false) so missing-unsubscribe error shows
|
|
1525
|
+
const shouldRunTagValidation = !isFullMode
|
|
1526
|
+
|| (isEmail && hasLiquidSupportFeature())
|
|
1527
|
+
|| (isEmail && !isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound);
|
|
1528
|
+
if (tags && tags.length && !isFullMode) {
|
|
1529
|
+
_.forEach(tags, (tag) => {
|
|
1530
|
+
_.forEach(tag.definition.supportedModules, (module) => {
|
|
1531
|
+
if (module.mandatory && (currentModule === module.context)) {
|
|
1532
|
+
if (content.indexOf(`{{${tag.definition.value}}}`) === -1) {
|
|
1533
|
+
response.valid = false;
|
|
1534
|
+
response.missingTags.push(tag.definition.value);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
});
|
|
1539
|
+
// Legacy Email (CK Editor): when unsubscribe is required, ensure we validate it even if tag schema didn't mark it mandatory for this module
|
|
1540
|
+
if (isEmail && !isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound) {
|
|
1541
|
+
const hasUnsubscribeInContent = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g.test(content);
|
|
1542
|
+
if (!hasUnsubscribeInContent && response.missingTags.indexOf('unsubscribe') === -1) {
|
|
1543
|
+
response.valid = false;
|
|
1544
|
+
response.missingTags.push('unsubscribe');
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
1548
|
+
let match = regex.exec(content);
|
|
1549
|
+
const regexImgSrc=/<img[^>]*\bsrc\s*=\s*"[^"]*{{customer_barcode}}[^"]*"/;
|
|
1550
|
+
let matchImg = regexImgSrc.exec(content);
|
|
1551
|
+
const regexCustomerBarcode = /{{customer_barcode}}(?![^<]*>)/g;
|
|
1552
|
+
let matchCustomerBarcode = regexCustomerBarcode.exec(content);
|
|
1553
|
+
// \S matches anything other than a space, a tab, a newline, or a carriage return.
|
|
1554
|
+
const validString= /\S/.test(contentForValidation);
|
|
1555
|
+
if (isEmailUnsubscribeTagMandatory() && isEmail && isModuleTypeOutbound) {
|
|
1556
|
+
const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
|
|
1557
|
+
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1558
|
+
response?.missingTags?.splice(missingTagIndex, 1);
|
|
1559
|
+
}
|
|
1560
|
+
if (validString) {
|
|
1561
|
+
response.valid = true;
|
|
1562
|
+
} else {
|
|
1563
|
+
response.isContentEmpty = true;
|
|
1564
|
+
}
|
|
1518
1565
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1566
|
+
while (match !== null ) {
|
|
1567
|
+
const tagValue = match[0].substring(this.indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
|
|
1568
|
+
const tagIndex = match?.index;
|
|
1569
|
+
match = regex.exec(content);
|
|
1570
|
+
let ifSupported = false;
|
|
1571
|
+
_.forEach(tags, (tag) => {
|
|
1572
|
+
if (tag.definition.value === tagValue) {
|
|
1573
|
+
ifSupported = true;
|
|
1574
|
+
}
|
|
1575
|
+
if(tagValue === CUSTOMER_BARCODE_TAG && (matchImg === null || matchCustomerBarcode !== null)){
|
|
1576
|
+
ifSupported = false;
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
const ifSkipped = this.skipTags(tagValue);
|
|
1580
|
+
if (ifSkipped) {
|
|
1581
|
+
ifSupported = true;
|
|
1582
|
+
let isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") != -1 ;
|
|
1583
|
+
if (isUnsubscribeSkipped) {
|
|
1584
|
+
const missingTagIndex = response.missingTags.indexOf("unsubscribe");
|
|
1585
|
+
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1586
|
+
response.missingTags.splice(missingTagIndex, 1);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// Event Context Tags support
|
|
1592
|
+
this.props?.eventContextTags?.forEach((tag) => {
|
|
1593
|
+
if (tagValue === tag?.tagName) {
|
|
1594
|
+
ifSupported = true;
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
|
|
1599
|
+
// Only add to unsupportedTags if not inside a {% ... %} block (scenario 3: liquid orgs also get unsupported-tag errors)
|
|
1600
|
+
if (!ifSupported && !isInsideLiquidBlock(content, tagIndex)) {
|
|
1601
|
+
response.unsupportedTags.push(tagValue);
|
|
1602
|
+
response.valid = false;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
if (response?.unsupportedTags?.length == 0 && response?.missingTags?.length == 0 ) {
|
|
1606
|
+
response.valid = true;
|
|
1607
|
+
}
|
|
1521
1608
|
}
|
|
1522
1609
|
}
|
|
1610
|
+
if(!validateIfTagClosed(contentForValidation)){
|
|
1611
|
+
response.isBraceError = true;
|
|
1612
|
+
response.valid = false;
|
|
1613
|
+
}
|
|
1523
1614
|
return response;
|
|
1524
1615
|
}
|
|
1525
1616
|
/* eslint-enable */
|
|
@@ -2752,21 +2843,21 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2752
2843
|
|
|
2753
2844
|
|
|
2754
2845
|
getMissingOrUnsupportedTagsName = (content = '', type) => {
|
|
2755
|
-
const { MISSING_TAGS } = tagsTypes;
|
|
2846
|
+
const { MISSING_TAGS, UNSUPPORTED_TAGS} = tagsTypes;
|
|
2756
2847
|
const tagValidationResponse = this.validateTags(content);
|
|
2757
|
-
if (type && type === MISSING_TAGS) {
|
|
2758
|
-
return
|
|
2848
|
+
if (type && (type === MISSING_TAGS || type === UNSUPPORTED_TAGS)) {
|
|
2849
|
+
return tagValidationResponse[type].join(', ').toString();
|
|
2759
2850
|
}
|
|
2760
2851
|
return null;
|
|
2761
2852
|
};
|
|
2762
2853
|
|
|
2763
2854
|
renderTextAreaContent = (styling, columns, val, isVersionEnable, rows, cols, offset = 0) => {
|
|
2764
2855
|
const { checkValidation, errorData, currentTab, formData } = this.state;
|
|
2765
|
-
const { MISSING_TAGS } = tagsTypes;
|
|
2856
|
+
const { MISSING_TAGS, UNSUPPORTED_TAGS } = tagsTypes;
|
|
2766
2857
|
const errorType = (isVersionEnable ? errorData[`${currentTab - 1}`][val.id] : errorData[val.id]);
|
|
2767
2858
|
const ifError = checkValidation && errorType;
|
|
2768
2859
|
const messageContent = isVersionEnable ? formData[`${currentTab - 1}`][val.id] : formData[val.id];
|
|
2769
|
-
const { MISSING_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
|
|
2860
|
+
const { MISSING_TAG_ERROR, UNSUPPORTED_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
|
|
2770
2861
|
const { formatMessage } = this.props.intl;
|
|
2771
2862
|
|
|
2772
2863
|
const { accessibleFeatures = [] } = this.props.currentOrgDetails || {};
|
|
@@ -2779,6 +2870,9 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2779
2870
|
case MISSING_TAG_ERROR:
|
|
2780
2871
|
errorMessageText = formatMessage(messages.missingTagsValidationError, {missingTags: this.getMissingOrUnsupportedTagsName(messageContent, MISSING_TAGS)});
|
|
2781
2872
|
break;
|
|
2873
|
+
case UNSUPPORTED_TAG_ERROR:
|
|
2874
|
+
errorMessageText = formatMessage(messages.unsupportedTagsValidationError, {unsupportedTags: this.getMissingOrUnsupportedTagsName(messageContent, UNSUPPORTED_TAGS)});
|
|
2875
|
+
break;
|
|
2782
2876
|
case TAG_BRACKET_COUNT_MISMATCH_ERROR:
|
|
2783
2877
|
errorMessageText = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
2784
2878
|
break;
|
|
@@ -2789,7 +2883,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2789
2883
|
break;
|
|
2790
2884
|
}
|
|
2791
2885
|
const prevErrorMessage = this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.[0];
|
|
2792
|
-
if (prevErrorMessage !== errorMessageText && errorMessageText && this.
|
|
2886
|
+
if (prevErrorMessage !== errorMessageText && errorMessageText && this.liquidFlow()) {
|
|
2793
2887
|
this.setState((prevState) => ({
|
|
2794
2888
|
liquidErrorMessage: {
|
|
2795
2889
|
...prevState.liquidErrorMessage,
|
|
@@ -2808,7 +2902,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2808
2902
|
id={val.id}
|
|
2809
2903
|
placeholder={val.placeholder ? val.placeholder : ''}
|
|
2810
2904
|
className={`${ifError ? 'error-form-builder' : ''}`}
|
|
2811
|
-
errorMessage={errorMessageText && !this.
|
|
2905
|
+
errorMessage={errorMessageText && !this.liquidFlow() ? errorMessageText : ''}
|
|
2812
2906
|
label={val.label}
|
|
2813
2907
|
autosize={val.autosize ? val.autosizeParams : false}
|
|
2814
2908
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
@@ -3891,7 +3985,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3891
3985
|
<CapColumn
|
|
3892
3986
|
style={val.colStyle ? val.colStyle : {border : ""}}
|
|
3893
3987
|
span={val.width}
|
|
3894
|
-
className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.
|
|
3988
|
+
className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow() && "error-boundary"} `}
|
|
3895
3989
|
>
|
|
3896
3990
|
<CKEditor
|
|
3897
3991
|
id={val.id}
|
|
@@ -3935,7 +4029,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3935
4029
|
isModuleFilterEnabled = this.props.isFullMode;
|
|
3936
4030
|
}
|
|
3937
4031
|
columns.push(
|
|
3938
|
-
<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.
|
|
4032
|
+
<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"}`}>
|
|
3939
4033
|
<BeeEditor
|
|
3940
4034
|
uid={uuid}
|
|
3941
4035
|
tokenData={beeToken}
|
|
@@ -4221,7 +4315,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4221
4315
|
|
|
4222
4316
|
|
|
4223
4317
|
return (
|
|
4224
|
-
<CapSpin spinning={Boolean((this.
|
|
4318
|
+
<CapSpin spinning={Boolean((this.liquidFlow() && this.props.liquidExtractionInProgress) || this.props.metaDataStatus === REQUEST)} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
|
|
4225
4319
|
<CapRow>
|
|
4226
4320
|
{this.props.schema && this.renderForm()}
|
|
4227
4321
|
<SlideBox
|
|
@@ -26,6 +26,10 @@ export default defineMessages({
|
|
|
26
26
|
id: 'creatives.components.FormBuilder.missingTagsValidationError',
|
|
27
27
|
defaultMessage: 'Missing tags: {missingTags}. Please add them to this message.',
|
|
28
28
|
},
|
|
29
|
+
unsupportedTagsValidationError: {
|
|
30
|
+
id: 'creatives.components.FormBuilder.unsupportedTagsValidationError',
|
|
31
|
+
defaultMessage: 'Unsupported tags: {unsupportedTags}. Please remove them from this message.',
|
|
32
|
+
},
|
|
29
33
|
genericTagsValidationError: {
|
|
30
34
|
id: 'creatives.components.FormBuilder.genericTagsValidationError',
|
|
31
35
|
defaultMessage: 'Please check the message content for unsupported/missing tags',
|
|
@@ -38,6 +42,10 @@ export default defineMessages({
|
|
|
38
42
|
id: 'creatives.componentsV2.FormBuilder.missingTags',
|
|
39
43
|
defaultMessage: 'Missing tags are:',
|
|
40
44
|
},
|
|
45
|
+
unsupportedTags: {
|
|
46
|
+
id: 'creatives.componentsV2.FormBuilder.unsupportedTags',
|
|
47
|
+
defaultMessage: 'Unsupported tags are:',
|
|
48
|
+
},
|
|
41
49
|
upload: {
|
|
42
50
|
id: 'creatives.componentsV2.FormBuilder.upload',
|
|
43
51
|
defaultMessage: 'Upload',
|
|
@@ -102,6 +102,7 @@ const HTMLEditor = forwardRef(({
|
|
|
102
102
|
onTagSelect = null,
|
|
103
103
|
onContextChange = null,
|
|
104
104
|
globalActions = null,
|
|
105
|
+
isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
|
|
105
106
|
isFullMode = true, // Full mode vs library mode - controls layout and visibility
|
|
106
107
|
onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
|
|
107
108
|
onValidationChange = null, // Callback when validation state changes (for parent to track errors)
|
|
@@ -572,6 +573,7 @@ const HTMLEditor = forwardRef(({
|
|
|
572
573
|
content,
|
|
573
574
|
layout,
|
|
574
575
|
validation,
|
|
576
|
+
isLiquidEnabled,
|
|
575
577
|
editorRef: getActiveEditorRef(),
|
|
576
578
|
handleLabelInsert,
|
|
577
579
|
handleSave,
|
|
@@ -591,6 +593,7 @@ const HTMLEditor = forwardRef(({
|
|
|
591
593
|
content,
|
|
592
594
|
layout,
|
|
593
595
|
validation,
|
|
596
|
+
isLiquidEnabled,
|
|
594
597
|
getActiveEditorRef,
|
|
595
598
|
handleLabelInsert,
|
|
596
599
|
handleSave,
|
|
@@ -779,6 +782,7 @@ HTMLEditor.propTypes = {
|
|
|
779
782
|
onTagSelect: PropTypes.func,
|
|
780
783
|
onContextChange: PropTypes.func, // Deprecated: use globalActions instead
|
|
781
784
|
globalActions: PropTypes.object,
|
|
785
|
+
isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
|
|
782
786
|
isFullMode: PropTypes.bool, // Full mode vs library mode
|
|
783
787
|
onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
|
|
784
788
|
onValidationChange: PropTypes.func, // Callback when validation state changes
|
|
@@ -812,6 +816,7 @@ HTMLEditor.defaultProps = {
|
|
|
812
816
|
onTagSelect: null,
|
|
813
817
|
onContextChange: null,
|
|
814
818
|
globalActions: null, // Redux actions for API calls
|
|
819
|
+
isLiquidEnabled: false,
|
|
815
820
|
isFullMode: true, // Default to full mode
|
|
816
821
|
onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
|
|
817
822
|
onValidationChange: null, // Callback when validation state changes
|
|
@@ -3201,6 +3201,7 @@ describe('HTMLEditor', () => {
|
|
|
3201
3201
|
onTagSelect={onTagSelect}
|
|
3202
3202
|
onContextChange={onContextChange}
|
|
3203
3203
|
globalActions={globalActions}
|
|
3204
|
+
isLiquidEnabled={true}
|
|
3204
3205
|
isFullMode={false}
|
|
3205
3206
|
onErrorAcknowledged={onErrorAcknowledged}
|
|
3206
3207
|
onValidationChange={onValidationChange}
|
|
@@ -3260,6 +3261,20 @@ describe('HTMLEditor', () => {
|
|
|
3260
3261
|
expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
|
|
3261
3262
|
});
|
|
3262
3263
|
|
|
3264
|
+
it('should handle isLiquidEnabled prop', () => {
|
|
3265
|
+
render(
|
|
3266
|
+
<TestWrapper>
|
|
3267
|
+
<HTMLEditor isLiquidEnabled={true} />
|
|
3268
|
+
</TestWrapper>
|
|
3269
|
+
);
|
|
3270
|
+
|
|
3271
|
+
act(() => {
|
|
3272
|
+
jest.runAllTimers();
|
|
3273
|
+
});
|
|
3274
|
+
|
|
3275
|
+
expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
|
|
3276
|
+
});
|
|
3277
|
+
|
|
3263
3278
|
it('should handle isFullMode prop', () => {
|
|
3264
3279
|
render(
|
|
3265
3280
|
<TestWrapper>
|
|
@@ -69,7 +69,7 @@ const CodeEditorPaneComponent = ({
|
|
|
69
69
|
}) => {
|
|
70
70
|
const context = useEditorContext();
|
|
71
71
|
const {
|
|
72
|
-
content, validation, variant,
|
|
72
|
+
content, validation, variant, isLiquidEnabled,
|
|
73
73
|
} = context || {};
|
|
74
74
|
const { content: contentValue, updateContent } = content || {};
|
|
75
75
|
const editorRef = useRef(null);
|
|
@@ -298,6 +298,7 @@ const CodeEditorPaneComponent = ({
|
|
|
298
298
|
<ValidationErrorDisplay
|
|
299
299
|
validation={validation}
|
|
300
300
|
onErrorClick={onErrorClick}
|
|
301
|
+
isLiquidEnabled={isLiquidEnabled}
|
|
301
302
|
className="code-editor-pane__validation"
|
|
302
303
|
/>
|
|
303
304
|
</div>
|