@capillarytech/creatives-library 8.0.262-alpha.1 → 8.0.263

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 (29) hide show
  1. package/package.json +1 -1
  2. package/v2Components/ErrorInfoNote/index.js +58 -113
  3. package/v2Components/ErrorInfoNote/messages.js +8 -12
  4. package/v2Components/HtmlEditor/HTMLEditor.js +48 -182
  5. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +11 -15
  6. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +7 -8
  7. package/v2Components/HtmlEditor/_htmlEditor.scss +6 -6
  8. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +1 -1
  9. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +0 -1
  10. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +27 -2
  11. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +4 -4
  12. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +17 -0
  13. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +15 -28
  14. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +10 -33
  15. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +48 -13
  16. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +77 -146
  17. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +14 -14
  18. package/v2Components/HtmlEditor/constants.js +3 -0
  19. package/v2Components/HtmlEditor/hooks/useValidation.js +34 -13
  20. package/v2Components/HtmlEditor/messages.js +10 -0
  21. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +38 -36
  22. package/v2Components/HtmlEditor/utils/validationConstants.js +3 -4
  23. package/v2Components/MobilePushPreviewV2/constants.js +6 -0
  24. package/v2Components/MobilePushPreviewV2/index.js +4 -3
  25. package/v2Containers/CreativesContainer/index.js +1 -3
  26. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +6 -9
  27. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +5 -49
  28. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +23 -38
  29. 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.1",
4
+ "version": "8.0.263",
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="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"
@@ -462,7 +406,7 @@ const ErrorSection = ({
462
406
  <CapButton
463
407
  type="flat"
464
408
  className="add-btn"
465
- onClick={() => window.open('https://docs.capillarytech.com/docs/liquid-language-in-messages', '_blank')}
409
+ onClick={() => window.open(LIQUID_DOC_URL, '_blank')}
466
410
  >
467
411
  <FormattedMessage {...messages.liquidDoc} />
468
412
  <CapIcon size="s" type="launch" />
@@ -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`,
@@ -41,8 +41,9 @@ 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,
45
+ VALIDATION_SEVERITY,
44
46
  } from './constants';
45
- import { ISSUE_SOURCES, LABEL_ISSUE_PATTERNS } from './utils/validationConstants';
46
47
 
47
48
  // Styles
48
49
  import './_htmlEditor.scss';
@@ -50,6 +51,31 @@ import './components/FullscreenModal/_fullscreenModal.scss';
50
51
 
51
52
  // Messages
52
53
  import messages from './messages';
54
+ import { ISSUE_SOURCES } from './utils/validationConstants';
55
+
56
+ /** Check if an issue is a blocking error (Errors tab). Non-blocking = Warnings. */
57
+ const isBlockingError = (issue) => {
58
+ const { rule, source, severity } = issue || {};
59
+ if (rule === 'liquid-api-validation' || rule === 'standard-api-validation') return true;
60
+ if (source === ISSUE_SOURCES.LIQUID && severity === VALIDATION_SEVERITY.ERROR) return true;
61
+ if (BLOCKING_ERROR_RULE_IDS.includes(rule)) return true;
62
+ return false;
63
+ };
64
+
65
+ /** Count issues as Errors (blocking) and Warnings (non-blocking). */
66
+ const countIssuesBySeverity = (allIssues) => {
67
+ let errors = 0;
68
+ let warnings = 0;
69
+ (allIssues || []).forEach((issue) => {
70
+ if (isBlockingError(issue)) errors += 1;
71
+ else warnings += 1;
72
+ });
73
+ return {
74
+ errors,
75
+ warnings,
76
+ total: (allIssues || []).length,
77
+ };
78
+ };
53
79
 
54
80
  const HTMLEditor = forwardRef(({
55
81
  intl,
@@ -276,96 +302,17 @@ const HTMLEditor = forwardRef(({
276
302
  getContent: () => currentContent,
277
303
  isContentEmpty: () => !currentContent || currentContent.trim() === '',
278
304
  getIssueCounts: () => {
279
- // Check if validation is ready and has getAllIssues method
280
305
  if (!validation || typeof validation.getAllIssues !== 'function') {
281
- return {
282
- html: 0, label: 0, liquid: 0, total: 0,
283
- };
306
+ return { errors: 0, warnings: 0, total: 0 };
284
307
  }
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
- };
308
+ return countIssuesBySeverity(validation.getAllIssues());
323
309
  },
324
310
  getValidationState: () => ({
325
311
  isValidating: validation?.isValidating || false,
326
312
  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
- })(),
313
+ issueCounts: !validation || typeof validation.getAllIssues !== 'function'
314
+ ? { errors: 0, warnings: 0, total: 0 }
315
+ : countIssuesBySeverity(validation.getAllIssues()),
369
316
  })
370
317
  ,
371
318
  }), [validation, currentContent, apiValidationErrors]); // Include apiValidationErrors so ref methods return updated counts
@@ -402,49 +349,13 @@ const HTMLEditor = forwardRef(({
402
349
  return;
403
350
  }
404
351
 
405
- // Calculate issue counts from validation using ref (avoid stale closure)
352
+ // Calculate issue counts: Errors (blocking) and Warnings (non-blocking)
406
353
  const calculateIssueCounts = () => {
407
354
  const currentValidation = validationRef.current;
408
355
  if (!currentValidation || typeof currentValidation.getAllIssues !== 'function') {
409
- return {
410
- html: 0, label: 0, liquid: 0, total: 0,
411
- };
356
+ return { errors: 0, warnings: 0, total: 0 };
412
357
  }
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
- };
358
+ return countIssuesBySeverity(currentValidation.getAllIssues());
448
359
  };
449
360
 
450
361
  const issueCounts = calculateIssueCounts();
@@ -465,9 +376,8 @@ const HTMLEditor = forwardRef(({
465
376
  || lastState.validationComplete !== newState.validationComplete
466
377
  || lastState.hasErrors !== newState.hasErrors
467
378
  || 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;
379
+ || lastState.issueCounts?.errors !== newState.issueCounts?.errors
380
+ || lastState.issueCounts?.warnings !== newState.issueCounts?.warnings;
471
381
 
472
382
  if (hasChanged) {
473
383
  lastSentValidationStateRef.current = newState;
@@ -487,9 +397,7 @@ const HTMLEditor = forwardRef(({
487
397
  const isContentEmpty = !currentContent || currentContent.trim() === '';
488
398
  onValidationChangeRef.current({
489
399
  isContentEmpty,
490
- issueCounts: {
491
- html: 0, label: 0, liquid: 0, total: 0,
492
- },
400
+ issueCounts: { errors: 0, warnings: 0, total: 0 },
493
401
  validationComplete: false, // Validation hasn't run yet
494
402
  hasErrors: false,
495
403
  });
@@ -507,51 +415,10 @@ const HTMLEditor = forwardRef(({
507
415
  return;
508
416
  }
509
417
 
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();
418
+ // Recalculate issue counts (Errors + Warnings) including API errors
419
+ const issueCounts = !validation || typeof validation.getAllIssues !== 'function'
420
+ ? { errors: 0, warnings: 0, total: 0 }
421
+ : countIssuesBySeverity(validation.getAllIssues());
555
422
  const isContentEmpty = !currentContent || currentContent.trim() === '';
556
423
 
557
424
  const newState = {
@@ -565,9 +432,8 @@ const HTMLEditor = forwardRef(({
565
432
  const hasChanged = !lastState
566
433
  || lastState.hasErrors !== newState.hasErrors
567
434
  || 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;
435
+ || lastState.issueCounts?.errors !== newState.issueCounts?.errors
436
+ || lastState.issueCounts?.warnings !== newState.issueCounts?.warnings;
571
437
 
572
438
  if (hasChanged) {
573
439
  lastSentValidationStateRef.current = newState;
@@ -643,10 +509,10 @@ const HTMLEditor = forwardRef(({
643
509
  const handleSave = useCallback(() => {
644
510
  try {
645
511
  const { html, css, javascript } = content?.content || {};
646
- const currentContent = { html, css, javascript };
512
+ const contentToSave = { html, css, javascript };
647
513
 
648
514
  if (onSave) {
649
- onSave(currentContent);
515
+ onSave(contentToSave);
650
516
  }
651
517
 
652
518
  markAsSaved?.();
@@ -819,10 +685,10 @@ const HTMLEditor = forwardRef(({
819
685
  visible={isFullscreenModalOpen}
820
686
  onCancel={handleCloseFullscreen}
821
687
  footer={null}
822
- maskClosable={false}
688
+ maskClosable
823
689
  centered
824
690
  closable={false}
825
- width="90vw"
691
+ width="93vw"
826
692
  className="html-editor-fullscreen-modal"
827
693
  >
828
694
  <CapRow className={`html-editor-fullscreen html-editor-fullscreen--${variant} ${isLibraryMode ? 'html-editor-fullscreen--library-mode' : ''}`}>