@capillarytech/creatives-library 8.0.288 → 8.0.290-alpha.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/constants/unified.js +0 -1
- package/initialState.js +0 -2
- package/package.json +1 -1
- package/utils/common.js +5 -8
- package/utils/commonUtils.js +4 -85
- package/utils/tagValidations.js +84 -222
- package/utils/tests/commonUtil.test.js +461 -118
- package/utils/tests/tagValidations.test.js +280 -358
- package/v2Components/ErrorInfoNote/index.js +2 -5
- package/v2Components/FormBuilder/index.js +78 -161
- package/v2Components/FormBuilder/messages.js +0 -8
- package/v2Components/HtmlEditor/HTMLEditor.js +0 -5
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -15
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
- package/v2Containers/Cap/mockData.js +0 -14
- package/v2Containers/Cap/reducer.js +3 -55
- package/v2Containers/Cap/tests/reducer.test.js +0 -102
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -1
- package/v2Containers/CreativesContainer/index.js +19 -6
- package/v2Containers/Email/index.js +1 -5
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +10 -62
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +12 -115
- package/v2Containers/FTP/index.js +2 -51
- package/v2Containers/FTP/messages.js +0 -4
- package/v2Containers/InApp/index.js +1 -96
- package/v2Containers/InApp/tests/index.test.js +17 -6
- package/v2Containers/InappAdvance/index.js +2 -103
- package/v2Containers/Line/Container/Text/index.js +0 -1
- package/v2Containers/MobilePush/Create/index.js +6 -16
- package/v2Containers/MobilePush/Edit/index.js +6 -16
- package/v2Containers/MobilePushNew/index.js +2 -33
- package/v2Containers/Rcs/index.js +12 -37
- package/v2Containers/Sms/Create/index.js +31 -3
- package/v2Containers/Sms/Create/messages.js +4 -0
- package/v2Containers/Sms/Edit/index.js +29 -3
- package/v2Containers/Sms/commonMethods.js +6 -6
- package/v2Containers/SmsTrai/Edit/index.js +6 -47
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/Templates/reducer.js +3 -1
- package/v2Containers/Templates/tests/reducer.test.js +12 -0
- package/v2Containers/Viber/index.js +0 -1
- package/v2Containers/WebPush/Create/components/BrandIconSection.test.js +264 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/BrandIconSection.test.js.snap +187 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +269 -0
- package/v2Containers/WebPush/Create/utils/validation.js +17 -2
- package/v2Containers/WebPush/Create/utils/validation.test.js +0 -24
- package/v2Containers/Whatsapp/index.js +9 -17
- package/v2Containers/Zalo/index.js +3 -11
|
@@ -186,7 +186,6 @@ export const ErrorInfoNote = (props) => {
|
|
|
186
186
|
errorMessages,
|
|
187
187
|
onErrorClick,
|
|
188
188
|
onClose,
|
|
189
|
-
isLiquidEnabled = true,
|
|
190
189
|
intl,
|
|
191
190
|
useLegacyDisplay = false, // Use simple list display instead of tabs (for BEE Editor)
|
|
192
191
|
} = props;
|
|
@@ -230,7 +229,7 @@ export const ErrorInfoNote = (props) => {
|
|
|
230
229
|
const standardErrors = Array.isArray(rawStandardErrors) ? rawStandardErrors : [];
|
|
231
230
|
const liquidErrors = Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [];
|
|
232
231
|
const hasStandardErrors = standardErrors.length > 0;
|
|
233
|
-
const hasLiquidErrors = liquidErrors.length > 0
|
|
232
|
+
const hasLiquidErrors = liquidErrors.length > 0;
|
|
234
233
|
|
|
235
234
|
if (!hasStandardErrors && !hasLiquidErrors) {
|
|
236
235
|
return null;
|
|
@@ -357,7 +356,7 @@ export const ErrorInfoNote = (props) => {
|
|
|
357
356
|
className="error-info-note__tabs"
|
|
358
357
|
/>
|
|
359
358
|
<CapRow className="error-info-note__actions">
|
|
360
|
-
{hasLiquidErrors &&
|
|
359
|
+
{hasLiquidErrors && (
|
|
361
360
|
<CapButton
|
|
362
361
|
type="flat"
|
|
363
362
|
className="error-info-note__liquid-doc"
|
|
@@ -452,7 +451,6 @@ ErrorInfoNote.defaultProps = {
|
|
|
452
451
|
},
|
|
453
452
|
onErrorClick: null,
|
|
454
453
|
onClose: null,
|
|
455
|
-
isLiquidEnabled: true,
|
|
456
454
|
intl: null,
|
|
457
455
|
useLegacyDisplay: false, // Use simple list display for BEE Editor
|
|
458
456
|
};
|
|
@@ -479,7 +477,6 @@ ErrorInfoNote.propTypes = {
|
|
|
479
477
|
}),
|
|
480
478
|
onErrorClick: PropTypes.func,
|
|
481
479
|
onClose: PropTypes.func,
|
|
482
|
-
isLiquidEnabled: PropTypes.bool,
|
|
483
480
|
intl: PropTypes.object,
|
|
484
481
|
useLegacyDisplay: PropTypes.bool, // Use simple list display for BEE Editor
|
|
485
482
|
};
|
|
@@ -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 {
|
|
53
|
+
import { preprocessHtml, validateTagsCore, hasUnsubscribeTag } 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 { isEmailUnsubscribeTagOptional } from '../../utils/common';
|
|
64
64
|
import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
|
|
65
65
|
import { bindActionCreators } from 'redux';
|
|
66
66
|
import { getChannelData, hasPersonalizationTags, validateLiquidTemplateContent, validateMobilePushContent } from '../../utils/commonUtils';
|
|
@@ -72,10 +72,8 @@ const {CapRadioGroup} = CapRadio;
|
|
|
72
72
|
|
|
73
73
|
const tagsTypes = {
|
|
74
74
|
MISSING_TAGS: 'missingTags',
|
|
75
|
-
UNSUPPORTED_TAGS: 'unsupportedTags',
|
|
76
75
|
};
|
|
77
76
|
const errorMessageForTags = {
|
|
78
|
-
UNSUPPORTED_TAG_ERROR: 'unsupportedTagsError',
|
|
79
77
|
MISSING_TAG_ERROR: 'missingTagsError',
|
|
80
78
|
GENERIC_VALIDATION_ERROR: 'genericValidationError',
|
|
81
79
|
TAG_BRACKET_COUNT_MISMATCH_ERROR: 'tagBracketCountMismatchError'
|
|
@@ -138,7 +136,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
138
136
|
this.handleSetRadioValue = this.handleSetRadioValue.bind(this);
|
|
139
137
|
this.formElements = [];
|
|
140
138
|
// Check if the liquid flow feature is supported and the channel is in the supported list.
|
|
141
|
-
this.
|
|
139
|
+
this.isLiquidFlowSupportedByChannel = this.isLiquidFlowSupportedByChannel.bind(this);
|
|
142
140
|
this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
|
|
143
141
|
|
|
144
142
|
// Performance optimization: Debounced functions for high-frequency updates
|
|
@@ -330,8 +328,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
330
328
|
return updatedFormData;
|
|
331
329
|
}
|
|
332
330
|
|
|
333
|
-
|
|
334
|
-
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase())
|
|
331
|
+
isLiquidFlowSupportedByChannel = () => {
|
|
332
|
+
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()));
|
|
335
333
|
}
|
|
336
334
|
|
|
337
335
|
componentWillMount() {
|
|
@@ -714,7 +712,9 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
714
712
|
if (channel && channel.toUpperCase() === SMS) {
|
|
715
713
|
for (let count = 0; count < this.state.tabCount; count += 1) {
|
|
716
714
|
if (_.isEmpty(errorData[count])) {
|
|
717
|
-
return
|
|
715
|
+
// Do not return early. An empty tab object can appear transiently and returning here
|
|
716
|
+
// prevents onFormValidityChange from firing, which makes Done appear unresponsive.
|
|
717
|
+
errorData[count] = {};
|
|
718
718
|
}
|
|
719
719
|
const index = count + 1;
|
|
720
720
|
if (!this.state.formData[count]) {
|
|
@@ -726,17 +726,19 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
726
726
|
|
|
727
727
|
let tagValidationResponse = false;
|
|
728
728
|
if (content) {
|
|
729
|
-
tagValidationResponse = this.validateTags(content, tags,
|
|
729
|
+
tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
|
|
730
730
|
}
|
|
731
|
-
|
|
732
|
-
|
|
731
|
+
|
|
732
|
+
const tagResult = tagValidationResponse && typeof tagValidationResponse === 'object'
|
|
733
|
+
? tagValidationResponse
|
|
734
|
+
: { valid: false, missingTags: [], isBraceError: false };
|
|
735
|
+
if (tagResult.valid) {
|
|
733
736
|
errorData[count][`sms-editor${index > 1 ? index : ''}`] = false;
|
|
734
737
|
} else {
|
|
735
|
-
|
|
736
|
-
const {
|
|
737
|
-
|
|
738
|
-
errorData[count][`
|
|
739
|
-
errorData[count][`bracket-error`] = tagValidationResponse.isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
738
|
+
const { MISSING_TAG_ERROR, GENERIC_VALIDATION_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags || {};
|
|
739
|
+
const { missingTags, isBraceError } = tagResult;
|
|
740
|
+
errorData[count][`sms-editor${index > 1 ? index : ''}`] = missingTags && missingTags.length ? MISSING_TAG_ERROR : (isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : GENERIC_VALIDATION_ERROR);
|
|
741
|
+
errorData[count][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
740
742
|
isValid = false;
|
|
741
743
|
}
|
|
742
744
|
if(content !== '' && (ifUnicode && !unicodeCheck)) {
|
|
@@ -764,7 +766,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
764
766
|
if (this.state.formData['message-editor'] !== undefined ) {
|
|
765
767
|
const content = this.state.formData['0']['message-editor'] || '';
|
|
766
768
|
|
|
767
|
-
const tagValidationResponse = this.validateTags((content), tags,
|
|
769
|
+
const tagValidationResponse = this.validateTags((content), tags, false, this.props?.isFullMode);
|
|
768
770
|
|
|
769
771
|
if (tagValidationResponse.valid) {
|
|
770
772
|
errorData = {
|
|
@@ -847,7 +849,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
847
849
|
errorData[index] = true;
|
|
848
850
|
isValid = false;
|
|
849
851
|
} else {
|
|
850
|
-
const tagValidationResponse = this.validateTags(content, tags,
|
|
852
|
+
const tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
|
|
851
853
|
|
|
852
854
|
if (tagValidationResponse.valid) {
|
|
853
855
|
errorData[index] = false;
|
|
@@ -913,14 +915,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
913
915
|
isCurrentTabValid = false;
|
|
914
916
|
} else {
|
|
915
917
|
errorData[parseInt(index)][`message-editor${selector}`] = false;
|
|
916
|
-
const tagValidationResponse = this.validateTags(message, tags,
|
|
918
|
+
const tagValidationResponse = this.validateTags(message, tags, false, this.props?.isFullMode);
|
|
917
919
|
|
|
918
920
|
if (tagValidationResponse.valid) {
|
|
919
921
|
errorData[parseInt(index)][`message-editor${selector}`] = false;
|
|
920
922
|
} else {
|
|
921
|
-
const {isBraceError} = tagValidationResponse;
|
|
923
|
+
const { isBraceError } = tagValidationResponse;
|
|
922
924
|
errorData[parseInt(index)][`message-editor${selector}`] = isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : true;
|
|
923
|
-
errorData[parseInt(index)]['invalid-tags'] = tagValidationResponse.unsupportedTags;
|
|
924
925
|
errorData[parseInt(index)][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
925
926
|
isValid = false;
|
|
926
927
|
}
|
|
@@ -946,7 +947,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
946
947
|
isCurrentTabValid = false;
|
|
947
948
|
} else {
|
|
948
949
|
errorData[parseInt(index)][`message-title${selector}`] = false;
|
|
949
|
-
const tagValidationResponse = this.validateTags(title, tags,
|
|
950
|
+
const tagValidationResponse = this.validateTags(title, tags, false, this.props?.isFullMode);
|
|
950
951
|
|
|
951
952
|
if (tagValidationResponse.valid) {
|
|
952
953
|
errorData[parseInt(index)][`message-title${selector}`] = false;
|
|
@@ -1200,8 +1201,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1200
1201
|
if (!content) {
|
|
1201
1202
|
return false;
|
|
1202
1203
|
}
|
|
1203
|
-
const tagValidationResponse = this.validateTags(content, tags,
|
|
1204
|
-
|
|
1204
|
+
const tagValidationResponse = this.validateTags(content, tags, isEmail, this.props?.isFullMode);
|
|
1205
1205
|
// Check for base64 images in email content
|
|
1206
1206
|
isEmail && containsBase64Images({content, callback:()=>{
|
|
1207
1207
|
tagValidationResponse.valid = false;
|
|
@@ -1217,23 +1217,20 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1217
1217
|
errorData[index][currentLang]['template-content'] = true;
|
|
1218
1218
|
isValid = false;
|
|
1219
1219
|
isLiquidValid = false;
|
|
1220
|
-
if ((showMessages && !isNaN(index)) || this.
|
|
1221
|
-
if (tagValidationResponse?.missingTags?.length > 0
|
|
1220
|
+
if ((showMessages && !isNaN(index)) || this.isLiquidFlowSupportedByChannel()) {
|
|
1221
|
+
if (tagValidationResponse?.missingTags?.length > 0) {
|
|
1222
1222
|
errorString += `${this.props.intl.formatMessage(messages.contentNotValidLanguage)} ${currentLang}\n`;
|
|
1223
1223
|
}
|
|
1224
1224
|
if (tagValidationResponse?.missingTags?.length > 0) {
|
|
1225
1225
|
errorString += `${this.props.intl.formatMessage(messages.missingTags)} ${tagValidationResponse.missingTags.toString()}\n`;
|
|
1226
1226
|
}
|
|
1227
|
-
if (tagValidationResponse?.unsupportedTags?.length > 0) {
|
|
1228
|
-
errorString += `${this.props.intl.formatMessage(messages.unsupportedTags)} ${tagValidationResponse.unsupportedTags.toString()}\n`;
|
|
1229
|
-
}
|
|
1230
1227
|
if (tagValidationResponse?.isBraceError){
|
|
1231
1228
|
errorString += this.props.intl.formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
1232
1229
|
}
|
|
1233
1230
|
if (tagValidationResponse?.isContentEmpty) {
|
|
1234
1231
|
errorString += this.props.intl.formatMessage(messages.emailBodyEmptyError);
|
|
1235
1232
|
// Adds a bypass for cases where content is initially empty in the creation flow.
|
|
1236
|
-
if(this.
|
|
1233
|
+
if(this.isLiquidFlowSupportedByChannel()){
|
|
1237
1234
|
errorString = "";
|
|
1238
1235
|
isLiquidValid = true;
|
|
1239
1236
|
}
|
|
@@ -1245,7 +1242,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1245
1242
|
}
|
|
1246
1243
|
}
|
|
1247
1244
|
if (errorString) {
|
|
1248
|
-
if (this.
|
|
1245
|
+
if (this.isLiquidFlowSupportedByChannel()) {
|
|
1249
1246
|
this.setState(
|
|
1250
1247
|
(prevState) => ({
|
|
1251
1248
|
liquidErrorMessage: {
|
|
@@ -1272,7 +1269,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1272
1269
|
});
|
|
1273
1270
|
}
|
|
1274
1271
|
|
|
1275
|
-
const isTemplateValid = this.
|
|
1272
|
+
const isTemplateValid = this.isLiquidFlowSupportedByChannel() ? isLiquidValid : isValid;
|
|
1276
1273
|
//Updating the state with the error data
|
|
1277
1274
|
this.setState((prevState) => ({
|
|
1278
1275
|
isFormValid: isTemplateValid,
|
|
@@ -1331,7 +1328,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1331
1328
|
}
|
|
1332
1329
|
onSubmitWrapper = (args) => {
|
|
1333
1330
|
const {singleTab = null} = args || {};
|
|
1334
|
-
if (this.
|
|
1331
|
+
if (this.isLiquidFlowSupportedByChannel()) {
|
|
1335
1332
|
// For MPUSH, we need to validate both Android and iOS content separately
|
|
1336
1333
|
if (this.props.channel === MOBILE_PUSH || this.props?.schema?.channel?.toUpperCase() === MOBILE_PUSH) {
|
|
1337
1334
|
this.validateFormBuilderMPush(this.state.formData, singleTab);
|
|
@@ -1375,10 +1372,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1375
1372
|
messages,
|
|
1376
1373
|
onError,
|
|
1377
1374
|
onSuccess,
|
|
1378
|
-
tagLookupMap: this.props?.metaEntities?.tagLookupMap,
|
|
1379
|
-
eventContextTags: this.props?.eventContextTags,
|
|
1380
|
-
isLiquidFlow: this.liquidFlow(),
|
|
1381
|
-
forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
|
|
1382
1375
|
skipTags: this.skipTags.bind(this)
|
|
1383
1376
|
});
|
|
1384
1377
|
} else {
|
|
@@ -1447,13 +1440,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1447
1440
|
getLiquidTags: this.props.actions.getLiquidTags,
|
|
1448
1441
|
formatMessage: this.props.intl.formatMessage,
|
|
1449
1442
|
messages: messages,
|
|
1450
|
-
tagLookupMap: this.props?.metaEntities?.tagLookupMap,
|
|
1451
|
-
eventContextTags: this.props?.eventContextTags,
|
|
1452
|
-
isLiquidFlow: this.liquidFlow(), // Use the method instead of props
|
|
1453
|
-
forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
|
|
1454
|
-
skipTags: this.skipTags.bind(this),
|
|
1455
|
-
extractNames,
|
|
1456
|
-
checkSupport,
|
|
1457
1443
|
singleTab: singleTab?.toUpperCase(),
|
|
1458
1444
|
});
|
|
1459
1445
|
}
|
|
@@ -1508,115 +1494,40 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1508
1494
|
});
|
|
1509
1495
|
}
|
|
1510
1496
|
|
|
1511
|
-
validateTags(content, tagsParam,
|
|
1512
|
-
const type = (this.props.location && this.props.location.query.type) ? this.props.location.query.type : 'full';
|
|
1497
|
+
validateTags(content, tagsParam, isEmail = false, isFullMode = this.props?.isFullMode) {
|
|
1513
1498
|
let currentModule = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
1514
1499
|
if (this.props.tagModule) {
|
|
1515
1500
|
currentModule = this.props.tagModule;
|
|
1516
1501
|
}
|
|
1517
1502
|
const tags = tagsParam ? tagsParam : this.props.tags;
|
|
1518
|
-
const
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
response
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
if (tags && tags.length && !isFullMode) {
|
|
1536
|
-
_.forEach(tags, (tag) => {
|
|
1537
|
-
_.forEach(tag.definition.supportedModules, (module) => {
|
|
1538
|
-
if (module.mandatory && (currentModule === module.context)) {
|
|
1539
|
-
if (content.indexOf(`{{${tag.definition.value}}}`) === -1) {
|
|
1540
|
-
response.valid = false;
|
|
1541
|
-
response.missingTags.push(tag.definition.value);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
});
|
|
1545
|
-
});
|
|
1546
|
-
// Legacy Email (CK Editor): when unsubscribe is required, ensure we validate it even if tag schema didn't mark it mandatory for this module
|
|
1547
|
-
if (isEmail && !isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound) {
|
|
1548
|
-
const hasUnsubscribeInContent = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g.test(content);
|
|
1549
|
-
if (!hasUnsubscribeInContent && response.missingTags.indexOf('unsubscribe') === -1) {
|
|
1550
|
-
response.valid = false;
|
|
1551
|
-
response.missingTags.push('unsubscribe');
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
1555
|
-
let match = regex.exec(content);
|
|
1556
|
-
const regexImgSrc=/<img[^>]*\bsrc\s*=\s*"[^"]*{{customer_barcode}}[^"]*"/;
|
|
1557
|
-
let matchImg = regexImgSrc.exec(content);
|
|
1558
|
-
const regexCustomerBarcode = /{{customer_barcode}}(?![^<]*>)/g;
|
|
1559
|
-
let matchCustomerBarcode = regexCustomerBarcode.exec(content);
|
|
1560
|
-
// \S matches anything other than a space, a tab, a newline, or a carriage return.
|
|
1561
|
-
const validString= /\S/.test(contentForValidation);
|
|
1562
|
-
if (isEmailUnsubscribeTagMandatory() && isEmail && isModuleTypeOutbound) {
|
|
1563
|
-
const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
|
|
1564
|
-
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1565
|
-
response?.missingTags?.splice(missingTagIndex, 1);
|
|
1566
|
-
}
|
|
1567
|
-
if (validString) {
|
|
1568
|
-
response.valid = true;
|
|
1569
|
-
} else {
|
|
1570
|
-
response.isContentEmpty = true;
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
while (match !== null ) {
|
|
1574
|
-
const tagValue = match[0].substring(this.indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
|
|
1575
|
-
const tagIndex = match?.index;
|
|
1576
|
-
match = regex.exec(content);
|
|
1577
|
-
let ifSupported = false;
|
|
1578
|
-
_.forEach(tags, (tag) => {
|
|
1579
|
-
if (tag.definition.value === tagValue) {
|
|
1580
|
-
ifSupported = true;
|
|
1581
|
-
}
|
|
1582
|
-
if(tagValue === CUSTOMER_BARCODE_TAG && (matchImg === null || matchCustomerBarcode !== null)){
|
|
1583
|
-
ifSupported = false;
|
|
1584
|
-
}
|
|
1585
|
-
});
|
|
1586
|
-
const ifSkipped = this.skipTags(tagValue);
|
|
1587
|
-
if (ifSkipped) {
|
|
1588
|
-
ifSupported = true;
|
|
1589
|
-
let isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") != -1 ;
|
|
1590
|
-
if (isUnsubscribeSkipped) {
|
|
1591
|
-
const missingTagIndex = response.missingTags.indexOf("unsubscribe");
|
|
1592
|
-
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1593
|
-
response.missingTags.splice(missingTagIndex, 1);
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
// Event Context Tags support
|
|
1599
|
-
this.props?.eventContextTags?.forEach((tag) => {
|
|
1600
|
-
if (tagValue === tag?.tagName) {
|
|
1601
|
-
ifSupported = true;
|
|
1602
|
-
}
|
|
1603
|
-
});
|
|
1604
|
-
|
|
1605
|
-
ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
|
|
1606
|
-
// Only add to unsupportedTags if not inside a {% ... %} block (scenario 3: liquid orgs also get unsupported-tag errors)
|
|
1607
|
-
if (!ifSupported && !isInsideLiquidBlock(content, tagIndex)) {
|
|
1608
|
-
response.unsupportedTags.push(tagValue);
|
|
1609
|
-
response.valid = false;
|
|
1610
|
-
}
|
|
1503
|
+
const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content;
|
|
1504
|
+
const isOutboundModule = (currentModule || '').toUpperCase() === OUTBOUND;
|
|
1505
|
+
|
|
1506
|
+
const initialMissingTags = (tags && tags.length && !isFullMode && isEmail && isOutboundModule && !isEmailUnsubscribeTagOptional() && !hasUnsubscribeTag(content))
|
|
1507
|
+
? ['unsubscribe']
|
|
1508
|
+
: [];
|
|
1509
|
+
|
|
1510
|
+
const response = validateTagsCore({
|
|
1511
|
+
contentForBraceCheck: contentForValidation,
|
|
1512
|
+
contentForUnsubscribeScan: content,
|
|
1513
|
+
tags,
|
|
1514
|
+
currentModule,
|
|
1515
|
+
isFullMode,
|
|
1516
|
+
initialMissingTags, // [] or ['unsubscribe']; core uses this instead of definition-based when provided
|
|
1517
|
+
skipTagsFn: this.skipTags.bind(this),
|
|
1518
|
+
includeIsContentEmpty: true,
|
|
1519
|
+
});
|
|
1611
1520
|
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1521
|
+
// When unsubscribe tag is optional (isEmailUnsubscribeTagOptional): do not enforce unsubscribe (defensive splice); set isContentEmpty only when content is empty. Do not override response.valid so brace/format errors are preserved.
|
|
1522
|
+
const validString = /\S/.test(contentForValidation);
|
|
1523
|
+
if (isEmailUnsubscribeTagOptional() && isEmail && isOutboundModule) {
|
|
1524
|
+
const missingTagIndex = response.missingTags.indexOf('unsubscribe');
|
|
1525
|
+
if (missingTagIndex !== -1) {
|
|
1526
|
+
response.missingTags.splice(missingTagIndex, 1);
|
|
1527
|
+
}
|
|
1528
|
+
if (!validString) {
|
|
1529
|
+
response.isContentEmpty = true;
|
|
1615
1530
|
}
|
|
1616
|
-
}
|
|
1617
|
-
if(!validateIfTagClosed(contentForValidation)){
|
|
1618
|
-
response.isBraceError = true;
|
|
1619
|
-
response.valid = false;
|
|
1620
1531
|
}
|
|
1621
1532
|
return response;
|
|
1622
1533
|
}
|
|
@@ -2850,21 +2761,21 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2850
2761
|
|
|
2851
2762
|
|
|
2852
2763
|
getMissingOrUnsupportedTagsName = (content = '', type) => {
|
|
2853
|
-
const { MISSING_TAGS
|
|
2764
|
+
const { MISSING_TAGS } = tagsTypes;
|
|
2854
2765
|
const tagValidationResponse = this.validateTags(content);
|
|
2855
|
-
if (type &&
|
|
2856
|
-
return tagValidationResponse[type].join(', ').toString();
|
|
2766
|
+
if (type && type === MISSING_TAGS) {
|
|
2767
|
+
return (tagValidationResponse[type] || []).join(', ').toString();
|
|
2857
2768
|
}
|
|
2858
2769
|
return null;
|
|
2859
2770
|
};
|
|
2860
2771
|
|
|
2861
2772
|
renderTextAreaContent = (styling, columns, val, isVersionEnable, rows, cols, offset = 0) => {
|
|
2862
2773
|
const { checkValidation, errorData, currentTab, formData } = this.state;
|
|
2863
|
-
const { MISSING_TAGS
|
|
2774
|
+
const { MISSING_TAGS } = tagsTypes;
|
|
2864
2775
|
const errorType = (isVersionEnable ? errorData[`${currentTab - 1}`][val.id] : errorData[val.id]);
|
|
2865
2776
|
const ifError = checkValidation && errorType;
|
|
2866
2777
|
const messageContent = isVersionEnable ? formData[`${currentTab - 1}`][val.id] : formData[val.id];
|
|
2867
|
-
const { MISSING_TAG_ERROR,
|
|
2778
|
+
const { MISSING_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
|
|
2868
2779
|
const { formatMessage } = this.props.intl;
|
|
2869
2780
|
|
|
2870
2781
|
const { accessibleFeatures = [] } = this.props.currentOrgDetails || {};
|
|
@@ -2877,9 +2788,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2877
2788
|
case MISSING_TAG_ERROR:
|
|
2878
2789
|
errorMessageText = formatMessage(messages.missingTagsValidationError, {missingTags: this.getMissingOrUnsupportedTagsName(messageContent, MISSING_TAGS)});
|
|
2879
2790
|
break;
|
|
2880
|
-
case UNSUPPORTED_TAG_ERROR:
|
|
2881
|
-
errorMessageText = formatMessage(messages.unsupportedTagsValidationError, {unsupportedTags: this.getMissingOrUnsupportedTagsName(messageContent, UNSUPPORTED_TAGS)});
|
|
2882
|
-
break;
|
|
2883
2791
|
case TAG_BRACKET_COUNT_MISMATCH_ERROR:
|
|
2884
2792
|
errorMessageText = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
2885
2793
|
break;
|
|
@@ -2894,7 +2802,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2894
2802
|
errorMessageText = formatMessage(messages.personalizationTagsErrorMessage);
|
|
2895
2803
|
}
|
|
2896
2804
|
const prevErrorMessage = this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.[0];
|
|
2897
|
-
if (prevErrorMessage !== errorMessageText && errorMessageText && this.
|
|
2805
|
+
if (prevErrorMessage !== errorMessageText && errorMessageText && this.isLiquidFlowSupportedByChannel()) {
|
|
2898
2806
|
this.setState((prevState) => ({
|
|
2899
2807
|
liquidErrorMessage: {
|
|
2900
2808
|
...prevState.liquidErrorMessage,
|
|
@@ -2905,7 +2813,10 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2905
2813
|
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
|
|
2906
2814
|
});
|
|
2907
2815
|
}
|
|
2908
|
-
|
|
2816
|
+
// Always render the textarea regardless of the liquid error state update above.
|
|
2817
|
+
// Previously the textarea was inside the `else` branch, so it was not rendered during the
|
|
2818
|
+
// one render cycle when the liquid error message was first set – causing the input to lose
|
|
2819
|
+
// focus whenever a brace/tag error first appeared.
|
|
2909
2820
|
if (styling === 'semantic') {
|
|
2910
2821
|
columns.push(
|
|
2911
2822
|
<CapColumn key="input" span={val.width} offset={offset}>
|
|
@@ -2913,7 +2824,14 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2913
2824
|
id={val.id}
|
|
2914
2825
|
placeholder={val.placeholder ? val.placeholder : ''}
|
|
2915
2826
|
className={`${ifError ? 'error-form-builder' : ''}`}
|
|
2916
|
-
errorMessage={
|
|
2827
|
+
errorMessage={
|
|
2828
|
+
ifError && errorMessageText && (
|
|
2829
|
+
!this.isLiquidFlowSupportedByChannel() ||
|
|
2830
|
+
[MOBILE_PUSH, INAPP].includes(this.props.schema?.channel?.toUpperCase())
|
|
2831
|
+
)
|
|
2832
|
+
? errorMessageText
|
|
2833
|
+
: ''
|
|
2834
|
+
}
|
|
2917
2835
|
label={val.label}
|
|
2918
2836
|
autosize={val.autosize ? val.autosizeParams : false}
|
|
2919
2837
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
@@ -2944,7 +2862,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2944
2862
|
</CapColumn>
|
|
2945
2863
|
);
|
|
2946
2864
|
}
|
|
2947
|
-
}
|
|
2948
2865
|
};
|
|
2949
2866
|
|
|
2950
2867
|
|
|
@@ -4007,7 +3924,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4007
3924
|
<CapColumn
|
|
4008
3925
|
style={val.colStyle ? val.colStyle : {border : ""}}
|
|
4009
3926
|
span={val.width}
|
|
4010
|
-
className={
|
|
3927
|
+
className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.isLiquidFlowSupportedByChannel() ? "error-boundary" : ""}
|
|
4011
3928
|
>
|
|
4012
3929
|
<CKEditor
|
|
4013
3930
|
id={val.id}
|
|
@@ -4051,7 +3968,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4051
3968
|
isModuleFilterEnabled = this.props.isFullMode;
|
|
4052
3969
|
}
|
|
4053
3970
|
columns.push(
|
|
4054
|
-
<CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={
|
|
3971
|
+
<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.isLiquidFlowSupportedByChannel() ? "error-boundary" : ""}>
|
|
4055
3972
|
<BeeEditor
|
|
4056
3973
|
uid={uuid}
|
|
4057
3974
|
tokenData={beeToken}
|
|
@@ -4337,7 +4254,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4337
4254
|
|
|
4338
4255
|
|
|
4339
4256
|
return (
|
|
4340
|
-
<CapSpin spinning={Boolean((this.
|
|
4257
|
+
<CapSpin spinning={Boolean((this.isLiquidFlowSupportedByChannel() && this.props.liquidExtractionInProgress) || this.props.metaDataStatus === REQUEST)} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
|
|
4341
4258
|
<CapRow>
|
|
4342
4259
|
{this.props.schema && this.renderForm()}
|
|
4343
4260
|
<SlideBox
|
|
@@ -26,10 +26,6 @@ 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
|
-
},
|
|
33
29
|
genericTagsValidationError: {
|
|
34
30
|
id: 'creatives.components.FormBuilder.genericTagsValidationError',
|
|
35
31
|
defaultMessage: 'Please check the message content for unsupported/missing tags',
|
|
@@ -42,10 +38,6 @@ export default defineMessages({
|
|
|
42
38
|
id: 'creatives.componentsV2.FormBuilder.missingTags',
|
|
43
39
|
defaultMessage: 'Missing tags are:',
|
|
44
40
|
},
|
|
45
|
-
unsupportedTags: {
|
|
46
|
-
id: 'creatives.componentsV2.FormBuilder.unsupportedTags',
|
|
47
|
-
defaultMessage: 'Unsupported tags are:',
|
|
48
|
-
},
|
|
49
41
|
upload: {
|
|
50
42
|
id: 'creatives.componentsV2.FormBuilder.upload',
|
|
51
43
|
defaultMessage: 'Upload',
|
|
@@ -102,7 +102,6 @@ const HTMLEditor = forwardRef(({
|
|
|
102
102
|
onTagSelect = null,
|
|
103
103
|
onContextChange = null,
|
|
104
104
|
globalActions = null,
|
|
105
|
-
isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
|
|
106
105
|
isFullMode = true, // Full mode vs library mode - controls layout and visibility
|
|
107
106
|
onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
|
|
108
107
|
onValidationChange = null, // Callback when validation state changes (for parent to track errors)
|
|
@@ -573,7 +572,6 @@ const HTMLEditor = forwardRef(({
|
|
|
573
572
|
content,
|
|
574
573
|
layout,
|
|
575
574
|
validation,
|
|
576
|
-
isLiquidEnabled,
|
|
577
575
|
editorRef: getActiveEditorRef(),
|
|
578
576
|
handleLabelInsert,
|
|
579
577
|
handleSave,
|
|
@@ -593,7 +591,6 @@ const HTMLEditor = forwardRef(({
|
|
|
593
591
|
content,
|
|
594
592
|
layout,
|
|
595
593
|
validation,
|
|
596
|
-
isLiquidEnabled,
|
|
597
594
|
getActiveEditorRef,
|
|
598
595
|
handleLabelInsert,
|
|
599
596
|
handleSave,
|
|
@@ -782,7 +779,6 @@ HTMLEditor.propTypes = {
|
|
|
782
779
|
onTagSelect: PropTypes.func,
|
|
783
780
|
onContextChange: PropTypes.func, // Deprecated: use globalActions instead
|
|
784
781
|
globalActions: PropTypes.object,
|
|
785
|
-
isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
|
|
786
782
|
isFullMode: PropTypes.bool, // Full mode vs library mode
|
|
787
783
|
onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
|
|
788
784
|
onValidationChange: PropTypes.func, // Callback when validation state changes
|
|
@@ -816,7 +812,6 @@ HTMLEditor.defaultProps = {
|
|
|
816
812
|
onTagSelect: null,
|
|
817
813
|
onContextChange: null,
|
|
818
814
|
globalActions: null, // Redux actions for API calls
|
|
819
|
-
isLiquidEnabled: false,
|
|
820
815
|
isFullMode: true, // Default to full mode
|
|
821
816
|
onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
|
|
822
817
|
onValidationChange: null, // Callback when validation state changes
|
|
@@ -3201,7 +3201,6 @@ describe('HTMLEditor', () => {
|
|
|
3201
3201
|
onTagSelect={onTagSelect}
|
|
3202
3202
|
onContextChange={onContextChange}
|
|
3203
3203
|
globalActions={globalActions}
|
|
3204
|
-
isLiquidEnabled={true}
|
|
3205
3204
|
isFullMode={false}
|
|
3206
3205
|
onErrorAcknowledged={onErrorAcknowledged}
|
|
3207
3206
|
onValidationChange={onValidationChange}
|
|
@@ -3261,20 +3260,6 @@ describe('HTMLEditor', () => {
|
|
|
3261
3260
|
expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
|
|
3262
3261
|
});
|
|
3263
3262
|
|
|
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
|
-
|
|
3278
3263
|
it('should handle isFullMode prop', () => {
|
|
3279
3264
|
render(
|
|
3280
3265
|
<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,
|
|
73
73
|
} = context || {};
|
|
74
74
|
const { content: contentValue, updateContent } = content || {};
|
|
75
75
|
const editorRef = useRef(null);
|
|
@@ -298,7 +298,6 @@ const CodeEditorPaneComponent = ({
|
|
|
298
298
|
<ValidationErrorDisplay
|
|
299
299
|
validation={validation}
|
|
300
300
|
onErrorClick={onErrorClick}
|
|
301
|
-
isLiquidEnabled={isLiquidEnabled}
|
|
302
301
|
className="code-editor-pane__validation"
|
|
303
302
|
/>
|
|
304
303
|
</div>
|