@capillarytech/creatives-library 8.0.254 → 8.0.255-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/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/constants/unified.js +2 -1
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/services/api.js +10 -0
- package/services/tests/api.test.js +34 -0
- package/utils/common.js +5 -0
- package/utils/commonUtils.js +28 -5
- package/utils/tests/commonUtil.test.js +224 -0
- package/utils/transformTemplateConfig.js +0 -10
- package/v2Components/CapDeviceContent/index.js +61 -56
- package/v2Components/CapTagList/index.js +6 -1
- package/v2Components/CapTagListWithInput/index.js +5 -1
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
- package/v2Components/ErrorInfoNote/constants.js +1 -0
- package/v2Components/ErrorInfoNote/index.js +457 -72
- package/v2Components/ErrorInfoNote/messages.js +36 -6
- package/v2Components/ErrorInfoNote/style.scss +282 -6
- package/v2Components/FormBuilder/tests/index.test.js +13 -4
- package/v2Components/HtmlEditor/HTMLEditor.js +547 -94
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +874 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1358 -133
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
- package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
- package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +22 -101
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +149 -140
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +24 -34
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +50 -34
- package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +70 -41
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +364 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
- package/v2Components/HtmlEditor/constants.js +42 -20
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +794 -0
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
- package/v2Components/HtmlEditor/hooks/useValidation.js +189 -53
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +95 -85
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +94 -45
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
- package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +134 -102
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
- package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
- package/v2Components/HtmlEditor/utils/validationConstants.js +40 -0
- package/v2Components/MobilePushPreviewV2/index.js +32 -7
- package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
- package/v2Components/TemplatePreview/index.js +47 -32
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
- package/v2Containers/BeeEditor/index.js +172 -90
- package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
- package/v2Containers/BeePopupEditor/constants.js +10 -0
- package/v2Containers/BeePopupEditor/index.js +194 -0
- package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +128 -51
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +163 -13
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
- package/v2Containers/CreativesContainer/constants.js +1 -0
- package/v2Containers/CreativesContainer/index.js +239 -46
- package/v2Containers/CreativesContainer/messages.js +8 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +106 -0
- package/v2Containers/Email/actions.js +7 -0
- package/v2Containers/Email/constants.js +5 -1
- package/v2Containers/Email/index.js +234 -29
- package/v2Containers/Email/messages.js +32 -0
- package/v2Containers/Email/reducer.js +12 -1
- package/v2Containers/Email/sagas.js +61 -7
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
- package/v2Containers/Email/tests/reducer.test.js +46 -0
- package/v2Containers/Email/tests/sagas.test.js +320 -29
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1285 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +207 -19
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +1870 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
- package/v2Containers/EmailWrapper/constants.js +2 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +629 -77
- package/v2Containers/EmailWrapper/index.js +103 -23
- package/v2Containers/EmailWrapper/messages.js +61 -1
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +643 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +594 -77
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
- package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
- package/v2Containers/InApp/actions.js +7 -0
- package/v2Containers/InApp/constants.js +20 -4
- package/v2Containers/InApp/index.js +802 -359
- package/v2Containers/InApp/index.scss +4 -3
- package/v2Containers/InApp/messages.js +7 -3
- package/v2Containers/InApp/reducer.js +21 -3
- package/v2Containers/InApp/sagas.js +29 -9
- package/v2Containers/InApp/selectors.js +25 -5
- package/v2Containers/InApp/tests/index.test.js +154 -50
- package/v2Containers/InApp/tests/reducer.test.js +34 -0
- package/v2Containers/InApp/tests/sagas.test.js +61 -9
- package/v2Containers/InApp/tests/selectors.test.js +612 -0
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
- package/v2Containers/InAppWrapper/constants.js +16 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
- package/v2Containers/InAppWrapper/index.js +148 -0
- package/v2Containers/InAppWrapper/messages.js +49 -0
- package/v2Containers/InappAdvance/index.js +1099 -0
- package/v2Containers/InappAdvance/index.scss +10 -0
- package/v2Containers/InappAdvance/tests/index.test.js +448 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/TagList/index.js +62 -19
- package/v2Containers/Templates/_templates.scss +60 -1
- package/v2Containers/Templates/index.js +89 -4
- package/v2Containers/Templates/messages.js +4 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationTabs Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
6
|
+
|
|
7
|
+
.validation-tabs {
|
|
8
|
+
overflow-y: hidden;
|
|
9
|
+
width: 100%;
|
|
10
|
+
background-color: $CAP_COLOR_05; // Light pink background
|
|
11
|
+
border-radius: 0.25rem;
|
|
12
|
+
padding: 1rem 1rem;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
|
|
15
|
+
&__header {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: flex-start;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
width: 100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&__tabs {
|
|
23
|
+
flex: 1;
|
|
24
|
+
|
|
25
|
+
// Override CapTab styles for proper spacing
|
|
26
|
+
.cap-tab-v2 {
|
|
27
|
+
.ant-tabs-nav {
|
|
28
|
+
margin-bottom: 0;
|
|
29
|
+
|
|
30
|
+
&::before {
|
|
31
|
+
border-bottom: none;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.ant-tabs-tab {
|
|
36
|
+
padding: 0.5rem 0.75rem; // Add horizontal padding for spacing
|
|
37
|
+
margin-right: 0; // Remove margin, use padding instead
|
|
38
|
+
color: $CAP_G03;
|
|
39
|
+
font-size: 0.875rem;
|
|
40
|
+
font-weight: 500;
|
|
41
|
+
|
|
42
|
+
& + .ant-tabs-tab {
|
|
43
|
+
margin-left: 1.5rem; // Add space between tabs
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&:hover {
|
|
47
|
+
color: $CAP_COLOR_05;
|
|
48
|
+
background: transparent;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&.ant-tabs-tab-active {
|
|
52
|
+
.ant-tabs-tab-btn {
|
|
53
|
+
color: $CAP_COLOR_05;
|
|
54
|
+
font-weight: 600;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.ant-tabs-ink-bar {
|
|
60
|
+
background-color: $CAP_COLOR_05;
|
|
61
|
+
height: 0.125rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.ant-tabs-content-holder {
|
|
65
|
+
padding-top: 0.25rem; // Reduced from 0.5rem
|
|
66
|
+
padding-bottom: 0; // No bottom padding
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&__tab-label {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 0.25rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&__tab-count {
|
|
78
|
+
color: inherit;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&__actions {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: 0.5rem;
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
padding-top: 0.5rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&__close {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
width: 1.5rem;
|
|
94
|
+
height: 1.5rem;
|
|
95
|
+
padding: 0;
|
|
96
|
+
background: transparent;
|
|
97
|
+
border: none;
|
|
98
|
+
border-radius: 0.25rem;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
color: $CAP_G03;
|
|
101
|
+
transition: all 0.2s ease;
|
|
102
|
+
|
|
103
|
+
&:hover {
|
|
104
|
+
background-color: rgba($CAP_COLOR_05, 0.1);
|
|
105
|
+
color: $CAP_COLOR_05;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.cap-icon-v2 {
|
|
109
|
+
font-size: 0.875rem;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
&__content {
|
|
114
|
+
max-height: 15rem; // Limit height for many errors
|
|
115
|
+
overflow-y: auto;
|
|
116
|
+
padding-right: 0.25rem;
|
|
117
|
+
padding-bottom: 0; // Remove bottom padding completely
|
|
118
|
+
|
|
119
|
+
// Custom scrollbar
|
|
120
|
+
&::-webkit-scrollbar {
|
|
121
|
+
width: 0.375rem;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&::-webkit-scrollbar-track {
|
|
125
|
+
background: transparent;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
&::-webkit-scrollbar-thumb {
|
|
129
|
+
background-color: $CAP_G06;
|
|
130
|
+
border-radius: 0.1875rem;
|
|
131
|
+
|
|
132
|
+
&:hover {
|
|
133
|
+
background-color: $CAP_G04;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&__item {
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: flex-start;
|
|
141
|
+
gap: 0.5rem;
|
|
142
|
+
padding: 0.25rem 0; // Reduced from 0.375rem
|
|
143
|
+
border-bottom: 1px solid rgba($CAP_G06, 0.3);
|
|
144
|
+
|
|
145
|
+
&:last-child {
|
|
146
|
+
border-bottom: none;
|
|
147
|
+
padding-bottom: 0.25rem; // Minimal padding on last item
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
&--error {
|
|
151
|
+
.validation-tabs__icon--error {
|
|
152
|
+
color: $CAP_RED;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
&--warning {
|
|
157
|
+
.validation-tabs__icon--warning {
|
|
158
|
+
color: $CAP_YELLOW;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&__item-icon {
|
|
164
|
+
flex-shrink: 0;
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
padding-top: 0.125rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&__icon {
|
|
171
|
+
font-size: 0.875rem;
|
|
172
|
+
|
|
173
|
+
&--error {
|
|
174
|
+
color: $CAP_RED;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
&--warning {
|
|
178
|
+
color: $CAP_YELLOW;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
&__item-content {
|
|
183
|
+
flex: 1;
|
|
184
|
+
display: flex;
|
|
185
|
+
flex-wrap: wrap;
|
|
186
|
+
align-items: baseline;
|
|
187
|
+
gap: 0.25rem;
|
|
188
|
+
font-size: 0.75rem;
|
|
189
|
+
line-height: 2;
|
|
190
|
+
color: $CAP_G01;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
&__item-message {
|
|
194
|
+
color: $CAP_G01;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
&__item-location {
|
|
198
|
+
color: $CAP_G03;
|
|
199
|
+
white-space: nowrap;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
&__item-rule {
|
|
203
|
+
color: $CAP_G04;
|
|
204
|
+
font-family: monospace;
|
|
205
|
+
font-size: 0.6875rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
&__item-navigate {
|
|
209
|
+
flex-shrink: 0;
|
|
210
|
+
display: flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
justify-content: center;
|
|
213
|
+
width: 1.25rem;
|
|
214
|
+
height: 1.25rem;
|
|
215
|
+
padding: 0;
|
|
216
|
+
background: transparent;
|
|
217
|
+
border: none;
|
|
218
|
+
border-radius: 0.25rem;
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
color: $CAP_G04;
|
|
221
|
+
transition: all 0.2s ease;
|
|
222
|
+
|
|
223
|
+
&:hover {
|
|
224
|
+
background-color: rgba($CAP_G01, 0.1);
|
|
225
|
+
color: $CAP_G01;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.anticon {
|
|
229
|
+
font-size: 0.75rem;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Responsive adjustments
|
|
235
|
+
@media (max-width: 768px) {
|
|
236
|
+
.validation-tabs {
|
|
237
|
+
padding: 0.375rem 0.5rem;
|
|
238
|
+
|
|
239
|
+
&__tabs {
|
|
240
|
+
.ant-tabs-tab {
|
|
241
|
+
margin-right: 1rem;
|
|
242
|
+
font-size: 0.8125rem;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
&__content {
|
|
247
|
+
max-height: 10rem;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
&__item-content {
|
|
251
|
+
font-size: 0.6875rem;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationTabs Component
|
|
3
|
+
*
|
|
4
|
+
* Displays validation errors in a tabbed interface with 3 categories:
|
|
5
|
+
* - HTML issues: General HTML/CSS validation errors and warnings
|
|
6
|
+
* - Label issues: Tag syntax errors (open/close tags, attributes, brackets)
|
|
7
|
+
* - Liquid issues: Liquid expression errors (shown when liquid content is detected, even if liquid feature is disabled)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useState, useMemo } from 'react';
|
|
11
|
+
import PropTypes from 'prop-types';
|
|
12
|
+
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
|
13
|
+
|
|
14
|
+
// Cap UI Library
|
|
15
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
16
|
+
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
17
|
+
import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
|
|
18
|
+
|
|
19
|
+
// Messages
|
|
20
|
+
import messages from './messages';
|
|
21
|
+
import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
|
|
22
|
+
import { ISSUE_SOURCES, LABEL_ISSUE_PATTERNS } from '../../utils/validationConstants';
|
|
23
|
+
|
|
24
|
+
// Styles
|
|
25
|
+
import './_validationTabs.scss';
|
|
26
|
+
import {StyledCapTab} from '../../../../v2Containers/MobilePushNew/style';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Categorize issues into HTML, Label, and Liquid categories
|
|
30
|
+
*/
|
|
31
|
+
const categorizeIssues = (allIssues) => {
|
|
32
|
+
const htmlIssues = [];
|
|
33
|
+
const labelIssues = [];
|
|
34
|
+
const liquidIssues = [];
|
|
35
|
+
|
|
36
|
+
allIssues.forEach((issue) => {
|
|
37
|
+
const { source, rule, message } = issue;
|
|
38
|
+
const messageLower = (message || '').toLowerCase();
|
|
39
|
+
const ruleLower = (rule || '').toLowerCase();
|
|
40
|
+
|
|
41
|
+
// Check if it's a Liquid issue - ONLY by source, not by message content
|
|
42
|
+
// This prevents false positives where HTML errors mention liquid syntax
|
|
43
|
+
if (source === ISSUE_SOURCES.LIQUID) {
|
|
44
|
+
liquidIssues.push(issue);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if it's a Label (tag syntax) issue
|
|
49
|
+
const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
|
|
50
|
+
(pattern) => messageLower.includes(pattern.toLowerCase())
|
|
51
|
+
|| ruleLower.includes(pattern.toLowerCase()),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (isLabelIssue) {
|
|
55
|
+
labelIssues.push(issue);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Default to HTML issues
|
|
60
|
+
htmlIssues.push(issue);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return { htmlIssues, labelIssues, liquidIssues };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if an issue is a blocking error (API error, Rule Group #1, or client-side Liquid validation errors)
|
|
68
|
+
*/
|
|
69
|
+
const isBlockingError = (issue) => {
|
|
70
|
+
const { rule, source, severity } = issue || {};
|
|
71
|
+
// API errors are blocking
|
|
72
|
+
if (rule === 'liquid-api-validation' || rule === 'standard-api-validation') {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
// Client-side Liquid validation errors are blocking (genuine syntax errors)
|
|
76
|
+
if (source === 'liquid-validator' && severity === 'error') {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
// Rule Group #1 errors are blocking
|
|
80
|
+
if (BLOCKING_ERROR_RULE_IDS.includes(rule)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get icon based on whether issue is blocking error or warning
|
|
88
|
+
* Blocking errors use error-icon, warnings use alert-warning
|
|
89
|
+
*/
|
|
90
|
+
const getSeverityIcon = (issue) => {
|
|
91
|
+
if (isBlockingError(issue)) {
|
|
92
|
+
return <CapIcon type="error-icon" className="validation-tabs__icon validation-tabs__icon--error" />;
|
|
93
|
+
}
|
|
94
|
+
// All other issues (warnings, non-blocking) use warning icon
|
|
95
|
+
return <CapIcon type="alert-warning" className="validation-tabs__icon validation-tabs__icon--warning" />;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* ValidationTabContent - Renders the content for each tab
|
|
100
|
+
*/
|
|
101
|
+
const ValidationTabContent = ({
|
|
102
|
+
issues,
|
|
103
|
+
onErrorClick,
|
|
104
|
+
}) => {
|
|
105
|
+
if (!issues || issues.length === 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handleNavigateClick = (issue, e) => {
|
|
110
|
+
e.stopPropagation();
|
|
111
|
+
if (onErrorClick) {
|
|
112
|
+
// Always call onErrorClick to acknowledge the error (enables buttons)
|
|
113
|
+
// If line number exists, navigate to it; otherwise just acknowledge and focus editor
|
|
114
|
+
onErrorClick({
|
|
115
|
+
line: issue.line || 1, // Default to line 1 if no line number (for API errors)
|
|
116
|
+
column: issue.column || 1,
|
|
117
|
+
message: issue.message,
|
|
118
|
+
severity: issue.severity,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div className="validation-tabs__content">
|
|
125
|
+
{issues.map((issue, index) => {
|
|
126
|
+
const {
|
|
127
|
+
message, line, column,
|
|
128
|
+
} = issue;
|
|
129
|
+
const key = `${message}-${line}-${column}-${index}`;
|
|
130
|
+
const isBlocking = isBlockingError(issue);
|
|
131
|
+
const displaySeverity = isBlocking ? 'error' : 'warning';
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
key={key}
|
|
136
|
+
className={`validation-tabs__item validation-tabs__item--${displaySeverity}`}
|
|
137
|
+
>
|
|
138
|
+
<div className="validation-tabs__item-icon">
|
|
139
|
+
{getSeverityIcon(issue)}
|
|
140
|
+
</div>
|
|
141
|
+
<div className="validation-tabs__item-content">
|
|
142
|
+
<span className="validation-tabs__item-message">
|
|
143
|
+
{message}
|
|
144
|
+
</span>
|
|
145
|
+
{line && (
|
|
146
|
+
<span className="validation-tabs__item-location">
|
|
147
|
+
<FormattedMessage
|
|
148
|
+
{...messages.lineChar}
|
|
149
|
+
values={{ line, char: column || 1 }}
|
|
150
|
+
/>
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
{/* Always show redirection icon for errors (even API errors without line numbers) */}
|
|
155
|
+
{/* Clicking it acknowledges the error and enables buttons */}
|
|
156
|
+
<CapTooltip title={line ? `Line ${line}, Char ${column || 1}` : 'Click to acknowledge error and focus editor'}>
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
className="validation-tabs__item-navigate"
|
|
160
|
+
onClick={(e) => handleNavigateClick(issue, e)}
|
|
161
|
+
aria-label={line ? `Line ${line}, Char ${column || 1}` : 'Acknowledge error'}
|
|
162
|
+
>
|
|
163
|
+
<CapIcon type="redirection" />
|
|
164
|
+
</button>
|
|
165
|
+
</CapTooltip>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
ValidationTabContent.propTypes = {
|
|
174
|
+
issues: PropTypes.array,
|
|
175
|
+
onErrorClick: PropTypes.func,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
ValidationTabContent.defaultProps = {
|
|
179
|
+
issues: [],
|
|
180
|
+
onErrorClick: null,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* ValidationTabs Component
|
|
185
|
+
*/
|
|
186
|
+
const ValidationTabs = ({
|
|
187
|
+
intl,
|
|
188
|
+
validation,
|
|
189
|
+
onErrorClick,
|
|
190
|
+
onClose,
|
|
191
|
+
className,
|
|
192
|
+
}) => {
|
|
193
|
+
const [activeKey, setActiveKey] = useState(null);
|
|
194
|
+
|
|
195
|
+
// Categorize issues
|
|
196
|
+
const { htmlIssues, labelIssues, liquidIssues } = useMemo(() => {
|
|
197
|
+
if (!validation) {
|
|
198
|
+
return { htmlIssues: [], labelIssues: [], liquidIssues: [] };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Get all issues from validation
|
|
202
|
+
const allIssues = validation.getAllIssues ? validation.getAllIssues() : [];
|
|
203
|
+
const categorized = categorizeIssues(allIssues);
|
|
204
|
+
return categorized;
|
|
205
|
+
}, [validation]);
|
|
206
|
+
|
|
207
|
+
// Calculate counts
|
|
208
|
+
const htmlCount = htmlIssues.length;
|
|
209
|
+
const labelCount = labelIssues.length;
|
|
210
|
+
const liquidCount = liquidIssues.length;
|
|
211
|
+
// Include liquid issues in total count even when liquid is disabled
|
|
212
|
+
// This ensures liquid errors are shown when liquid content is detected but feature is disabled
|
|
213
|
+
const totalCount = htmlCount + labelCount + liquidCount;
|
|
214
|
+
|
|
215
|
+
// Set default active key when issues change
|
|
216
|
+
useMemo(() => {
|
|
217
|
+
if (htmlCount > 0 && !activeKey) {
|
|
218
|
+
setActiveKey('html');
|
|
219
|
+
} else if (labelCount > 0 && !activeKey) {
|
|
220
|
+
setActiveKey('label');
|
|
221
|
+
} else if (liquidCount > 0 && !activeKey) {
|
|
222
|
+
// Show liquid tab even when liquid is disabled if liquid content is detected
|
|
223
|
+
setActiveKey('liquid');
|
|
224
|
+
}
|
|
225
|
+
}, [htmlCount, labelCount, liquidCount, activeKey]);
|
|
226
|
+
|
|
227
|
+
// Don't render if no issues
|
|
228
|
+
if (totalCount === 0) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Build tab panes (CapTab uses 'panes' with 'tab' and 'content' properties)
|
|
233
|
+
const tabPanes = [];
|
|
234
|
+
|
|
235
|
+
if (htmlCount > 0) {
|
|
236
|
+
tabPanes.push({
|
|
237
|
+
key: 'html',
|
|
238
|
+
tab: (
|
|
239
|
+
<CapTooltip title={`${intl.formatMessage(messages.htmlIssues)} (${htmlCount})`}>
|
|
240
|
+
<span className="validation-tabs__tab-label">
|
|
241
|
+
<FormattedMessage {...messages.htmlIssues} />
|
|
242
|
+
<span className="validation-tabs__tab-count">
|
|
243
|
+
(
|
|
244
|
+
{htmlCount}
|
|
245
|
+
)
|
|
246
|
+
</span>
|
|
247
|
+
</span>
|
|
248
|
+
</CapTooltip>
|
|
249
|
+
),
|
|
250
|
+
content: (
|
|
251
|
+
<ValidationTabContent
|
|
252
|
+
issues={htmlIssues}
|
|
253
|
+
onErrorClick={onErrorClick}
|
|
254
|
+
intl={intl}
|
|
255
|
+
/>
|
|
256
|
+
),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (labelCount > 0) {
|
|
261
|
+
tabPanes.push({
|
|
262
|
+
key: 'label',
|
|
263
|
+
tab: (
|
|
264
|
+
<CapTooltip title={`${intl.formatMessage(messages.labelIssues)} (${labelCount})`}>
|
|
265
|
+
<span className="validation-tabs__tab-label">
|
|
266
|
+
<FormattedMessage {...messages.labelIssues} />
|
|
267
|
+
<span className="validation-tabs__tab-count">
|
|
268
|
+
(
|
|
269
|
+
{labelCount}
|
|
270
|
+
)
|
|
271
|
+
</span>
|
|
272
|
+
</span>
|
|
273
|
+
</CapTooltip>
|
|
274
|
+
),
|
|
275
|
+
content: (
|
|
276
|
+
<ValidationTabContent
|
|
277
|
+
issues={labelIssues}
|
|
278
|
+
onErrorClick={onErrorClick}
|
|
279
|
+
intl={intl}
|
|
280
|
+
/>
|
|
281
|
+
),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Show liquid issues tab even when liquid is disabled if liquid content is detected
|
|
286
|
+
// This allows users to see errors when they add liquid content but liquid feature is not enabled
|
|
287
|
+
if (liquidCount > 0) {
|
|
288
|
+
tabPanes.push({
|
|
289
|
+
key: 'liquid',
|
|
290
|
+
tab: (
|
|
291
|
+
<CapTooltip title={`${intl.formatMessage(messages.liquidIssues)} (${liquidCount})`}>
|
|
292
|
+
<span className="validation-tabs__tab-label">
|
|
293
|
+
<FormattedMessage {...messages.liquidIssues} />
|
|
294
|
+
<span className="validation-tabs__tab-count">
|
|
295
|
+
(
|
|
296
|
+
{liquidCount}
|
|
297
|
+
)
|
|
298
|
+
</span>
|
|
299
|
+
</span>
|
|
300
|
+
</CapTooltip>
|
|
301
|
+
),
|
|
302
|
+
content: (
|
|
303
|
+
<ValidationTabContent
|
|
304
|
+
issues={liquidIssues}
|
|
305
|
+
onErrorClick={onErrorClick}
|
|
306
|
+
intl={intl}
|
|
307
|
+
/>
|
|
308
|
+
),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Handle close
|
|
313
|
+
const handleClose = () => {
|
|
314
|
+
if (onClose) {
|
|
315
|
+
onClose();
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
<div className={`validation-tabs ${className || ''}`}>
|
|
321
|
+
<CapRow className="validation-tabs__header">
|
|
322
|
+
<StyledCapTab
|
|
323
|
+
className="validation-tabs__tabs"
|
|
324
|
+
activeKey={activeKey || (tabPanes[0]?.key)}
|
|
325
|
+
onChange={setActiveKey}
|
|
326
|
+
panes={tabPanes}
|
|
327
|
+
/>
|
|
328
|
+
<CapRow className="validation-tabs__actions">
|
|
329
|
+
<CapTooltip title={intl.formatMessage(messages.closePanel)}>
|
|
330
|
+
<button
|
|
331
|
+
type="button"
|
|
332
|
+
className="validation-tabs__close"
|
|
333
|
+
onClick={handleClose}
|
|
334
|
+
aria-label={intl.formatMessage(messages.closePanel)}
|
|
335
|
+
>
|
|
336
|
+
<CapIcon type="close" />
|
|
337
|
+
</button>
|
|
338
|
+
</CapTooltip>
|
|
339
|
+
</CapRow>
|
|
340
|
+
</CapRow>
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
ValidationTabs.propTypes = {
|
|
346
|
+
intl: intlShape.isRequired,
|
|
347
|
+
validation: PropTypes.shape({
|
|
348
|
+
getAllIssues: PropTypes.func,
|
|
349
|
+
}),
|
|
350
|
+
onErrorClick: PropTypes.func,
|
|
351
|
+
onClose: PropTypes.func,
|
|
352
|
+
isLiquidEnabled: PropTypes.bool,
|
|
353
|
+
className: PropTypes.string,
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
ValidationTabs.defaultProps = {
|
|
357
|
+
validation: null,
|
|
358
|
+
onErrorClick: null,
|
|
359
|
+
onClose: null,
|
|
360
|
+
isLiquidEnabled: false,
|
|
361
|
+
className: '',
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
export default injectIntl(ValidationTabs);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationTabs Messages
|
|
3
|
+
*
|
|
4
|
+
* Internationalization messages for the ValidationTabs component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineMessages } from 'react-intl';
|
|
8
|
+
|
|
9
|
+
const scope = 'app.components.HtmlEditor.ValidationTabs';
|
|
10
|
+
|
|
11
|
+
export default defineMessages({
|
|
12
|
+
// Tab labels
|
|
13
|
+
htmlIssues: {
|
|
14
|
+
id: `${scope}.htmlIssues`,
|
|
15
|
+
defaultMessage: 'HTML issues',
|
|
16
|
+
},
|
|
17
|
+
labelIssues: {
|
|
18
|
+
id: `${scope}.labelIssues`,
|
|
19
|
+
defaultMessage: 'Label issues',
|
|
20
|
+
},
|
|
21
|
+
liquidIssues: {
|
|
22
|
+
id: `${scope}.liquidIssues`,
|
|
23
|
+
defaultMessage: 'Liquid issues',
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// Error item labels
|
|
27
|
+
syntaxError: {
|
|
28
|
+
id: `${scope}.syntaxError`,
|
|
29
|
+
defaultMessage: 'Syntax Error',
|
|
30
|
+
},
|
|
31
|
+
lineChar: {
|
|
32
|
+
id: `${scope}.lineChar`,
|
|
33
|
+
defaultMessage: 'Line {line}, Char {char}.',
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// Tooltips
|
|
37
|
+
navigateToError: {
|
|
38
|
+
id: `${scope}.navigateToError`,
|
|
39
|
+
defaultMessage: 'Go to error location',
|
|
40
|
+
},
|
|
41
|
+
closePanel: {
|
|
42
|
+
id: `${scope}.closePanel`,
|
|
43
|
+
defaultMessage: 'Close validation panel',
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Liquid documentation
|
|
47
|
+
liquidDocumentation: {
|
|
48
|
+
id: `${scope}.liquidDocumentation`,
|
|
49
|
+
defaultMessage: 'Liquid documentation',
|
|
50
|
+
},
|
|
51
|
+
});
|