@capillarytech/creatives-library 8.0.207 → 8.0.209
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/package.json +16 -2
- package/v2Components/HtmlEditor/HTMLEditor.js +508 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1809 -0
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +532 -0
- package/v2Components/HtmlEditor/_htmlEditor.scss +304 -0
- package/v2Components/HtmlEditor/_index.lazy.scss +26 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +376 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +331 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/__tests__/index.test.js +314 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +244 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +111 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/PreviewModeGroup.js +72 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/__tests__/PreviewModeGroup.test.js +1594 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +113 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/_previewModeGroup.scss +82 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +115 -0
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +57 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/ContentOverlay.js +90 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +60 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/LayoutSelector.js +58 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/ContentOverlay.test.js +389 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +424 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/LayoutSelector.test.js +248 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +253 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +104 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +179 -0
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +220 -0
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +229 -0
- package/v2Components/HtmlEditor/components/SplitContainer/SplitContainer.js +276 -0
- package/v2Components/HtmlEditor/components/SplitContainer/__tests__/SplitContainer.test.js +295 -0
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +257 -0
- package/v2Components/HtmlEditor/components/SplitContainer/index.js +7 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +31 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +70 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/__tests__/index.test.js +98 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +311 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +297 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/messages.js +57 -0
- package/v2Components/HtmlEditor/components/common/EditorContext.js +84 -0
- package/v2Components/HtmlEditor/components/common/__tests__/EditorContext.test.js +660 -0
- package/v2Components/HtmlEditor/constants.js +241 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useEditorContent.test.js +450 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +785 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useLayoutState.test.js +580 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.enhanced.test.js +768 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +590 -0
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +274 -0
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +407 -0
- package/v2Components/HtmlEditor/hooks/useLayoutState.js +247 -0
- package/v2Components/HtmlEditor/hooks/useValidation.js +325 -0
- package/v2Components/HtmlEditor/index.js +29 -0
- package/v2Components/HtmlEditor/index.lazy.js +114 -0
- package/v2Components/HtmlEditor/messages.js +389 -0
- package/v2Components/HtmlEditor/utils/__tests__/contentSanitizer.test.js +741 -0
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +1042 -0
- package/v2Components/HtmlEditor/utils/__tests__/liquidTemplateSupport.test.js +515 -0
- package/v2Components/HtmlEditor/utils/__tests__/properSyntaxHighlighting.test.js +473 -0
- package/v2Components/HtmlEditor/utils/__tests__/simplePerformance.test.js +1109 -0
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +240 -0
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +433 -0
- package/v2Components/HtmlEditor/utils/htmlValidator.js +508 -0
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +524 -0
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +163 -0
- package/v2Components/HtmlEditor/utils/simplePerformance.js +145 -0
- package/v2Components/HtmlEditor/utils/validationAdapter.js +130 -0
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +200 -0
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +545 -0
- package/v2Containers/EmailWrapper/index.js +8 -1
- package/v2Containers/Templates/constants.js +8 -0
- package/v2Containers/Templates/index.js +56 -28
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +5 -14
- package/v2Containers/Whatsapp/constants.js +26 -2
- package/v2Containers/Whatsapp/index.js +4 -1
- package/v2Containers/Whatsapp/tests/index.test.js +460 -18
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationPanel Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
6
|
+
|
|
7
|
+
.validation-panel {
|
|
8
|
+
border: 1px solid #d9d9d9;
|
|
9
|
+
border-radius: 6px;
|
|
10
|
+
background: #fff;
|
|
11
|
+
|
|
12
|
+
&--loading {
|
|
13
|
+
padding: 16px;
|
|
14
|
+
text-align: center;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&--clean {
|
|
18
|
+
padding: 16px;
|
|
19
|
+
text-align: center;
|
|
20
|
+
background: #f6ffed;
|
|
21
|
+
border-color: #b7eb8f;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&__loading {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
color: #666;
|
|
30
|
+
font-size: 14px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&__clean {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
gap: 8px;
|
|
38
|
+
color: #52c41a;
|
|
39
|
+
font-size: 14px;
|
|
40
|
+
font-weight: 500;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&__summary {
|
|
44
|
+
display: flex;
|
|
45
|
+
gap: 16px;
|
|
46
|
+
padding: 12px 16px;
|
|
47
|
+
background: #fafafa;
|
|
48
|
+
border-bottom: 1px solid #d9d9d9;
|
|
49
|
+
border-radius: 6px 6px 0 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&__summary-item {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 4px;
|
|
56
|
+
font-size: 12px;
|
|
57
|
+
color: #666;
|
|
58
|
+
|
|
59
|
+
&--security {
|
|
60
|
+
color: #ff4d4f;
|
|
61
|
+
font-weight: 500;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
span:first-of-type {
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
margin-left: 2px;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&__collapse {
|
|
71
|
+
.ant-collapse-item {
|
|
72
|
+
border: none;
|
|
73
|
+
|
|
74
|
+
&:last-child {
|
|
75
|
+
border-radius: 0 0 6px 6px;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ant-collapse-header {
|
|
80
|
+
padding: 12px 16px !important;
|
|
81
|
+
align-items: center !important;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ant-collapse-content {
|
|
85
|
+
border-top: 1px solid #f0f0f0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.ant-collapse-content-box {
|
|
89
|
+
padding: 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&__panel {
|
|
94
|
+
&--error {
|
|
95
|
+
.ant-collapse-header {
|
|
96
|
+
background: #fff2f0;
|
|
97
|
+
border-left: 3px solid #ff4d4f;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
&--warning {
|
|
102
|
+
.ant-collapse-header {
|
|
103
|
+
background: #fffbe6;
|
|
104
|
+
border-left: 3px solid #faad14;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
&--info {
|
|
109
|
+
.ant-collapse-header {
|
|
110
|
+
background: #e6f7ff;
|
|
111
|
+
border-left: 3px solid #1890ff;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&__header {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: space-between;
|
|
120
|
+
width: 100%;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&__title {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
gap: 8px;
|
|
127
|
+
font-weight: 500;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&__issues {
|
|
131
|
+
max-height: 300px;
|
|
132
|
+
overflow-y: auto;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&__sanitization {
|
|
136
|
+
border-top: 1px solid #d9d9d9;
|
|
137
|
+
background: #f9f9f9;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&__sanitization-header {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 8px;
|
|
144
|
+
padding: 12px 16px;
|
|
145
|
+
font-weight: 500;
|
|
146
|
+
color: #666;
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
&__sanitization-list {
|
|
151
|
+
padding: 0 16px 12px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
&__sanitization-item {
|
|
155
|
+
padding: 4px 0;
|
|
156
|
+
font-size: 12px;
|
|
157
|
+
color: #8c8c8c;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.validation-issue {
|
|
162
|
+
display: flex;
|
|
163
|
+
gap: 12px;
|
|
164
|
+
padding: 12px 16px;
|
|
165
|
+
border-bottom: 1px solid #f0f0f0;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
transition: background-color 0.2s;
|
|
168
|
+
|
|
169
|
+
&:hover {
|
|
170
|
+
background: #fafafa;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
&:last-child {
|
|
174
|
+
border-bottom: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
&--error {
|
|
178
|
+
border-left: 2px solid #ff4d4f;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
&--warning {
|
|
182
|
+
border-left: 2px solid #faad14;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
&--info {
|
|
186
|
+
border-left: 2px solid #1890ff;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
&__icon {
|
|
190
|
+
flex-shrink: 0;
|
|
191
|
+
margin-top: 2px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
&__content {
|
|
195
|
+
flex: 1;
|
|
196
|
+
min-width: 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
&__message {
|
|
200
|
+
font-size: 13px;
|
|
201
|
+
line-height: 1.4;
|
|
202
|
+
color: #262626;
|
|
203
|
+
margin-bottom: 4px;
|
|
204
|
+
word-break: break-word;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
&__meta {
|
|
208
|
+
display: flex;
|
|
209
|
+
flex-wrap: wrap;
|
|
210
|
+
gap: 8px;
|
|
211
|
+
font-size: 11px;
|
|
212
|
+
color: #8c8c8c;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
&__location {
|
|
216
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
217
|
+
background: #f5f5f5;
|
|
218
|
+
padding: 2px 6px;
|
|
219
|
+
border-radius: 3px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
&__rule {
|
|
223
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
224
|
+
background: #e6f7ff;
|
|
225
|
+
padding: 2px 6px;
|
|
226
|
+
border-radius: 3px;
|
|
227
|
+
color: #1890ff;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
&__source {
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: 4px;
|
|
234
|
+
background: #f0f0f0;
|
|
235
|
+
padding: 2px 6px;
|
|
236
|
+
border-radius: 3px;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Dark theme support for editor integration
|
|
241
|
+
.html-editor-content .validation-panel {
|
|
242
|
+
border-color: #424242;
|
|
243
|
+
background: #2d2d2d;
|
|
244
|
+
color: #e0e0e0;
|
|
245
|
+
|
|
246
|
+
&__summary {
|
|
247
|
+
background: #3d3d3d;
|
|
248
|
+
border-color: #424242;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.validation-issue {
|
|
252
|
+
border-color: #424242;
|
|
253
|
+
color: #e0e0e0;
|
|
254
|
+
|
|
255
|
+
&:hover {
|
|
256
|
+
background: #3d3d3d;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
&__message {
|
|
260
|
+
color: #e0e0e0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
&__location {
|
|
264
|
+
background: #424242;
|
|
265
|
+
color: #e0e0e0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
&__rule {
|
|
269
|
+
background: #1f1f1f;
|
|
270
|
+
color: #69c0ff;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
&__source {
|
|
274
|
+
background: #424242;
|
|
275
|
+
color: #e0e0e0;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.ant-collapse-header {
|
|
280
|
+
background: #3d3d3d !important;
|
|
281
|
+
color: #e0e0e0 !important;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.ant-collapse-content {
|
|
285
|
+
background: #2d2d2d;
|
|
286
|
+
border-color: #424242;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Responsive design
|
|
291
|
+
@media (max-width: 768px) {
|
|
292
|
+
.validation-panel {
|
|
293
|
+
&__summary {
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
gap: 8px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
&__issues {
|
|
299
|
+
max-height: 200px;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.validation-issue {
|
|
304
|
+
padding: 8px 12px;
|
|
305
|
+
|
|
306
|
+
&__meta {
|
|
307
|
+
flex-direction: column;
|
|
308
|
+
gap: 4px;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationPanel Component
|
|
3
|
+
* Displays validation errors, warnings, and security issues
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState, useMemo } from 'react';
|
|
7
|
+
import PropTypes from 'prop-types';
|
|
8
|
+
import { FormattedMessage } from 'react-intl';
|
|
9
|
+
|
|
10
|
+
// Import components individually to avoid undefined imports
|
|
11
|
+
import Badge from 'antd/lib/badge';
|
|
12
|
+
import Collapse from 'antd/lib/collapse';
|
|
13
|
+
import Empty from 'antd/lib/empty';
|
|
14
|
+
import Spin from 'antd/lib/spin';
|
|
15
|
+
|
|
16
|
+
// Import icons individually
|
|
17
|
+
import ExclamationCircleOutlined from '@ant-design/icons/ExclamationCircleOutlined';
|
|
18
|
+
import WarningOutlined from '@ant-design/icons/WarningOutlined';
|
|
19
|
+
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined';
|
|
20
|
+
import ShieldOutlined from '@ant-design/icons/ShieldOutlined';
|
|
21
|
+
import BugOutlined from '@ant-design/icons/BugOutlined';
|
|
22
|
+
import CodeOutlined from '@ant-design/icons/CodeOutlined';
|
|
23
|
+
import EyeInvisibleOutlined from '@ant-design/icons/EyeInvisibleOutlined';
|
|
24
|
+
import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
|
|
25
|
+
|
|
26
|
+
import messages from './messages';
|
|
27
|
+
import './_validationPanel.scss';
|
|
28
|
+
|
|
29
|
+
const { Panel } = Collapse;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* ValidationPanel Component
|
|
33
|
+
*/
|
|
34
|
+
const ValidationPanel = ({
|
|
35
|
+
validation,
|
|
36
|
+
isVisible = true,
|
|
37
|
+
onErrorClick,
|
|
38
|
+
showLineNumbers = true,
|
|
39
|
+
groupBySource = false,
|
|
40
|
+
variant = 'email'
|
|
41
|
+
}) => {
|
|
42
|
+
const [activeKeys, setActiveKeys] = useState(['errors', 'warnings']);
|
|
43
|
+
|
|
44
|
+
// Group issues by type or source
|
|
45
|
+
const groupedIssues = useMemo(() => {
|
|
46
|
+
if (!validation) return {};
|
|
47
|
+
|
|
48
|
+
const allIssues = validation.getAllIssues();
|
|
49
|
+
|
|
50
|
+
if (groupBySource) {
|
|
51
|
+
return allIssues.reduce((groups, issue) => {
|
|
52
|
+
const source = issue.source || 'unknown';
|
|
53
|
+
if (!groups[source]) {
|
|
54
|
+
groups[source] = [];
|
|
55
|
+
}
|
|
56
|
+
groups[source].push(issue);
|
|
57
|
+
return groups;
|
|
58
|
+
}, {});
|
|
59
|
+
} else {
|
|
60
|
+
return {
|
|
61
|
+
errors: allIssues.filter(issue => issue.severity === 'error'),
|
|
62
|
+
warnings: allIssues.filter(issue => issue.severity === 'warning'),
|
|
63
|
+
info: allIssues.filter(issue => issue.severity === 'info')
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}, [validation, groupBySource]);
|
|
67
|
+
|
|
68
|
+
// Get icon for issue type
|
|
69
|
+
const getIssueIcon = (severity, source) => {
|
|
70
|
+
if (source === 'security') {
|
|
71
|
+
return <ShieldOutlined style={{ color: '#ff4d4f' }} />;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
switch (severity) {
|
|
75
|
+
case 'error':
|
|
76
|
+
return <ExclamationCircleOutlined style={{ color: '#ff4d4f' }} />;
|
|
77
|
+
case 'warning':
|
|
78
|
+
return <WarningOutlined style={{ color: '#faad14' }} />;
|
|
79
|
+
case 'info':
|
|
80
|
+
return <InfoCircleOutlined style={{ color: '#1890ff' }} />;
|
|
81
|
+
default:
|
|
82
|
+
return <BugOutlined style={{ color: '#666' }} />;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Get source icon
|
|
87
|
+
const getSourceIcon = (source) => {
|
|
88
|
+
switch (source) {
|
|
89
|
+
case 'htmlhint':
|
|
90
|
+
return <CodeOutlined />;
|
|
91
|
+
case 'css-validator':
|
|
92
|
+
return <CodeOutlined />;
|
|
93
|
+
case 'security':
|
|
94
|
+
return <ShieldOutlined />;
|
|
95
|
+
case 'custom':
|
|
96
|
+
return <BugOutlined />;
|
|
97
|
+
default:
|
|
98
|
+
return <InfoCircleOutlined />;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Handle issue click
|
|
103
|
+
const handleIssueClick = (issue) => {
|
|
104
|
+
if (onErrorClick && issue.line) {
|
|
105
|
+
onErrorClick({
|
|
106
|
+
line: issue.line,
|
|
107
|
+
column: issue.column || 1,
|
|
108
|
+
message: issue.message,
|
|
109
|
+
severity: issue.severity
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Render issue item
|
|
115
|
+
const renderIssue = (issue, index) => (
|
|
116
|
+
<div
|
|
117
|
+
key={index}
|
|
118
|
+
className={`validation-issue validation-issue--${issue.severity}`}
|
|
119
|
+
onClick={() => handleIssueClick(issue)}
|
|
120
|
+
role="button"
|
|
121
|
+
tabIndex={0}
|
|
122
|
+
onKeyPress={(e) => {
|
|
123
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
124
|
+
handleIssueClick(issue);
|
|
125
|
+
}
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<div className="validation-issue__icon">
|
|
129
|
+
{getIssueIcon(issue.severity, issue.source)}
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<div className="validation-issue__content">
|
|
133
|
+
<div className="validation-issue__message">
|
|
134
|
+
{issue.message}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div className="validation-issue__meta">
|
|
138
|
+
{showLineNumbers && issue.line && (
|
|
139
|
+
<span className="validation-issue__location">
|
|
140
|
+
<FormattedMessage {...messages.lineColumn} values={{
|
|
141
|
+
line: issue.line,
|
|
142
|
+
column: issue.column || 1
|
|
143
|
+
}} />
|
|
144
|
+
</span>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{issue.rule && (
|
|
148
|
+
<span className="validation-issue__rule">
|
|
149
|
+
{issue.rule}
|
|
150
|
+
</span>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{issue.source && (
|
|
154
|
+
<span className="validation-issue__source">
|
|
155
|
+
{getSourceIcon(issue.source)}
|
|
156
|
+
{issue.source}
|
|
157
|
+
</span>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Render panel header with count
|
|
165
|
+
const renderPanelHeader = (title, count, severity) => (
|
|
166
|
+
<div className="validation-panel__header">
|
|
167
|
+
<span className="validation-panel__title">
|
|
168
|
+
{getIssueIcon(severity)}
|
|
169
|
+
<FormattedMessage {...title} />
|
|
170
|
+
</span>
|
|
171
|
+
{count > 0 && (
|
|
172
|
+
<Badge count={count} style={{ backgroundColor: getSeverityColor(severity) }} />
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Get severity color
|
|
178
|
+
const getSeverityColor = (severity) => {
|
|
179
|
+
switch (severity) {
|
|
180
|
+
case 'error': return '#ff4d4f';
|
|
181
|
+
case 'warning': return '#faad14';
|
|
182
|
+
case 'info': return '#1890ff';
|
|
183
|
+
default: return '#666';
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
if (!isVisible) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Show loading state
|
|
192
|
+
if (validation?.isValidating) {
|
|
193
|
+
return (
|
|
194
|
+
<div className="validation-panel validation-panel--loading">
|
|
195
|
+
<div className="validation-panel__loading">
|
|
196
|
+
<Spin size="small" />
|
|
197
|
+
<span><FormattedMessage {...messages.validating} /></span>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Skip clean state - don't show "No validation issues found" message
|
|
204
|
+
|
|
205
|
+
// Render validation issues
|
|
206
|
+
return (
|
|
207
|
+
<div className="validation-panel">
|
|
208
|
+
<div className="validation-panel__summary">
|
|
209
|
+
<div className="validation-panel__summary-item">
|
|
210
|
+
<ExclamationCircleOutlined style={{ color: '#ff4d4f' }} />
|
|
211
|
+
<span>{validation?.summary?.totalErrors || 0}</span>
|
|
212
|
+
<FormattedMessage {...messages.errors} />
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div className="validation-panel__summary-item">
|
|
216
|
+
<WarningOutlined style={{ color: '#faad14' }} />
|
|
217
|
+
<span>{validation?.summary?.totalWarnings || 0}</span>
|
|
218
|
+
<FormattedMessage {...messages.warnings} />
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{validation?.summary?.hasSecurityIssues && (
|
|
222
|
+
<div className="validation-panel__summary-item validation-panel__summary-item--security">
|
|
223
|
+
<ShieldOutlined style={{ color: '#ff4d4f' }} />
|
|
224
|
+
<FormattedMessage {...messages.securityIssues} />
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<Collapse
|
|
230
|
+
activeKey={activeKeys}
|
|
231
|
+
onChange={setActiveKeys}
|
|
232
|
+
className="validation-panel__collapse"
|
|
233
|
+
ghost
|
|
234
|
+
>
|
|
235
|
+
{Object.entries(groupedIssues).map(([key, issues]) => {
|
|
236
|
+
if (!issues || issues.length === 0) return null;
|
|
237
|
+
|
|
238
|
+
const isSourceGroup = groupBySource;
|
|
239
|
+
const title = isSourceGroup
|
|
240
|
+
? { id: `htmlEditor.validation.source.${key}`, defaultMessage: key }
|
|
241
|
+
: messages[key] || { id: `htmlEditor.validation.${key}`, defaultMessage: key };
|
|
242
|
+
|
|
243
|
+
const severity = isSourceGroup
|
|
244
|
+
? (issues.find(i => i.severity === 'error') ? 'error' :
|
|
245
|
+
issues.find(i => i.severity === 'warning') ? 'warning' : 'info')
|
|
246
|
+
: key;
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<Panel
|
|
250
|
+
key={key}
|
|
251
|
+
header={renderPanelHeader(title, issues.length, severity)}
|
|
252
|
+
className={`validation-panel__panel validation-panel__panel--${severity}`}
|
|
253
|
+
>
|
|
254
|
+
<div className="validation-panel__issues">
|
|
255
|
+
{issues.length === 0 ? (
|
|
256
|
+
<Empty
|
|
257
|
+
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
258
|
+
description={<FormattedMessage {...messages.noIssuesOfType} />}
|
|
259
|
+
/>
|
|
260
|
+
) : (
|
|
261
|
+
issues.map((issue, index) => renderIssue(issue, index))
|
|
262
|
+
)}
|
|
263
|
+
</div>
|
|
264
|
+
</Panel>
|
|
265
|
+
);
|
|
266
|
+
})}
|
|
267
|
+
</Collapse>
|
|
268
|
+
|
|
269
|
+
{validation?.sanitizationWarnings?.length > 0 && (
|
|
270
|
+
<div className="validation-panel__sanitization">
|
|
271
|
+
<div className="validation-panel__sanitization-header">
|
|
272
|
+
<EyeInvisibleOutlined />
|
|
273
|
+
<FormattedMessage {...messages.sanitizationWarnings} />
|
|
274
|
+
</div>
|
|
275
|
+
<div className="validation-panel__sanitization-list">
|
|
276
|
+
{validation.sanitizationWarnings.map((warning, index) => (
|
|
277
|
+
<div key={index} className="validation-panel__sanitization-item">
|
|
278
|
+
{warning.message}
|
|
279
|
+
</div>
|
|
280
|
+
))}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
ValidationPanel.propTypes = {
|
|
289
|
+
validation: PropTypes.object,
|
|
290
|
+
isVisible: PropTypes.bool,
|
|
291
|
+
onErrorClick: PropTypes.func,
|
|
292
|
+
showLineNumbers: PropTypes.bool,
|
|
293
|
+
groupBySource: PropTypes.bool,
|
|
294
|
+
variant: PropTypes.oneOf(['email', 'inapp'])
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
export default ValidationPanel;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationPanel Messages
|
|
3
|
+
* Internationalization messages for the validation panel
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { defineMessages } from 'react-intl';
|
|
7
|
+
|
|
8
|
+
export default defineMessages({
|
|
9
|
+
errors: {
|
|
10
|
+
id: 'htmlEditor.validation.errors',
|
|
11
|
+
defaultMessage: 'Errors'
|
|
12
|
+
},
|
|
13
|
+
warnings: {
|
|
14
|
+
id: 'htmlEditor.validation.warnings',
|
|
15
|
+
defaultMessage: 'Warnings'
|
|
16
|
+
},
|
|
17
|
+
info: {
|
|
18
|
+
id: 'htmlEditor.validation.info',
|
|
19
|
+
defaultMessage: 'Info'
|
|
20
|
+
},
|
|
21
|
+
securityIssues: {
|
|
22
|
+
id: 'htmlEditor.validation.securityIssues',
|
|
23
|
+
defaultMessage: 'Security Issues'
|
|
24
|
+
},
|
|
25
|
+
noIssues: {
|
|
26
|
+
id: 'htmlEditor.validation.noIssues',
|
|
27
|
+
defaultMessage: 'No validation issues found'
|
|
28
|
+
},
|
|
29
|
+
noIssuesOfType: {
|
|
30
|
+
id: 'htmlEditor.validation.noIssuesOfType',
|
|
31
|
+
defaultMessage: 'No issues of this type'
|
|
32
|
+
},
|
|
33
|
+
validating: {
|
|
34
|
+
id: 'htmlEditor.validation.validating',
|
|
35
|
+
defaultMessage: 'Validating...'
|
|
36
|
+
},
|
|
37
|
+
lineColumn: {
|
|
38
|
+
id: 'htmlEditor.validation.lineColumn',
|
|
39
|
+
defaultMessage: 'Line {line}, Column {column}'
|
|
40
|
+
},
|
|
41
|
+
sanitizationWarnings: {
|
|
42
|
+
id: 'htmlEditor.validation.sanitizationWarnings',
|
|
43
|
+
defaultMessage: 'Content Sanitization Warnings'
|
|
44
|
+
},
|
|
45
|
+
htmlValidation: {
|
|
46
|
+
id: 'htmlEditor.validation.htmlValidation',
|
|
47
|
+
defaultMessage: 'HTML Validation'
|
|
48
|
+
},
|
|
49
|
+
cssValidation: {
|
|
50
|
+
id: 'htmlEditor.validation.cssValidation',
|
|
51
|
+
defaultMessage: 'CSS Validation'
|
|
52
|
+
},
|
|
53
|
+
securityValidation: {
|
|
54
|
+
id: 'htmlEditor.validation.securityValidation',
|
|
55
|
+
defaultMessage: 'Security Validation'
|
|
56
|
+
}
|
|
57
|
+
});
|