@capillarytech/creatives-library 8.0.262-alpha.0 → 8.0.262

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.
Files changed (24) hide show
  1. package/package.json +1 -1
  2. package/v2Components/ErrorInfoNote/index.js +57 -112
  3. package/v2Components/ErrorInfoNote/messages.js +8 -12
  4. package/v2Components/ErrorInfoNote/style.scss +0 -4
  5. package/v2Components/HtmlEditor/HTMLEditor.js +46 -182
  6. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +11 -15
  7. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +7 -8
  8. package/v2Components/HtmlEditor/_htmlEditor.scss +6 -6
  9. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +1 -1
  10. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +4 -4
  11. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +17 -0
  12. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +15 -28
  13. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +0 -4
  14. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +7 -31
  15. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +53 -26
  16. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +74 -144
  17. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +14 -14
  18. package/v2Components/HtmlEditor/hooks/useValidation.js +23 -3
  19. package/v2Components/HtmlEditor/utils/validationConstants.js +3 -4
  20. package/v2Containers/CreativesContainer/index.js +1 -3
  21. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +6 -9
  22. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +5 -49
  23. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +23 -38
  24. package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.262-alpha.0",
4
+ "version": "8.0.262",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -1,4 +1,4 @@
1
- import React, { useState, useMemo } from 'react';
1
+ import React, { useState, useMemo, useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
4
4
  import CapButton from '@capillarytech/cap-ui-library/CapButton';
@@ -15,57 +15,45 @@ import messages from './messages';
15
15
  import { processErrors } from './utils';
16
16
  import ErrorTypeRenderer from './ErrorTypeRenderer';
17
17
  import { ANDROID, GENERIC, IOS } from '../../v2Containers/CreativesContainer/constants';
18
- import { LABEL_ISSUE_PATTERNS, ERROR_TAB_KEYS } from '../HtmlEditor/utils/validationConstants';
18
+ import { ERROR_TAB_KEYS } from '../HtmlEditor/utils/validationConstants';
19
19
  import { SEVERITY } from '../HtmlEditor/components/ValidationPanel/constants';
20
20
  import { LIQUID_DOC_URL } from './constants';
21
21
 
22
22
  const { CapLabelInline } = CapLabel;
23
23
 
24
24
  /**
25
- * Categorize error messages into HTML, Label, and Liquid categories
25
+ * Group messages into Errors (blocking) and Warnings (non-blocking).
26
+ * STANDARD_ERROR_MSG + LIQUID_ERROR_MSG = errors; optional STANDARD_WARNING_MSG = warnings.
26
27
  */
27
- const categorizeErrorMessages = (standardErrors, liquidErrors) => {
28
- const htmlIssues = [];
29
- const labelIssues = [];
30
- const liquidIssues = [];
31
-
32
- // Process standard errors
33
- (standardErrors || []).forEach((error, index) => {
34
- const errorLower = (error || '').toLowerCase();
35
-
36
- // Check if it's a Label (tag syntax) issue
37
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
38
- (pattern) => errorLower.includes(pattern.toLowerCase()),
39
- );
28
+ const groupByErrorsAndWarnings = (standardErrors, liquidErrors, standardWarnings = []) => {
29
+ const errors = [];
40
30
 
41
- if (isLabelIssue) {
42
- labelIssues.push({
43
- message: error,
44
- severity: SEVERITY.ERROR,
45
- index,
46
- source: 'label',
47
- });
48
- } else {
49
- htmlIssues.push({
50
- message: error,
51
- severity: SEVERITY.ERROR,
52
- index,
53
- source: 'html',
54
- });
55
- }
31
+ (standardErrors || []).forEach((message, index) => {
32
+ errors.push({
33
+ message,
34
+ severity: SEVERITY.ERROR,
35
+ index,
36
+ source: 'standard',
37
+ });
56
38
  });
57
39
 
58
- // Process liquid errors
59
- (liquidErrors || []).forEach((error, index) => {
60
- liquidIssues.push({
61
- message: error,
40
+ (liquidErrors || []).forEach((message, index) => {
41
+ errors.push({
42
+ message,
62
43
  severity: SEVERITY.ERROR,
63
44
  index,
64
45
  source: 'liquid',
65
46
  });
66
47
  });
67
48
 
68
- return { htmlIssues, labelIssues, liquidIssues };
49
+ const warnings = (standardWarnings || []).map((message, index) => ({
50
+ message,
51
+ severity: SEVERITY.WARNING,
52
+ index,
53
+ source: 'standard',
54
+ }));
55
+
56
+ return { errors, warnings };
69
57
  };
70
58
 
71
59
  /**
@@ -90,16 +78,6 @@ const cleanErrorMessage = (message, lineMatch, charMatch, ruleMatch) => {
90
78
  return displayMessage.trim();
91
79
  };
92
80
 
93
- /**
94
- * Get icon based on severity
95
- */
96
- const getSeverityIcon = (severity) => {
97
- if (severity === SEVERITY.WARNING) {
98
- return <CapIcon type="alert-warning" className="error-info-note__icon error-info-note__icon--warning" />;
99
- }
100
- return <CapIcon type="warning-circle" className="error-info-note__icon error-info-note__icon--error" />;
101
- };
102
-
103
81
  /**
104
82
  * Tab content component
105
83
  */
@@ -139,9 +117,6 @@ const TabContent = ({ issues, onErrorClick, intl }) => {
139
117
  key={key}
140
118
  className={`error-info-note__item error-info-note__item--${severity || 'error'}`}
141
119
  >
142
- <CapRow className="error-info-note__item-icon">
143
- {getSeverityIcon(severity)}
144
- </CapRow>
145
120
  <CapRow className="error-info-note__item-content">
146
121
  <CapLabelInline type="label2" className="error-info-note__item-message">
147
122
  {displayMessage}
@@ -222,6 +197,7 @@ export const ErrorInfoNote = (props) => {
222
197
  const {
223
198
  LIQUID_ERROR_MSG: rawLiquidErrors = [],
224
199
  STANDARD_ERROR_MSG: rawStandardErrors = [],
200
+ STANDARD_WARNING_MSG: rawStandardWarnings = [],
225
201
  } = errorMessages || {};
226
202
 
227
203
  // Detect if platform-specific (ANDROID/IOS) or GENERIC
@@ -280,33 +256,26 @@ export const ErrorInfoNote = (props) => {
280
256
  );
281
257
  }
282
258
 
283
- // Categorize errors for tabbed interface
284
- const { htmlIssues, labelIssues, liquidIssues } = useMemo(() => categorizeErrorMessages(
259
+ // Group into Errors (blocking) and Warnings (non-blocking)
260
+ const { errors: errorIssues, warnings: warningIssues } = useMemo(() => groupByErrorsAndWarnings(
285
261
  Array.isArray(rawStandardErrors) ? rawStandardErrors : [],
286
262
  Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [],
287
- ), [rawStandardErrors, rawLiquidErrors]);
288
-
289
- // Calculate counts
290
- const htmlCount = htmlIssues.length;
291
- const labelCount = labelIssues.length;
292
- const liquidCount = liquidIssues.length;
293
- // Include liquid issues in total count even when liquid is disabled
294
- // This ensures liquid errors are shown when liquid content is detected but feature is disabled
295
- const totalCount = htmlCount + labelCount + liquidCount;
296
-
297
- // Set default active key
298
- useMemo(() => {
299
- if (!activeKey) {
300
- if (htmlCount > 0) {
301
- setActiveKey(ERROR_TAB_KEYS.HTML);
302
- } else if (labelCount > 0) {
303
- setActiveKey(ERROR_TAB_KEYS.LABEL);
304
- } else if (liquidCount > 0) {
305
- // Show liquid tab even when liquid is disabled if liquid content is detected
306
- setActiveKey(ERROR_TAB_KEYS.LIQUID);
307
- }
263
+ Array.isArray(rawStandardWarnings) ? rawStandardWarnings : [],
264
+ ), [rawStandardErrors, rawLiquidErrors, rawStandardWarnings]);
265
+
266
+ const errorsCount = errorIssues.length;
267
+ const warningsCount = warningIssues.length;
268
+ const totalCount = errorsCount + warningsCount;
269
+ const hasLiquidErrors = Array.isArray(rawLiquidErrors) && rawLiquidErrors.length > 0;
270
+
271
+ // Default active tab: errors if any, else warnings
272
+ useEffect(() => {
273
+ if (errorsCount > 0) {
274
+ setActiveKey(ERROR_TAB_KEYS.ERRORS);
275
+ } else if (warningsCount > 0) {
276
+ setActiveKey(ERROR_TAB_KEYS.WARNINGS);
308
277
  }
309
- }, [htmlCount, labelCount, liquidCount, activeKey]);
278
+ }, [errorsCount, warningsCount]);
310
279
 
311
280
  // Handle close
312
281
  const handleClose = () => {
@@ -329,48 +298,25 @@ export const ErrorInfoNote = (props) => {
329
298
  return null;
330
299
  }
331
300
 
332
- // Build tab panes (CapTab uses 'panes' with 'tab' and 'content' properties)
301
+ // Build tab panes: only Errors and Warnings
333
302
  const tabPanes = [];
334
303
 
335
- if (htmlCount > 0) {
336
- tabPanes.push({
337
- key: ERROR_TAB_KEYS.HTML,
338
- tab: (
339
- <CapLabelInline type="label2" className="error-info-note__tab-label">
340
- <FormattedMessage {...messages.htmlIssues} />
341
- <CapLabelInline type="label2" className="error-info-note__tab-count">
342
- (
343
- {htmlCount}
344
- )
345
- </CapLabelInline>
346
- </CapLabelInline>
347
- ),
348
- content: (
349
- <TabContent
350
- issues={htmlIssues}
351
- onErrorClick={onErrorClick}
352
- intl={intl}
353
- />
354
- ),
355
- });
356
- }
357
-
358
- if (labelCount > 0) {
304
+ if (errorsCount > 0) {
359
305
  tabPanes.push({
360
- key: ERROR_TAB_KEYS.LABEL,
306
+ key: ERROR_TAB_KEYS.ERRORS,
361
307
  tab: (
362
- <CapLabelInline type="label2" className="error-info-note__tab-label">
363
- <FormattedMessage {...messages.labelIssues} />
308
+ <CapLabelInline type="label2" className="error-info-note__tab-label error-info-note__tab-label--errors">
309
+ <FormattedMessage {...messages.errors} />
364
310
  <CapLabelInline type="label2" className="error-info-note__tab-count">
365
311
  (
366
- {labelCount}
312
+ {errorsCount}
367
313
  )
368
314
  </CapLabelInline>
369
315
  </CapLabelInline>
370
316
  ),
371
317
  content: (
372
318
  <TabContent
373
- issues={labelIssues}
319
+ issues={errorIssues}
374
320
  onErrorClick={onErrorClick}
375
321
  intl={intl}
376
322
  />
@@ -378,24 +324,22 @@ export const ErrorInfoNote = (props) => {
378
324
  });
379
325
  }
380
326
 
381
- // Show liquid issues tab even when liquid is disabled if liquid content is detected
382
- // This allows users to see errors when they add liquid content but liquid feature is not enabled
383
- if (liquidCount > 0) {
327
+ if (warningsCount > 0) {
384
328
  tabPanes.push({
385
- key: ERROR_TAB_KEYS.LIQUID,
329
+ key: ERROR_TAB_KEYS.WARNINGS,
386
330
  tab: (
387
- <CapLabelInline type="label2" className="error-info-note__tab-label">
388
- <FormattedMessage {...messages.liquidIssues} />
331
+ <CapLabelInline type="label2" className="error-info-note__tab-label error-info-note__tab-label--warnings">
332
+ <FormattedMessage {...messages.warnings} />
389
333
  <CapLabelInline type="label2" className="error-info-note__tab-count">
390
334
  (
391
- {liquidCount}
335
+ {warningsCount}
392
336
  )
393
337
  </CapLabelInline>
394
338
  </CapLabelInline>
395
339
  ),
396
340
  content: (
397
341
  <TabContent
398
- issues={liquidIssues}
342
+ issues={warningIssues}
399
343
  onErrorClick={onErrorClick}
400
344
  intl={intl}
401
345
  />
@@ -413,7 +357,7 @@ export const ErrorInfoNote = (props) => {
413
357
  className="error-info-note__tabs"
414
358
  />
415
359
  <CapRow className="error-info-note__actions">
416
- {activeKey === ERROR_TAB_KEYS.LIQUID && isLiquidEnabled && (
360
+ {hasLiquidErrors && isLiquidEnabled && (
417
361
  <CapButton
418
362
  type="flat"
419
363
  className="error-info-note__liquid-doc"
@@ -531,6 +475,7 @@ ErrorInfoNote.propTypes = {
531
475
  GENERIC: PropTypes.array,
532
476
  }),
533
477
  ]),
478
+ STANDARD_WARNING_MSG: PropTypes.array,
534
479
  }),
535
480
  onErrorClick: PropTypes.func,
536
481
  onClose: PropTypes.func,
@@ -6,18 +6,14 @@
6
6
  import { defineMessages } from "react-intl";
7
7
  const scope = "creatives.componentsV2.ErrorInfoNote";
8
8
  export default defineMessages({
9
- // Tab labels for new tabbed interface
10
- htmlIssues: {
11
- id: `${scope}.htmlIssues`,
12
- defaultMessage: "HTML issues",
13
- },
14
- labelIssues: {
15
- id: `${scope}.labelIssues`,
16
- defaultMessage: "Label issues",
17
- },
18
- liquidIssues: {
19
- id: `${scope}.liquidIssues`,
20
- defaultMessage: "Liquid issues",
9
+ // Tab labels: Errors = blocking, Warnings = non-blocking
10
+ errors: {
11
+ id: `${scope}.errors`,
12
+ defaultMessage: "Errors",
13
+ },
14
+ warnings: {
15
+ id: `${scope}.warnings`,
16
+ defaultMessage: "Warnings",
21
17
  },
22
18
  navigateToError: {
23
19
  id: `${scope}.navigateToError`,
@@ -189,10 +189,6 @@
189
189
  &--error {
190
190
  color: $CAP_RED;
191
191
  }
192
-
193
- &--warning {
194
- color: $CAP_YELLOW;
195
- }
196
192
  }
197
193
 
198
194
  &__item-content {
@@ -41,8 +41,8 @@ import { useValidation } from './hooks/useValidation';
41
41
  // Constants
42
42
  import {
43
43
  HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT, TAG, EMBEDDED, DEFAULT, FULL, ALL, SMS, EMAIL,
44
+ BLOCKING_ERROR_RULE_IDS,
44
45
  } from './constants';
45
- import { ISSUE_SOURCES, LABEL_ISSUE_PATTERNS } from './utils/validationConstants';
46
46
 
47
47
  // Styles
48
48
  import './_htmlEditor.scss';
@@ -51,6 +51,30 @@ import './components/FullscreenModal/_fullscreenModal.scss';
51
51
  // Messages
52
52
  import messages from './messages';
53
53
 
54
+ /** Check if an issue is a blocking error (Errors tab). Non-blocking = Warnings. */
55
+ const isBlockingError = (issue) => {
56
+ const { rule, source, severity } = issue || {};
57
+ if (rule === 'liquid-api-validation' || rule === 'standard-api-validation') return true;
58
+ if (source === 'liquid-validator' && severity === 'error') return true;
59
+ if (BLOCKING_ERROR_RULE_IDS.includes(rule)) return true;
60
+ return false;
61
+ };
62
+
63
+ /** Count issues as Errors (blocking) and Warnings (non-blocking). */
64
+ const countIssuesBySeverity = (allIssues) => {
65
+ let errors = 0;
66
+ let warnings = 0;
67
+ (allIssues || []).forEach((issue) => {
68
+ if (isBlockingError(issue)) errors += 1;
69
+ else warnings += 1;
70
+ });
71
+ return {
72
+ errors,
73
+ warnings,
74
+ total: (allIssues || []).length,
75
+ };
76
+ };
77
+
54
78
  const HTMLEditor = forwardRef(({
55
79
  intl,
56
80
  variant = HTML_EDITOR_VARIANTS.EMAIL, // New prop: 'email' or 'inapp'
@@ -276,96 +300,17 @@ const HTMLEditor = forwardRef(({
276
300
  getContent: () => currentContent,
277
301
  isContentEmpty: () => !currentContent || currentContent.trim() === '',
278
302
  getIssueCounts: () => {
279
- // Check if validation is ready and has getAllIssues method
280
303
  if (!validation || typeof validation.getAllIssues !== 'function') {
281
- return {
282
- html: 0, label: 0, liquid: 0, total: 0,
283
- };
304
+ return { errors: 0, warnings: 0, total: 0 };
284
305
  }
285
- const allIssues = validation.getAllIssues();
286
-
287
- let htmlCount = 0;
288
- let labelCount = 0;
289
- let liquidCount = 0;
290
-
291
- allIssues.forEach((issue) => {
292
- const { source, rule, message } = issue;
293
- const messageLower = (message || '').toLowerCase();
294
- const ruleLower = (rule || '').toLowerCase();
295
-
296
- // Check if it's a Liquid issue
297
- if (source === ISSUE_SOURCES.LIQUID) {
298
- liquidCount++;
299
- return;
300
- }
301
-
302
- // Check if it's a Label (tag syntax) issue
303
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
304
- (pattern) => messageLower.includes(pattern.toLowerCase())
305
- || ruleLower.includes(pattern.toLowerCase()),
306
- );
307
-
308
- if (isLabelIssue) {
309
- labelCount++;
310
- return;
311
- }
312
-
313
- // Default to HTML issues
314
- htmlCount++;
315
- });
316
-
317
- return {
318
- html: htmlCount,
319
- label: labelCount,
320
- liquid: liquidCount,
321
- total: allIssues.length,
322
- };
306
+ return countIssuesBySeverity(validation.getAllIssues());
323
307
  },
324
308
  getValidationState: () => ({
325
309
  isValidating: validation?.isValidating || false,
326
310
  hasErrors: validation?.hasBlockingErrors || false,
327
- issueCounts: (() => {
328
- if (!validation || typeof validation.getAllIssues !== 'function') {
329
- return {
330
- html: 0, label: 0, liquid: 0, total: 0,
331
- };
332
- }
333
- const allIssues = validation.getAllIssues();
334
- // Use same logic as getIssueCounts
335
- let htmlCount = 0;
336
- let labelCount = 0;
337
- let liquidCount = 0;
338
-
339
- allIssues.forEach((issue) => {
340
- const { source, rule, message } = issue;
341
- const messageLower = (message || '').toLowerCase();
342
- const ruleLower = (rule || '').toLowerCase();
343
-
344
- if (source === ISSUE_SOURCES.LIQUID) {
345
- liquidCount++;
346
- return;
347
- }
348
-
349
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
350
- (pattern) => messageLower.includes(pattern.toLowerCase())
351
- || ruleLower.includes(pattern.toLowerCase()),
352
- );
353
-
354
- if (isLabelIssue) {
355
- labelCount++;
356
- return;
357
- }
358
-
359
- htmlCount++;
360
- });
361
-
362
- return {
363
- html: htmlCount,
364
- label: labelCount,
365
- liquid: liquidCount,
366
- total: allIssues.length,
367
- };
368
- })(),
311
+ issueCounts: !validation || typeof validation.getAllIssues !== 'function'
312
+ ? { errors: 0, warnings: 0, total: 0 }
313
+ : countIssuesBySeverity(validation.getAllIssues()),
369
314
  })
370
315
  ,
371
316
  }), [validation, currentContent, apiValidationErrors]); // Include apiValidationErrors so ref methods return updated counts
@@ -402,49 +347,13 @@ const HTMLEditor = forwardRef(({
402
347
  return;
403
348
  }
404
349
 
405
- // Calculate issue counts from validation using ref (avoid stale closure)
350
+ // Calculate issue counts: Errors (blocking) and Warnings (non-blocking)
406
351
  const calculateIssueCounts = () => {
407
352
  const currentValidation = validationRef.current;
408
353
  if (!currentValidation || typeof currentValidation.getAllIssues !== 'function') {
409
- return {
410
- html: 0, label: 0, liquid: 0, total: 0,
411
- };
354
+ return { errors: 0, warnings: 0, total: 0 };
412
355
  }
413
- const allIssues = currentValidation.getAllIssues();
414
-
415
- let htmlCount = 0;
416
- let labelCount = 0;
417
- let liquidCount = 0;
418
-
419
- allIssues.forEach((issue) => {
420
- const { source, rule, message } = issue;
421
- const messageLower = (message || '').toLowerCase();
422
- const ruleLower = (rule || '').toLowerCase();
423
-
424
- if (source === ISSUE_SOURCES.LIQUID) {
425
- liquidCount += 1;
426
- return;
427
- }
428
-
429
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
430
- (pattern) => messageLower.includes(pattern.toLowerCase())
431
- || ruleLower.includes(pattern.toLowerCase()),
432
- );
433
-
434
- if (isLabelIssue) {
435
- labelCount += 1;
436
- return;
437
- }
438
-
439
- htmlCount += 1;
440
- });
441
-
442
- return {
443
- html: htmlCount,
444
- label: labelCount,
445
- liquid: liquidCount,
446
- total: allIssues.length,
447
- };
356
+ return countIssuesBySeverity(currentValidation.getAllIssues());
448
357
  };
449
358
 
450
359
  const issueCounts = calculateIssueCounts();
@@ -465,9 +374,8 @@ const HTMLEditor = forwardRef(({
465
374
  || lastState.validationComplete !== newState.validationComplete
466
375
  || lastState.hasErrors !== newState.hasErrors
467
376
  || lastState.issueCounts?.total !== newState.issueCounts?.total
468
- || lastState.issueCounts?.html !== newState.issueCounts?.html
469
- || lastState.issueCounts?.label !== newState.issueCounts?.label
470
- || lastState.issueCounts?.liquid !== newState.issueCounts?.liquid;
377
+ || lastState.issueCounts?.errors !== newState.issueCounts?.errors
378
+ || lastState.issueCounts?.warnings !== newState.issueCounts?.warnings;
471
379
 
472
380
  if (hasChanged) {
473
381
  lastSentValidationStateRef.current = newState;
@@ -487,9 +395,7 @@ const HTMLEditor = forwardRef(({
487
395
  const isContentEmpty = !currentContent || currentContent.trim() === '';
488
396
  onValidationChangeRef.current({
489
397
  isContentEmpty,
490
- issueCounts: {
491
- html: 0, label: 0, liquid: 0, total: 0,
492
- },
398
+ issueCounts: { errors: 0, warnings: 0, total: 0 },
493
399
  validationComplete: false, // Validation hasn't run yet
494
400
  hasErrors: false,
495
401
  });
@@ -507,51 +413,10 @@ const HTMLEditor = forwardRef(({
507
413
  return;
508
414
  }
509
415
 
510
- // Recalculate issue counts including API errors
511
- const calculateIssueCounts = () => {
512
- if (!validation || typeof validation.getAllIssues !== 'function') {
513
- return {
514
- html: 0, label: 0, liquid: 0, total: 0,
515
- };
516
- }
517
- const allIssues = validation.getAllIssues();
518
-
519
- let htmlCount = 0;
520
- let labelCount = 0;
521
- let liquidCount = 0;
522
-
523
- allIssues.forEach((issue) => {
524
- const { source, rule, message } = issue;
525
- const messageLower = (message || '').toLowerCase();
526
- const ruleLower = (rule || '').toLowerCase();
527
-
528
- if (source === ISSUE_SOURCES.LIQUID) {
529
- liquidCount += 1;
530
- return;
531
- }
532
-
533
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
534
- (pattern) => messageLower.includes(pattern.toLowerCase())
535
- || ruleLower.includes(pattern.toLowerCase()),
536
- );
537
-
538
- if (isLabelIssue) {
539
- labelCount += 1;
540
- return;
541
- }
542
-
543
- htmlCount += 1;
544
- });
545
-
546
- return {
547
- html: htmlCount,
548
- label: labelCount,
549
- liquid: liquidCount,
550
- total: allIssues.length,
551
- };
552
- };
553
-
554
- const issueCounts = calculateIssueCounts();
416
+ // Recalculate issue counts (Errors + Warnings) including API errors
417
+ const issueCounts = !validation || typeof validation.getAllIssues !== 'function'
418
+ ? { errors: 0, warnings: 0, total: 0 }
419
+ : countIssuesBySeverity(validation.getAllIssues());
555
420
  const isContentEmpty = !currentContent || currentContent.trim() === '';
556
421
 
557
422
  const newState = {
@@ -565,9 +430,8 @@ const HTMLEditor = forwardRef(({
565
430
  const hasChanged = !lastState
566
431
  || lastState.hasErrors !== newState.hasErrors
567
432
  || lastState.issueCounts?.total !== newState.issueCounts?.total
568
- || lastState.issueCounts?.html !== newState.issueCounts?.html
569
- || lastState.issueCounts?.label !== newState.issueCounts?.label
570
- || lastState.issueCounts?.liquid !== newState.issueCounts?.liquid;
433
+ || lastState.issueCounts?.errors !== newState.issueCounts?.errors
434
+ || lastState.issueCounts?.warnings !== newState.issueCounts?.warnings;
571
435
 
572
436
  if (hasChanged) {
573
437
  lastSentValidationStateRef.current = newState;
@@ -643,10 +507,10 @@ const HTMLEditor = forwardRef(({
643
507
  const handleSave = useCallback(() => {
644
508
  try {
645
509
  const { html, css, javascript } = content?.content || {};
646
- const currentContent = { html, css, javascript };
510
+ const contentToSave = { html, css, javascript };
647
511
 
648
512
  if (onSave) {
649
- onSave(currentContent);
513
+ onSave(contentToSave);
650
514
  }
651
515
 
652
516
  markAsSaved?.();
@@ -819,10 +683,10 @@ const HTMLEditor = forwardRef(({
819
683
  visible={isFullscreenModalOpen}
820
684
  onCancel={handleCloseFullscreen}
821
685
  footer={null}
822
- maskClosable={false}
686
+ maskClosable
823
687
  centered
824
688
  closable={false}
825
- width="90vw"
689
+ width="93vw"
826
690
  className="html-editor-fullscreen-modal"
827
691
  >
828
692
  <CapRow className={`html-editor-fullscreen html-editor-fullscreen--${variant} ${isLibraryMode ? 'html-editor-fullscreen--library-mode' : ''}`}>