@capillarytech/creatives-library 9.0.73-alpha.sprint-cap-212983.19 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +58 -1
- package/v2Components/CapTagListWithInput/index.js +64 -35
- package/v2Components/CapTagListWithInput/index.scss +9 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/EmailPreviewContent.js +8 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +7 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +1 -0
- package/v2Components/CommonTestAndPreview/actions.js +1 -1
- package/v2Components/CommonTestAndPreview/index.js +29 -13
- package/v2Components/CommonTestAndPreview/sagas.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/EmailPreviewContent.test.js +27 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +40 -0
- package/v2Components/CommonTestAndPreview/tests/index.test.js +95 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -0
- package/v2Components/EmailMobilePreview/index.js +8 -1
- package/v2Components/EmailMobilePreview/index.scss +12 -3
- package/v2Components/EmailMobilePreview/tests/index.test.js +31 -0
- package/v2Components/EmailPreviewV2/_emailPreviewV2.scss +37 -5
- package/v2Components/EmailPreviewV2/index.js +18 -6
- package/v2Components/EmailPreviewV2/messages.js +4 -0
- package/v2Components/EmailPreviewV2/tests/index.test.js +43 -0
- package/v2Components/FormBuilder/Classic.js +134 -14
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +9 -0
- package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +11 -12
- package/v2Components/FormBuilder/Functional/tests/mpush.singlePlatform.test.js +144 -0
- package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +17 -5
- package/v2Components/FormBuilder/_formBuilder.scss +21 -0
- package/v2Components/FormBuilder/messages.js +12 -0
- package/v2Components/FormBuilder/tests/mpush.characterization.test.js +45 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +2 -1
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +4 -4
- package/v2Containers/CommunicationFlow/constants.js +1 -0
- package/v2Containers/CommunicationFlow/utils/Tests/mapCommDefinitionToStepData.test.js +2 -1
- package/v2Containers/CommunicationFlow/utils/mapCommDefinitionToStepData.js +1 -0
- package/v2Containers/CreativesContainer/index.js +7 -1
- package/v2Containers/CreativesContainer/tests/index.test.js +81 -0
- package/v2Containers/Email/index.js +31 -4
- package/v2Containers/Email/initialSchema.js +30 -0
- package/v2Containers/Email/messages.js +8 -0
- package/v2Containers/Email/tests/index.test.js +107 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +172 -8
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +264 -5
- package/v2Containers/EmailWrapper/components/_emailHTMLEditor.scss +29 -0
- package/v2Containers/EmailWrapper/constants.js +5 -0
- package/v2Containers/EmailWrapper/messages.js +24 -0
- package/v2Containers/MobilePush/Create/constants.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +2 -2
- package/v2Containers/MobilePush/Create/test/contentValidity.test.js +58 -0
- package/v2Containers/MobilePush/Edit/index.js +2 -2
- package/v2Containers/MobilePush/Edit/test/contentValidity.test.js +17 -0
- package/v2Containers/MobilePush/commonMethods.js +7 -8
- package/v2Containers/TagList/index.js +7 -0
- package/v2Containers/TagList/tests/TagList.test.js +42 -0
- package/v2Containers/Viber/index.scss +0 -31
|
@@ -224,6 +224,7 @@ describe('CommonTestAndPreview Sagas', () => {
|
|
|
224
224
|
expect(generator.next().value).toEqual(
|
|
225
225
|
call(Api.extractTagsWithMetaData, {
|
|
226
226
|
messageTitle: 'Hello {{name}}',
|
|
227
|
+
messageSubtitle: '',
|
|
227
228
|
messageBody: 'Welcome {{email}}',
|
|
228
229
|
})
|
|
229
230
|
);
|
|
@@ -238,6 +239,25 @@ describe('CommonTestAndPreview Sagas', () => {
|
|
|
238
239
|
expect(generator.next().done).toBe(true);
|
|
239
240
|
});
|
|
240
241
|
|
|
242
|
+
it('should pass templatePreheader through as messageSubtitle', () => {
|
|
243
|
+
const action = {
|
|
244
|
+
payload: {
|
|
245
|
+
templateSubject: 'Hello {{name}}',
|
|
246
|
+
templatePreheader: 'Enjoy {{discount}}',
|
|
247
|
+
templateContent: 'Welcome {{email}}',
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const generator = extractTagsSaga(action);
|
|
251
|
+
|
|
252
|
+
expect(generator.next().value).toEqual(
|
|
253
|
+
call(Api.extractTagsWithMetaData, {
|
|
254
|
+
messageTitle: 'Hello {{name}}',
|
|
255
|
+
messageSubtitle: 'Enjoy {{discount}}',
|
|
256
|
+
messageBody: 'Welcome {{email}}',
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
241
261
|
it('should handle empty templateSubject', () => {
|
|
242
262
|
const action = {
|
|
243
263
|
payload: {
|
|
@@ -250,6 +270,7 @@ describe('CommonTestAndPreview Sagas', () => {
|
|
|
250
270
|
expect(generator.next().value).toEqual(
|
|
251
271
|
call(Api.extractTagsWithMetaData, {
|
|
252
272
|
messageTitle: '',
|
|
273
|
+
messageSubtitle: '',
|
|
253
274
|
messageBody: 'Welcome {{email}}',
|
|
254
275
|
})
|
|
255
276
|
);
|
|
@@ -268,6 +289,7 @@ describe('CommonTestAndPreview Sagas', () => {
|
|
|
268
289
|
expect(generator.next().value).toEqual(
|
|
269
290
|
call(Api.extractTagsWithMetaData, {
|
|
270
291
|
messageTitle: 'Hello {{name}}',
|
|
292
|
+
messageSubtitle: '',
|
|
271
293
|
messageBody: '',
|
|
272
294
|
})
|
|
273
295
|
);
|
|
@@ -284,6 +306,7 @@ describe('CommonTestAndPreview Sagas', () => {
|
|
|
284
306
|
expect(generator.next().value).toEqual(
|
|
285
307
|
call(Api.extractTagsWithMetaData, {
|
|
286
308
|
messageTitle: '',
|
|
309
|
+
messageSubtitle: '',
|
|
287
310
|
messageBody: 'Welcome {{email}}',
|
|
288
311
|
})
|
|
289
312
|
);
|
|
@@ -301,6 +324,7 @@ describe('CommonTestAndPreview Sagas', () => {
|
|
|
301
324
|
expect(generator.next().value).toEqual(
|
|
302
325
|
call(Api.extractTagsWithMetaData, {
|
|
303
326
|
messageTitle: 'Hello {{name}}',
|
|
327
|
+
messageSubtitle: '',
|
|
304
328
|
messageBody: '',
|
|
305
329
|
})
|
|
306
330
|
);
|
|
@@ -29,7 +29,7 @@ const deviceAspectRatio = {
|
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
class EmailMobilePreview extends React.Component {
|
|
32
|
+
export class EmailMobilePreview extends React.Component {
|
|
33
33
|
static getDerivedStateFromProps(props) {
|
|
34
34
|
if (props.showAllDevices) {
|
|
35
35
|
return {
|
|
@@ -66,6 +66,7 @@ class EmailMobilePreview extends React.Component {
|
|
|
66
66
|
templateContent,
|
|
67
67
|
enableShowAllDevices,
|
|
68
68
|
templateSubject,
|
|
69
|
+
templatePreHeader,
|
|
69
70
|
} = this.props;
|
|
70
71
|
const copyContent = templateContent && removeLinksFromHtmlContent(templateContent);
|
|
71
72
|
const updateCopyContent = templateContent && iframePreviewAdjustWidth(copyContent);
|
|
@@ -76,6 +77,10 @@ class EmailMobilePreview extends React.Component {
|
|
|
76
77
|
<CapLabel className="email-subject-mobile" type="label2">
|
|
77
78
|
<FormattedMessage {...messages.subject} /> {templateSubject}</CapLabel>
|
|
78
79
|
);
|
|
80
|
+
const emailPreHeaderLabel = templatePreHeader && (
|
|
81
|
+
<CapLabel className="email-preheader-mobile" type="label2">
|
|
82
|
+
<FormattedMessage {...messages.preheader} /> {templatePreHeader}</CapLabel>
|
|
83
|
+
);
|
|
79
84
|
|
|
80
85
|
return (
|
|
81
86
|
<div className="email-mobile-preview-container">
|
|
@@ -100,6 +105,7 @@ class EmailMobilePreview extends React.Component {
|
|
|
100
105
|
<img src={mobileBody} alt="capillary" width="386" />
|
|
101
106
|
<div className={`${messageContainerName} ${emailSubjectLabel ? 'with-subject' : ''}`}>
|
|
102
107
|
{emailSubjectLabel}
|
|
108
|
+
{emailPreHeaderLabel}
|
|
103
109
|
<iframe srcDoc={updateCopyContent} {...deviceAspectRatio.mobile} >
|
|
104
110
|
<p><FormattedMessage {...messages.noIframeSupport}/></p>
|
|
105
111
|
</iframe>
|
|
@@ -127,6 +133,7 @@ class EmailMobilePreview extends React.Component {
|
|
|
127
133
|
EmailMobilePreview.propTypes = {
|
|
128
134
|
templateContent: PropTypes.string,
|
|
129
135
|
templateSubject: PropTypes.string,
|
|
136
|
+
templatePreHeader: PropTypes.string,
|
|
130
137
|
templateData: PropTypes.object,
|
|
131
138
|
templateActions: PropTypes.object,
|
|
132
139
|
onClickShowInAllDevices: PropTypes.func,
|
|
@@ -44,10 +44,19 @@ $classPrefix: email-mobile-preview-container;
|
|
|
44
44
|
justify-content: center;
|
|
45
45
|
}
|
|
46
46
|
.email-subject-mobile {
|
|
47
|
-
width:
|
|
47
|
+
width: 14.1875rem;
|
|
48
48
|
text-align: left;
|
|
49
|
-
margin:
|
|
50
|
-
|
|
49
|
+
margin: 0.5rem 0.25rem 0;
|
|
50
|
+
padding-bottom: 0.5rem;
|
|
51
|
+
border-bottom: 0.0625rem solid #d9d9d9;
|
|
52
|
+
}
|
|
53
|
+
.email-preheader-mobile {
|
|
54
|
+
width: 14.1875rem;
|
|
55
|
+
text-align: left;
|
|
56
|
+
margin: 0.5rem 0.25rem 0;
|
|
57
|
+
padding-bottom: 0.5rem;
|
|
58
|
+
border-bottom: 0.0625rem solid #d9d9d9;
|
|
59
|
+
color: #8c8c8c;
|
|
51
60
|
}
|
|
52
61
|
// .mobile-message-container {
|
|
53
62
|
// position: absolute;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallow } from 'enzyme';
|
|
3
|
+
import { EmailMobilePreview } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('<EmailMobilePreview />', () => {
|
|
6
|
+
const baseProps = {
|
|
7
|
+
templateContent: '<p>Body</p>',
|
|
8
|
+
templateActions: {
|
|
9
|
+
getTemplateDetails: jest.fn(),
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it('does not render a pre-header label when templatePreHeader is not provided', () => {
|
|
14
|
+
const wrapper = shallow(<EmailMobilePreview {...baseProps} templateSubject="Hello" />);
|
|
15
|
+
expect(wrapper.find('.email-preheader-mobile')).toHaveLength(0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders the pre-header label when templatePreHeader is provided', () => {
|
|
19
|
+
const wrapper = shallow(
|
|
20
|
+
<EmailMobilePreview {...baseProps} templateSubject="Hello" templatePreHeader="Enjoy 20% off" />
|
|
21
|
+
);
|
|
22
|
+
const label = wrapper.find('.email-preheader-mobile');
|
|
23
|
+
expect(label).toHaveLength(1);
|
|
24
|
+
expect(label.text()).toContain('Enjoy 20% off');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders the pre-header label even when subject is absent', () => {
|
|
28
|
+
const wrapper = shallow(<EmailMobilePreview {...baseProps} templatePreHeader="Enjoy 20% off" />);
|
|
29
|
+
expect(wrapper.find('.email-preheader-mobile')).toHaveLength(1);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
@import '~@capillarytech/cap-ui-library/styles/_variables';
|
|
2
|
+
|
|
1
3
|
$classPrefix: email-preview-v2;
|
|
4
|
+
$preheaderMutedColor: #8c8c8c;
|
|
5
|
+
|
|
2
6
|
.#{$classPrefix}:not(.email-mobile-preview-container){
|
|
3
7
|
#emailPreivew-container {
|
|
4
8
|
position: relative;
|
|
@@ -54,19 +58,47 @@ $classPrefix: email-preview-v2;
|
|
|
54
58
|
.email-subject-mobile {
|
|
55
59
|
width: 310px;
|
|
56
60
|
text-align: left;
|
|
57
|
-
margin:
|
|
58
|
-
|
|
61
|
+
margin: 0.5rem 0.25rem 0;
|
|
62
|
+
padding-bottom: 0.5rem;
|
|
63
|
+
border-bottom: 0.0625rem solid $CAP_COLOR_16;
|
|
59
64
|
}
|
|
60
65
|
.email-subject-tablet {
|
|
61
66
|
width: 450px;
|
|
62
67
|
text-align: left;
|
|
63
|
-
margin:
|
|
64
|
-
|
|
68
|
+
margin: 0.5rem 0.25rem 0;
|
|
69
|
+
padding-bottom: 0.5rem;
|
|
70
|
+
border-bottom: 0.0625rem solid $CAP_COLOR_16;
|
|
65
71
|
}
|
|
66
72
|
.email-subject-desktop {
|
|
67
73
|
text-align: left;
|
|
68
74
|
margin-left: 6%;
|
|
69
|
-
margin-
|
|
75
|
+
margin-right: 6%;
|
|
76
|
+
padding-bottom: 0.5rem;
|
|
77
|
+
border-bottom: 0.0625rem solid $CAP_COLOR_16;
|
|
78
|
+
}
|
|
79
|
+
.email-preheader-mobile {
|
|
80
|
+
width: 19.375rem;
|
|
81
|
+
text-align: left;
|
|
82
|
+
margin: 0.5rem 0.25rem 0;
|
|
83
|
+
padding-bottom: 0.5rem;
|
|
84
|
+
border-bottom: 0.0625rem solid $CAP_COLOR_16;
|
|
85
|
+
color: $preheaderMutedColor;
|
|
86
|
+
}
|
|
87
|
+
.email-preheader-tablet {
|
|
88
|
+
width: 28.125rem;
|
|
89
|
+
text-align: left;
|
|
90
|
+
margin: 0.5rem 0.25rem 0;
|
|
91
|
+
padding-bottom: 0.5rem;
|
|
92
|
+
border-bottom: 0.0625rem solid $CAP_COLOR_16;
|
|
93
|
+
color: $preheaderMutedColor;
|
|
94
|
+
}
|
|
95
|
+
.email-preheader-desktop {
|
|
96
|
+
text-align: left;
|
|
97
|
+
margin-left: 6%;
|
|
98
|
+
margin-right: 6%;
|
|
99
|
+
padding-bottom: 0.5rem;
|
|
100
|
+
border-bottom: 0.0625rem solid $CAP_COLOR_16;
|
|
101
|
+
color: $preheaderMutedColor;
|
|
70
102
|
}
|
|
71
103
|
.tablet-message-container {
|
|
72
104
|
position: absolute;
|
|
@@ -30,12 +30,12 @@ const deviceAspectRatio = {
|
|
|
30
30
|
height: "500",
|
|
31
31
|
width: "1024",
|
|
32
32
|
}),
|
|
33
|
-
tablet: (emailSubject) => ({
|
|
34
|
-
height: emailSubject ? "570" : "600",
|
|
33
|
+
tablet: (emailSubject, emailPreHeader) => ({
|
|
34
|
+
height: emailSubject ? (emailPreHeader ? "545" : "570") : "600",
|
|
35
35
|
width: "450",
|
|
36
36
|
}),
|
|
37
|
-
mobile: (emailSubject) => ({
|
|
38
|
-
height: emailSubject ? "541" : "511",
|
|
37
|
+
mobile: (emailSubject, emailPreHeader) => ({
|
|
38
|
+
height: emailSubject ? (emailPreHeader ? "516" : "541") : "511",
|
|
39
39
|
width: "340",
|
|
40
40
|
}),
|
|
41
41
|
};
|
|
@@ -72,25 +72,36 @@ export class EmailPreviewV2 extends React.Component {
|
|
|
72
72
|
render() {
|
|
73
73
|
let messageContainerName = '';
|
|
74
74
|
const {currentDevice} = this.state;
|
|
75
|
-
const {
|
|
75
|
+
const {
|
|
76
|
+
templateContent, templateData = {}, templateSubject, templatePreHeader,
|
|
77
|
+
} = this.props;
|
|
76
78
|
const copyContent = templateContent && removeLinksFromHtmlContent(templateContent);
|
|
77
79
|
const updateCopyContent = templateContent && iframePreviewAdjustWidth(copyContent);
|
|
78
80
|
let subjectClassName = 'email-subject-desktop';
|
|
81
|
+
let preheaderClassName = 'email-preheader-desktop';
|
|
79
82
|
if (currentDevice === devices.tablet) {
|
|
80
83
|
messageContainerName = 'tablet-message-container';
|
|
81
84
|
subjectClassName = "email-subject-tablet";
|
|
85
|
+
preheaderClassName = "email-preheader-tablet";
|
|
82
86
|
} else if (currentDevice === devices.mobile) {
|
|
83
87
|
messageContainerName = 'mobile-message-container';
|
|
84
88
|
subjectClassName = "email-subject-mobile";
|
|
89
|
+
preheaderClassName = "email-preheader-mobile";
|
|
85
90
|
}
|
|
86
91
|
const emailSubject = get(templateData, 'versions.base.subject') || templateSubject;
|
|
92
|
+
const emailPreHeader = get(templateData, 'versions.base.preheader') || templatePreHeader;
|
|
87
93
|
const emailSubjectLabel = emailSubject && (
|
|
88
94
|
<CapLabel className={subjectClassName} type="label2">
|
|
89
95
|
<FormattedMessage {...messages.subject} /> {emailSubject}</CapLabel>
|
|
90
96
|
);
|
|
97
|
+
const emailPreHeaderLabel = emailPreHeader && (
|
|
98
|
+
<CapLabel className={preheaderClassName} type="label2">
|
|
99
|
+
<FormattedMessage {...messages.preheader} /> {emailPreHeader}</CapLabel>
|
|
100
|
+
);
|
|
91
101
|
const iframeDiv = (<div className={`${messageContainerName}`}>
|
|
92
102
|
{emailSubjectLabel}
|
|
93
|
-
|
|
103
|
+
{emailPreHeaderLabel}
|
|
104
|
+
<iframe srcDoc={updateCopyContent} {...deviceAspectRatio[currentDevice](emailSubject, emailPreHeader)} >
|
|
94
105
|
<p><FormattedMessage {...messages.browserDoesntSupportIframe} /></p>
|
|
95
106
|
</iframe>
|
|
96
107
|
</div>);
|
|
@@ -171,6 +182,7 @@ export class EmailPreviewV2 extends React.Component {
|
|
|
171
182
|
EmailPreviewV2.propTypes = {
|
|
172
183
|
templateContent: PropTypes.string,
|
|
173
184
|
templateSubject: PropTypes.string,
|
|
185
|
+
templatePreHeader: PropTypes.string,
|
|
174
186
|
templateData: PropTypes.object,
|
|
175
187
|
templateActions: PropTypes.object,
|
|
176
188
|
device: PropTypes.string,
|
|
@@ -42,6 +42,10 @@ export default defineMessages({
|
|
|
42
42
|
id: 'creatives.componentsV2.EmailPreviewV2.subject',
|
|
43
43
|
defaultMessage: 'Subject:',
|
|
44
44
|
},
|
|
45
|
+
preheader: {
|
|
46
|
+
id: 'creatives.componentsV2.EmailPreviewV2.preheader',
|
|
47
|
+
defaultMessage: 'Pre-header:',
|
|
48
|
+
},
|
|
45
49
|
browserDoesntSupportIframe: {
|
|
46
50
|
id: 'creatives.componentsV2.EmailPreviewV2.browserDoesntSupportIframe',
|
|
47
51
|
defaultMessage: 'Your browser does not support iframes.',
|
|
@@ -13,4 +13,47 @@ describe('<EmailPreviewV2 />', () => {
|
|
|
13
13
|
/>);
|
|
14
14
|
expect(compoent).toMatchSnapshot();
|
|
15
15
|
});
|
|
16
|
+
|
|
17
|
+
const baseProps = {
|
|
18
|
+
templateData: {},
|
|
19
|
+
templateActions: {
|
|
20
|
+
getTemplateDetails: jest.fn(),
|
|
21
|
+
resetTemplateData: jest.fn(),
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// The desktop pane's content is passed as a prop to CapTab (not a rendered child), so
|
|
26
|
+
// shallow() won't reach into it via .find() directly - render that pane's content JSX
|
|
27
|
+
// on its own to inspect it.
|
|
28
|
+
const getDesktopPaneWrapper = (wrapper) => {
|
|
29
|
+
const panes = wrapper.find('CapTab').prop('panes');
|
|
30
|
+
const desktopPane = panes.find((pane) => pane.key === 'desktop');
|
|
31
|
+
return shallow(desktopPane.content);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
it('does not render a pre-header label when templatePreHeader is not provided', () => {
|
|
35
|
+
const wrapper = shallow(<EmailPreviewV2 {...baseProps} templateSubject="Hello" />);
|
|
36
|
+
expect(getDesktopPaneWrapper(wrapper).find('.email-preheader-desktop')).toHaveLength(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders the pre-header label for the current (desktop) device by default', () => {
|
|
40
|
+
const wrapper = shallow(
|
|
41
|
+
<EmailPreviewV2 {...baseProps} templateSubject="Hello" templatePreHeader="Enjoy 20% off" />
|
|
42
|
+
);
|
|
43
|
+
const label = getDesktopPaneWrapper(wrapper).find('.email-preheader-desktop');
|
|
44
|
+
expect(label).toHaveLength(1);
|
|
45
|
+
expect(label.text()).toContain('Enjoy 20% off');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('prefers templateData.versions.base.preheader over the templatePreHeader prop', () => {
|
|
49
|
+
const wrapper = shallow(
|
|
50
|
+
<EmailPreviewV2
|
|
51
|
+
{...baseProps}
|
|
52
|
+
templateData={{ versions: { base: { preheader: 'From template data' } } }}
|
|
53
|
+
templatePreHeader="From prop"
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
const label = getDesktopPaneWrapper(wrapper).find('.email-preheader-desktop');
|
|
57
|
+
expect(label.text()).toContain('From template data');
|
|
58
|
+
});
|
|
16
59
|
});
|
|
@@ -30,7 +30,9 @@ import CapUploader from '@capillarytech/cap-ui-library/CapUploader';
|
|
|
30
30
|
import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
31
31
|
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
32
32
|
import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
|
|
33
|
+
import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
|
|
33
34
|
import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
|
|
35
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
34
36
|
|
|
35
37
|
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
|
|
36
38
|
import LabelHOC from '@capillarytech/cap-ui-library/assets/HOCs/ComponentWithLabelHOC';
|
|
@@ -61,7 +63,7 @@ import { convert } from 'html-to-text';
|
|
|
61
63
|
import { OUTBOUND, ADD_LANGUAGE, UPLOAD, USE_EDITOR, COPY_PRIMARY_LANGUAGE, GLOBAL_CONVERT_OPTIONS } from './constants';
|
|
62
64
|
import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
|
|
63
65
|
import moment from 'moment';
|
|
64
|
-
import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS} from '../../constants/unified';
|
|
66
|
+
import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS, UNSUBSCRIBE_TAG} from '../../constants/unified';
|
|
65
67
|
import { REQUEST } from '../../v2Containers/Cap/constants'
|
|
66
68
|
import { isEmailUnsubscribeTagOptional, isAiContentBotDisabled } from '../../utils/common';
|
|
67
69
|
import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
|
|
@@ -70,6 +72,7 @@ import { getChannelData, hasPersonalizationTags, validateLiquidTemplateContent,
|
|
|
70
72
|
const {Column} = Table;
|
|
71
73
|
const {TextArea} = CapInput;
|
|
72
74
|
const {CapRadioGroup} = CapRadio;
|
|
75
|
+
const {CapLabelInline} = CapLabel;
|
|
73
76
|
// import styled from 'styled-components';
|
|
74
77
|
|
|
75
78
|
const tagsTypes = {
|
|
@@ -110,21 +113,32 @@ class HighFreqInput extends React.Component {
|
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
//
|
|
116
|
+
// EMAIL fields kept visible in BEE mode; use the blur-commit HighFreqTagInput below.
|
|
117
|
+
const HIGH_FREQ_EMAIL_TAG_INPUT_IDS = ['template-subject', 'template-preheader'];
|
|
118
|
+
|
|
119
|
+
// EMAIL subject/preheader wrapper: commits on blur, like HighFreqInput.
|
|
114
120
|
class HighFreqTagInput extends React.Component {
|
|
115
121
|
constructor(props) {
|
|
116
122
|
super(props);
|
|
117
|
-
this.state = { localInputValue: props.inputValue || '' };
|
|
123
|
+
this.state = { localInputValue: props.inputValue || '', errorDismissed: false };
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
componentDidUpdate(prevProps) {
|
|
121
127
|
if (prevProps.inputValue !== this.props.inputValue && this.state.localInputValue !== this.props.inputValue) {
|
|
122
128
|
this.setState({ localInputValue: this.props.inputValue || '' });
|
|
123
129
|
}
|
|
130
|
+
// New errorDataRef = fresh validation (each Save): un-dismiss so a repeated error shows again.
|
|
131
|
+
if (this.props.errorDataRef !== undefined && prevProps.errorDataRef !== this.props.errorDataRef && this.state.errorDismissed) {
|
|
132
|
+
this.setState({ errorDismissed: false });
|
|
133
|
+
}
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
handleInputChange = (e) => {
|
|
127
137
|
this.setState({ localInputValue: e.target.value });
|
|
138
|
+
// Hide a stale error on edit (like the HTML editor's Pre-header); re-validates only on Save.
|
|
139
|
+
if (this.props.errorDataRef !== undefined && !this.state.errorDismissed) {
|
|
140
|
+
this.setState({ errorDismissed: true });
|
|
141
|
+
}
|
|
128
142
|
};
|
|
129
143
|
|
|
130
144
|
handleBlur = () => {
|
|
@@ -132,13 +146,39 @@ class HighFreqTagInput extends React.Component {
|
|
|
132
146
|
};
|
|
133
147
|
|
|
134
148
|
render() {
|
|
135
|
-
const {
|
|
149
|
+
const {
|
|
150
|
+
inputValue: _iv, onCommit: _oc, inputOnChange: _ic, inputMaxLength, onRemove, removeLabel,
|
|
151
|
+
errorDataRef, inputErrorMessage, ...rest
|
|
152
|
+
} = this.props;
|
|
153
|
+
const counterSuffix = inputMaxLength ? (
|
|
154
|
+
<CapLabelInline type="label1" className="high-freq-tag-input-counter">
|
|
155
|
+
{`${this.state.localInputValue.length} / ${inputMaxLength}`}
|
|
156
|
+
</CapLabelInline>
|
|
157
|
+
) : null;
|
|
158
|
+
const removeSuffix = onRemove ? (
|
|
159
|
+
<CapLabelInline type="label1" role="button" tabIndex={0} className="high-freq-tag-input-remove-link" onClick={onRemove}>
|
|
160
|
+
{removeLabel}
|
|
161
|
+
</CapLabelInline>
|
|
162
|
+
) : null;
|
|
163
|
+
const combinedSuffix = (counterSuffix || removeSuffix) ? (
|
|
164
|
+
<CapRow align="middle" type="flex" noWrap gap={8}>
|
|
165
|
+
{counterSuffix}
|
|
166
|
+
{removeSuffix}
|
|
167
|
+
</CapRow>
|
|
168
|
+
) : undefined;
|
|
136
169
|
return (
|
|
137
170
|
<CapTagListWithInput
|
|
138
171
|
{...rest}
|
|
172
|
+
inputMaxLength={inputMaxLength}
|
|
139
173
|
inputValue={this.state.localInputValue}
|
|
140
174
|
inputOnChange={this.handleInputChange}
|
|
141
|
-
|
|
175
|
+
inputErrorMessage={this.state.errorDismissed ? '' : inputErrorMessage}
|
|
176
|
+
inputProps={{
|
|
177
|
+
...(this.props.inputProps || {}),
|
|
178
|
+
onBlur: this.handleBlur,
|
|
179
|
+
...(combinedSuffix ? { suffix: combinedSuffix } : {}),
|
|
180
|
+
}}
|
|
181
|
+
singleLineLayout
|
|
142
182
|
/>
|
|
143
183
|
);
|
|
144
184
|
}
|
|
@@ -169,6 +209,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
169
209
|
STANDARD_ERROR_MSG: [],
|
|
170
210
|
LIQUID_ERROR_MSG: [],
|
|
171
211
|
},
|
|
212
|
+
// Pre-header visibility: null = show if it has content; true/false = user's Add/Remove click.
|
|
213
|
+
preheaderRevealed: null,
|
|
172
214
|
};
|
|
173
215
|
this.renderForm = this.renderForm.bind(this);
|
|
174
216
|
this.updateFormData = this.updateFormData.bind(this);
|
|
@@ -596,6 +638,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
596
638
|
if (!_.isEqual(prevProps.isTransactional, this.props.isTransactional)) {
|
|
597
639
|
this.validateForm(this.props.tags, this.props.injectedTags);
|
|
598
640
|
}
|
|
641
|
+
const panes = _.get(this.props, 'schema.containers[0].panes', []);
|
|
642
|
+
if (_.toUpper(_.get(this.props, 'schema.channel')) === MOBILE_PUSH && _.get(panes, `[${this.state.currentTab - 1}].isSupported`) === false) {
|
|
643
|
+
const visiblePaneIndex = _.findIndex(panes, (pane) => pane?.isSupported !== false);
|
|
644
|
+
if (visiblePaneIndex !== -1) {
|
|
645
|
+
this.setState({ currentTab: visiblePaneIndex + 1 });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
599
648
|
}
|
|
600
649
|
|
|
601
650
|
onEdit() {
|
|
@@ -1245,6 +1294,16 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1245
1294
|
}
|
|
1246
1295
|
}
|
|
1247
1296
|
|
|
1297
|
+
// Pre-header must not contain the unsubscribe tag (checked on Save, not per keystroke).
|
|
1298
|
+
if (index === 'template-preheader') {
|
|
1299
|
+
if (hasUnsubscribeTag(data)) {
|
|
1300
|
+
errorData[index] = true;
|
|
1301
|
+
isValid = false;
|
|
1302
|
+
} else {
|
|
1303
|
+
errorData[index] = false;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1248
1307
|
// Check if Template name present in full mode
|
|
1249
1308
|
if(type !== 'embedded' && index === 'template-name' && data === '') {
|
|
1250
1309
|
errorData[index] = true;
|
|
@@ -3447,6 +3506,18 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3447
3506
|
let contentSections = [];
|
|
3448
3507
|
let popoverSections = [];
|
|
3449
3508
|
const self = this;
|
|
3509
|
+
// Pre-header visibility: null = show if it has content; true/false = user's Add/Remove click.
|
|
3510
|
+
const preheaderValueForRenderSection = this.state.formData['template-preheader'] || '';
|
|
3511
|
+
const isPreheaderRevealed = this.state.preheaderRevealed !== null
|
|
3512
|
+
? this.state.preheaderRevealed
|
|
3513
|
+
: !!preheaderValueForRenderSection;
|
|
3514
|
+
// Hidden Pre-header section renders no fields by design — not the "not ready" spinner case below.
|
|
3515
|
+
const isHiddenPreheaderOnlySection = !isPreheaderRevealed
|
|
3516
|
+
&& section.inputFields
|
|
3517
|
+
&& section.inputFields.length === 1
|
|
3518
|
+
&& section.inputFields[0].cols
|
|
3519
|
+
&& section.inputFields[0].cols.length === 1
|
|
3520
|
+
&& section.inputFields[0].cols[0].id === 'template-preheader';
|
|
3450
3521
|
_.forEach(section.inputFields, (field) => {
|
|
3451
3522
|
const columns = [];
|
|
3452
3523
|
_.forEach(field.cols, (val) => {
|
|
@@ -3807,14 +3878,19 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3807
3878
|
const isBEEAppEnableForCapTagList = this.checkBeeEditorAllowedForLibrary();
|
|
3808
3879
|
ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
|
|
3809
3880
|
|
|
3810
|
-
//
|
|
3881
|
+
// Pre-header hidden behind "+ Pre-header" unless it has content or the user toggled it.
|
|
3882
|
+
if (val.id === 'template-preheader' && !isPreheaderRevealed) {
|
|
3883
|
+
break;
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
// Subject/Pre-header render even in BEE mode
|
|
3811
3887
|
if (
|
|
3812
|
-
val.id
|
|
3888
|
+
HIGH_FREQ_EMAIL_TAG_INPUT_IDS.includes(val.id) ||
|
|
3813
3889
|
!(_.get(this.state, `formData[${this.state.currentTab - 1}][${currentLangForCapTagList}].is_drag_drop`, false)) ||
|
|
3814
3890
|
isBEEAppEnableForCapTagList === false ||
|
|
3815
3891
|
channelForCapTagList !== 'EMAIL'
|
|
3816
3892
|
) {
|
|
3817
|
-
const isEmailStandaloneSubject = val.standalone && channelForCapTagList === EMAIL && val.id
|
|
3893
|
+
const isEmailStandaloneSubject = val.standalone && channelForCapTagList === EMAIL && HIGH_FREQ_EMAIL_TAG_INPUT_IDS.includes(val.id);
|
|
3818
3894
|
const tagListProps = {
|
|
3819
3895
|
key: `input-${val.id}`,
|
|
3820
3896
|
inputId: val.id,
|
|
@@ -3823,9 +3899,12 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3823
3899
|
inputErrorMessage: val.errorMessage && ifError ? val.errorMessage : '',
|
|
3824
3900
|
inputRequired: val.required || false,
|
|
3825
3901
|
inputDisabled: val.disabled || false,
|
|
3902
|
+
inputMaxLength: val.maxLength,
|
|
3826
3903
|
headingText: val.label || '',
|
|
3827
3904
|
headingStyle: val.headingStyle || { marginTop: '3%', marginRight: '79%' },
|
|
3828
3905
|
headingType: "h4",
|
|
3906
|
+
// Label is an inline input prefix (see inputProps), so no heading here.
|
|
3907
|
+
showHeading: !isEmailStandaloneSubject,
|
|
3829
3908
|
onTagSelect: (data) => this.callChildEvent(data, val, 'onTagSelect'),
|
|
3830
3909
|
onContextChange: this.props.onContextChange,
|
|
3831
3910
|
location: this.props.location,
|
|
@@ -3838,10 +3917,47 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3838
3917
|
waitEventContextTags: this.props?.waitEventContextTags,
|
|
3839
3918
|
moduleFilterEnabled: moduleFilterEnabledForCapTagList,
|
|
3840
3919
|
containerStyle: val.style || {},
|
|
3841
|
-
inputProps:
|
|
3920
|
+
inputProps: isEmailStandaloneSubject
|
|
3921
|
+
? {
|
|
3922
|
+
...(val.inputProps || {}),
|
|
3923
|
+
prefix: (
|
|
3924
|
+
<CapRow align="middle" type="flex" noWrap gap={4} className="high-freq-tag-input-prefix">
|
|
3925
|
+
<CapLabelInline type="label2" className="high-freq-tag-input-prefix-label">
|
|
3926
|
+
{val.label || ''}
|
|
3927
|
+
</CapLabelInline>
|
|
3928
|
+
{val.id === 'template-preheader' && (
|
|
3929
|
+
<CapTooltipWithInfo title={this.props.intl.formatMessage(messages.preheaderInfoTooltip)} />
|
|
3930
|
+
)}
|
|
3931
|
+
</CapRow>
|
|
3932
|
+
),
|
|
3933
|
+
}
|
|
3934
|
+
: (val.inputProps || {}),
|
|
3842
3935
|
showInput: val.showInput !== false,
|
|
3843
3936
|
showTagList: val.showTagList !== false,
|
|
3844
3937
|
restrictPersonalization: this.props.restrictPersonalization,
|
|
3938
|
+
...(val.id === 'template-subject' && !isPreheaderRevealed ? {
|
|
3939
|
+
extraContent: (
|
|
3940
|
+
<CapButton
|
|
3941
|
+
type="dashed"
|
|
3942
|
+
size="small"
|
|
3943
|
+
style={{ whiteSpace: 'nowrap' }}
|
|
3944
|
+
onClick={() => this.setState({ preheaderRevealed: true })}
|
|
3945
|
+
>
|
|
3946
|
+
{this.props.intl.formatMessage(messages.addPreheader)}
|
|
3947
|
+
</CapButton>
|
|
3948
|
+
),
|
|
3949
|
+
} : {}),
|
|
3950
|
+
...(val.id === 'template-preheader' ? {
|
|
3951
|
+
onRemove: () => {
|
|
3952
|
+
this.performFormDataUpdate('', val);
|
|
3953
|
+
this.setState({ preheaderRevealed: false });
|
|
3954
|
+
},
|
|
3955
|
+
removeLabel: this.props.intl.formatMessage(messages.removePreheader),
|
|
3956
|
+
// Unsubscribe is not a valid Pre-header tag — hide it from the Add Label dropdown.
|
|
3957
|
+
excludeTags: [UNSUBSCRIBE_TAG],
|
|
3958
|
+
// New ref per Save/Done click; lets HighFreqTagInput hide stale errors while editing.
|
|
3959
|
+
errorDataRef: this.state.errorData,
|
|
3960
|
+
} : {}),
|
|
3845
3961
|
};
|
|
3846
3962
|
columns.push(
|
|
3847
3963
|
<CapColumn key={`input-${val.id}`} offset={val.offset} span={val.width ? val.width : ''} style={val.style ? val.style : {marginBottom: '16px'}}>
|
|
@@ -4249,7 +4365,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4249
4365
|
fields.push(row);
|
|
4250
4366
|
}
|
|
4251
4367
|
});
|
|
4252
|
-
if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === EMAIL && !fields.length) {
|
|
4368
|
+
if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === EMAIL && !fields.length && !isHiddenPreheaderOnlySection) {
|
|
4253
4369
|
return (
|
|
4254
4370
|
<div style={{ textAlign: 'center' }}>
|
|
4255
4371
|
<CapSpin spinning />
|
|
@@ -4281,7 +4397,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4281
4397
|
const renderedPanes = [];
|
|
4282
4398
|
let renderedSections = [];
|
|
4283
4399
|
let renderedContentSections = [];
|
|
4284
|
-
let paneIndex = 1;
|
|
4285
4400
|
let panes = [];
|
|
4286
4401
|
let result = [];
|
|
4287
4402
|
let extraActionSections = [];
|
|
@@ -4292,6 +4407,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4292
4407
|
case "tabs":
|
|
4293
4408
|
panes = container.panes;
|
|
4294
4409
|
_.forEach(panes, (pane, index) => {
|
|
4410
|
+
const paneIndex = index + 1;
|
|
4295
4411
|
renderedSections = pane.sections.map((section) => {
|
|
4296
4412
|
result = this.renderSection(section, paneIndex);
|
|
4297
4413
|
return result;
|
|
@@ -4341,7 +4457,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4341
4457
|
})
|
|
4342
4458
|
),
|
|
4343
4459
|
});
|
|
4344
|
-
paneIndex += 1;
|
|
4345
4460
|
}
|
|
4346
4461
|
});
|
|
4347
4462
|
extraActionSections = container.tabBarExtraContent && container.tabBarExtraContent.sections ? container.tabBarExtraContent.sections.map((section) => {
|
|
@@ -4352,11 +4467,16 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4352
4467
|
if (stateTabKey && stateTabKey !== '') {
|
|
4353
4468
|
activeTabKey = stateTabKey;
|
|
4354
4469
|
}
|
|
4470
|
+
activeTabKey = activeTabKey && activeTabKey !== '' ? `${activeTabKey}` : '1';
|
|
4471
|
+
if (renderedPanes?.length && !_.some(renderedPanes, (renderedPane) => `${renderedPane?.key}` === activeTabKey)
|
|
4472
|
+
&& _.some(panes, (pane) => pane?.isSupported === false)) {
|
|
4473
|
+
activeTabKey = `${renderedPanes[0]?.key}`;
|
|
4474
|
+
}
|
|
4355
4475
|
return (
|
|
4356
4476
|
<CapTab
|
|
4357
4477
|
className={`cap-tabs-${container.id}`}
|
|
4358
|
-
activeKey={activeTabKey
|
|
4359
|
-
defaultActiveKey={activeTabKey
|
|
4478
|
+
activeKey={activeTabKey}
|
|
4479
|
+
defaultActiveKey={activeTabKey}
|
|
4360
4480
|
onChange={(data, e) => { this.callChildEvent(data, container, 'onTabChange', e); }}
|
|
4361
4481
|
onEdit={(data, e) => this.onEdit(data, e)}
|
|
4362
4482
|
style={{width: '100%'}}
|
|
@@ -303,6 +303,15 @@ const FormBuilderShell = (props) => {
|
|
|
303
303
|
dispatch(setActiveTab(currentTabProp - 1));
|
|
304
304
|
}, [currentTabProp, hasTabs]);
|
|
305
305
|
|
|
306
|
+
// Field writes target the active tab, so it must never be a hidden (isSupported: false) pane.
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
if (!hasTabs) return;
|
|
309
|
+
const panes = schema?.containers?.find((container) => container?.type === 'tabs')?.panes || [];
|
|
310
|
+
if (panes[activeTabIndex]?.isSupported !== false) return;
|
|
311
|
+
const visiblePaneIndex = panes.findIndex((pane) => pane?.isSupported !== false);
|
|
312
|
+
if (visiblePaneIndex !== -1) dispatch(setActiveTab(visiblePaneIndex));
|
|
313
|
+
}, [activeTabIndex, schema, hasTabs]);
|
|
314
|
+
|
|
306
315
|
// ---- container-driven modal visibility (mirrors Classic's props->state modal sync) ----
|
|
307
316
|
useEffect(() => {
|
|
308
317
|
setModalVisible(Boolean(showModal));
|