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

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 (133) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +1 -2
  4. package/initialReducer.js +0 -2
  5. package/package.json +1 -1
  6. package/services/api.js +0 -10
  7. package/services/tests/api.test.js +0 -18
  8. package/utils/common.js +0 -5
  9. package/utils/commonUtils.js +5 -28
  10. package/utils/tests/commonUtil.test.js +0 -224
  11. package/utils/transformTemplateConfig.js +10 -0
  12. package/v2Components/CapDeviceContent/index.js +56 -61
  13. package/v2Components/CapTagList/index.js +1 -6
  14. package/v2Components/CapTagListWithInput/index.js +1 -5
  15. package/v2Components/CapTagListWithInput/messages.js +1 -1
  16. package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
  17. package/v2Components/ErrorInfoNote/index.js +72 -447
  18. package/v2Components/ErrorInfoNote/messages.js +0 -22
  19. package/v2Components/ErrorInfoNote/style.scss +4 -280
  20. package/v2Components/FormBuilder/tests/index.test.js +4 -13
  21. package/v2Components/HtmlEditor/HTMLEditor.js +94 -642
  22. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1135
  23. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +16 -27
  24. package/v2Components/HtmlEditor/_htmlEditor.scss +45 -108
  25. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  26. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -13
  27. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +139 -148
  28. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
  29. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  30. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +0 -9
  31. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  32. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -22
  33. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
  34. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
  35. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
  36. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  37. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
  38. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +6 -3
  39. package/v2Components/HtmlEditor/components/PreviewPane/index.js +13 -11
  40. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  41. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
  42. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -49
  43. package/v2Components/HtmlEditor/constants.js +20 -29
  44. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
  45. package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
  46. package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
  47. package/v2Components/HtmlEditor/hooks/useValidation.js +45 -150
  48. package/v2Components/HtmlEditor/index.js +1 -1
  49. package/v2Components/HtmlEditor/messages.js +85 -95
  50. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +102 -134
  51. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
  52. package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -66
  53. package/v2Components/MobilePushPreviewV2/index.js +7 -32
  54. package/v2Components/TemplatePreview/_templatePreview.scss +24 -44
  55. package/v2Components/TemplatePreview/index.js +32 -47
  56. package/v2Components/TemplatePreview/messages.js +0 -4
  57. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +0 -1
  58. package/v2Components/TestAndPreviewSlidebox/index.js +25 -31
  59. package/v2Containers/BeeEditor/index.js +90 -172
  60. package/v2Containers/CreativesContainer/SlideBoxContent.js +51 -128
  61. package/v2Containers/CreativesContainer/SlideBoxFooter.js +12 -113
  62. package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -2
  63. package/v2Containers/CreativesContainer/constants.js +0 -1
  64. package/v2Containers/CreativesContainer/index.js +46 -238
  65. package/v2Containers/CreativesContainer/messages.js +0 -8
  66. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +2 -11
  67. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +50 -38
  68. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -91
  69. package/v2Containers/Email/actions.js +0 -7
  70. package/v2Containers/Email/constants.js +1 -5
  71. package/v2Containers/Email/index.js +30 -229
  72. package/v2Containers/Email/messages.js +0 -32
  73. package/v2Containers/Email/reducer.js +1 -12
  74. package/v2Containers/Email/sagas.js +7 -61
  75. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
  76. package/v2Containers/Email/tests/sagas.test.js +1 -1
  77. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +15 -210
  78. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
  79. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
  80. package/v2Containers/EmailWrapper/constants.js +0 -2
  81. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +77 -629
  82. package/v2Containers/EmailWrapper/index.js +23 -103
  83. package/v2Containers/EmailWrapper/messages.js +1 -61
  84. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
  85. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -509
  86. package/v2Containers/InApp/actions.js +0 -7
  87. package/v2Containers/InApp/constants.js +4 -20
  88. package/v2Containers/InApp/index.js +357 -801
  89. package/v2Containers/InApp/index.scss +3 -4
  90. package/v2Containers/InApp/messages.js +3 -7
  91. package/v2Containers/InApp/reducer.js +3 -21
  92. package/v2Containers/InApp/sagas.js +9 -29
  93. package/v2Containers/InApp/selectors.js +5 -25
  94. package/v2Containers/InApp/tests/index.test.js +50 -154
  95. package/v2Containers/InApp/tests/reducer.test.js +0 -34
  96. package/v2Containers/InApp/tests/sagas.test.js +9 -61
  97. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +0 -3
  98. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +0 -2
  99. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +0 -2
  100. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +0 -9
  101. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -12
  102. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +0 -4
  103. package/v2Containers/TagList/index.js +19 -62
  104. package/v2Containers/Templates/_templates.scss +1 -60
  105. package/v2Containers/Templates/index.js +4 -89
  106. package/v2Containers/Templates/messages.js +0 -4
  107. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +0 -35
  108. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -874
  109. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -254
  110. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -363
  111. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
  112. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +0 -630
  113. package/v2Containers/BeePopupEditor/constants.js +0 -10
  114. package/v2Containers/BeePopupEditor/index.js +0 -193
  115. package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
  116. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1317
  117. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -1605
  118. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +0 -520
  119. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +0 -643
  120. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
  121. package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
  122. package/v2Containers/InApp/tests/selectors.test.js +0 -612
  123. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -162
  124. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
  125. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -9
  126. package/v2Containers/InAppWrapper/constants.js +0 -16
  127. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
  128. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
  129. package/v2Containers/InAppWrapper/index.js +0 -148
  130. package/v2Containers/InAppWrapper/messages.js +0 -49
  131. package/v2Containers/InappAdvance/index.js +0 -1099
  132. package/v2Containers/InappAdvance/index.scss +0 -10
  133. package/v2Containers/InappAdvance/tests/index.test.js +0 -448
@@ -12,14 +12,14 @@
12
12
  * Note: Uses injectIntl with forwardRef to provide direct access to CodeEditorPane via ref
13
13
  */
14
14
 
15
- import React, {
16
- useRef, useCallback, useMemo, useState, useEffect, useImperativeHandle, forwardRef,
17
- } from 'react';
15
+ import React, { useRef, useCallback, useMemo, useState } from 'react';
18
16
  import PropTypes from 'prop-types';
17
+ import { Layout } from 'antd'; // Fallback - no Cap UI equivalent
19
18
  import { injectIntl, intlShape } from 'react-intl';
20
19
 
21
20
  // Cap UI Components (First Preference)
22
21
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
22
+ import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
23
23
  import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
24
24
  import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
25
25
  import CapModal from '@capillarytech/cap-ui-library/CapModal';
@@ -30,6 +30,7 @@ import DeviceToggle from './components/DeviceToggle';
30
30
  import SplitContainer from './components/SplitContainer';
31
31
  import CodeEditorPane from './components/CodeEditorPane';
32
32
  import PreviewPane from './components/PreviewPane';
33
+ import ValidationErrorDisplay from './components/ValidationErrorDisplay';
33
34
  import { EditorProvider } from './components/common/EditorContext';
34
35
 
35
36
  // Hooks and utilities
@@ -39,9 +40,7 @@ import { useLayoutState } from './hooks/useLayoutState';
39
40
  import { useValidation } from './hooks/useValidation';
40
41
 
41
42
  // Constants
42
- import {
43
- HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT, TAG, EMBEDDED, DEFAULT, FULL, ALL, SMS, EMAIL,
44
- } from './constants';
43
+ import { HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT } from './constants';
45
44
 
46
45
  // Styles
47
46
  import './_htmlEditor.scss';
@@ -50,7 +49,7 @@ import './components/FullscreenModal/_fullscreenModal.scss';
50
49
  // Messages
51
50
  import messages from './messages';
52
51
 
53
- const HTMLEditor = forwardRef(({
52
+ const HTMLEditor = ({
54
53
  intl,
55
54
  variant = HTML_EDITOR_VARIANTS.EMAIL, // New prop: 'email' or 'inapp'
56
55
  layoutType, // Layout type for InApp variant
@@ -62,33 +61,17 @@ const HTMLEditor = forwardRef(({
62
61
  showFullscreenButton = true,
63
62
  autoSave = true,
64
63
  autoSaveInterval = 30000, // 30 seconds
65
- // Tag-related props - tags are fetched and managed by parent component (EmailHTMLEditor, INAPP, etc.)
66
- tags = [],
67
- injectedTags = {},
68
- location,
69
- eventContextTags = [],
70
- selectedOfferDetails = [],
71
- channel,
72
- userLocale = 'en',
73
- moduleFilterEnabled = true,
74
- onTagContextChange, // Parent component handles tag fetching
75
- onTagSelect = null,
76
- onContextChange = null,
77
- globalActions = null,
78
- isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
79
- isFullMode = true, // Full mode vs library mode - controls layout and visibility
80
- onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
81
- onValidationChange = null, // Callback when validation state changes (for parent to track errors)
82
- apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent { liquidErrors: [], standardErrors: [] }
83
64
  ...props
84
- }, ref) => {
65
+ }) => {
85
66
  // Separate refs for main and modal editors to avoid conflicts
86
67
  const mainEditorRef = useRef(null);
87
68
  const modalEditorRef = useRef(null);
88
69
  const [isFullscreenModalOpen, setIsFullscreenModalOpen] = useState(false);
89
70
 
90
71
  // Get the currently active editor ref based on fullscreen state
91
- const getActiveEditorRef = useCallback(() => isFullscreenModalOpen ? modalEditorRef : mainEditorRef, [isFullscreenModalOpen]);
72
+ const getActiveEditorRef = useCallback(() => {
73
+ return isFullscreenModalOpen ? modalEditorRef : mainEditorRef;
74
+ }, [isFullscreenModalOpen]);
92
75
 
93
76
  // Initialize custom hooks for state management - always call both hooks to follow Rules of Hooks
94
77
  const isEmailVariant = variant === HTML_EDITOR_VARIANTS.EMAIL;
@@ -97,7 +80,7 @@ const HTMLEditor = forwardRef(({
97
80
  autoSave: isEmailVariant ? autoSave : false,
98
81
  autoSaveInterval,
99
82
  onSave: isEmailVariant ? onSave : null,
100
- onChange: isEmailVariant ? onContentChange : null,
83
+ onChange: isEmailVariant ? onContentChange : null
101
84
  };
102
85
 
103
86
  const emailContent = useEditorContent(
@@ -114,7 +97,7 @@ const HTMLEditor = forwardRef(({
114
97
  // Convert string content to device-specific format
115
98
  inAppInitialContent = {
116
99
  [DEVICE_TYPES.ANDROID]: initialContent,
117
- [DEVICE_TYPES.IOS]: initialContent,
100
+ [DEVICE_TYPES.IOS]: initialContent
118
101
  };
119
102
  } else {
120
103
  // Use provided device-specific content
@@ -126,7 +109,7 @@ const HTMLEditor = forwardRef(({
126
109
  autoSave: isInAppVariant ? autoSave : false,
127
110
  autoSaveInterval,
128
111
  onSave: isInAppVariant ? onSave : null,
129
- onChange: isInAppVariant ? onContentChange : null,
112
+ onChange: isInAppVariant ? onContentChange : null
130
113
  };
131
114
 
132
115
  const inAppContent = useInAppContent(inAppInitialContent, inAppOptions);
@@ -134,64 +117,6 @@ const HTMLEditor = forwardRef(({
134
117
  // Use appropriate content hook based on variant
135
118
  const content = variant === HTML_EDITOR_VARIANTS.EMAIL ? emailContent : inAppContent;
136
119
 
137
- // Update content when initialContent prop changes (for edit mode)
138
- // This ensures the editor updates when template data loads
139
- useEffect(() => {
140
- if (isEmailVariant && emailContent && initialContent !== undefined && initialContent !== null) {
141
- // Only update if content is different to avoid unnecessary updates
142
- if (emailContent.content !== initialContent) {
143
- emailContent.updateContent(initialContent, true); // immediate update
144
- }
145
- } else if (isInAppVariant && inAppContent && initialContent !== undefined && initialContent !== null) {
146
- // Handle InApp variant updates
147
- const contentToUpdate = typeof initialContent === 'string'
148
- ? { [DEVICE_TYPES.ANDROID]: initialContent, [DEVICE_TYPES.IOS]: initialContent }
149
- : initialContent;
150
- if (inAppContent.updateContent) {
151
- const currentContent = inAppContent.getDeviceContent?.(inAppContent.activeDevice);
152
- const newContent = contentToUpdate[inAppContent.activeDevice] || contentToUpdate[DEVICE_TYPES.ANDROID] || '';
153
- if (currentContent !== newContent) {
154
- inAppContent.updateContent(newContent, true);
155
- }
156
- }
157
- }
158
- }, [initialContent, isEmailVariant, isInAppVariant]);
159
- // Handle context change for tag API calls
160
- // If variant is INAPP, use SMS layout; otherwise use the channel (EMAIL)
161
- const handleContextChange = useCallback((contextData) => {
162
- // If onContextChange is provided, use it instead of making our own API call
163
- // This prevents duplicate API calls when parent component handles tag fetching
164
- if (onContextChange) {
165
- onContextChange(contextData);
166
- return;
167
- }
168
-
169
- // Only make API call if onContextChange is not provided and globalActions is available
170
- if (!globalActions || !location) {
171
- return;
172
- }
173
-
174
- const { type } = location.query || {};
175
- const tempData = (contextData || '').toLowerCase();
176
- const isEmbedded = type === EMBEDDED;
177
- const embedded = isEmbedded ? type : FULL;
178
- const context = tempData === ALL ? DEFAULT : tempData;
179
-
180
- // Determine layout: INAPP variant uses SMS, EMAIL variant uses EMAIL
181
- const layout = variant === HTML_EDITOR_VARIANTS.INAPP ? SMS : EMAIL;
182
-
183
- const query = {
184
- layout,
185
- type: TAG,
186
- context,
187
- embedded,
188
- };
189
-
190
- // Call the API via Redux action - this will trigger the saga which calls Api.fetchSchemaForEntity
191
- // The API endpoint will be: /meta/TAG?query={...}
192
- globalActions.fetchSchemaForEntity(query);
193
- }, [variant, globalActions, location, onContextChange]);
194
-
195
120
  // Destructure content properties for cleaner access throughout component
196
121
  const {
197
122
  activeDevice,
@@ -199,14 +124,14 @@ const HTMLEditor = forwardRef(({
199
124
  switchDevice,
200
125
  toggleContentSync,
201
126
  getDeviceContent,
202
- markAsSaved,
127
+ markAsSaved
203
128
  } = content || {};
204
129
 
205
130
  const layout = useLayoutState({
206
131
  splitSizes: [50, 50],
207
132
  viewMode: 'desktop',
208
133
  mobileWidth: 375,
209
- isFullscreen: false,
134
+ isFullscreen: false
210
135
  });
211
136
 
212
137
  // Get current content for validation based on variant
@@ -233,7 +158,7 @@ const HTMLEditor = forwardRef(({
233
158
  'sanitizer.productionValidHtml': messages.sanitizer.productionValidHtml,
234
159
  'sanitizer.productionSanitized': messages.sanitizer.productionSanitized,
235
160
  'sanitizer.productionInlineCss': messages.sanitizer.productionInlineCss,
236
- 'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent,
161
+ 'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent
237
162
  };
238
163
 
239
164
  const messageObj = messageMap[messageKey];
@@ -254,7 +179,7 @@ const HTMLEditor = forwardRef(({
254
179
  'validator.largeImageDetected': messages.validator.largeImageDetected,
255
180
  'validator.unclosedCssRule': messages.validator.unclosedCssRule,
256
181
  'validator.emptyCssRule': messages.validator.emptyCssRule,
257
- 'validator.cssValidationFailed': messages.validator.cssValidationFailed,
182
+ 'validator.cssValidationFailed': messages.validator.cssValidationFailed
258
183
  };
259
184
 
260
185
  const messageObj = messageMap[messageKey];
@@ -265,471 +190,57 @@ const HTMLEditor = forwardRef(({
265
190
  enableRealTime: true,
266
191
  debounceMs: 500,
267
192
  enableSanitization: true,
268
- securityLevel: 'standard',
269
- apiValidationErrors, // Pass API validation errors to merge with client-side validation
193
+ securityLevel: 'standard'
270
194
  }, formatSanitizerMessage, formatValidatorMessage);
271
195
 
272
- // Expose validation and content state via ref
273
- useImperativeHandle(ref, () => ({
274
- getValidation: () => validation,
275
- getContent: () => currentContent,
276
- isContentEmpty: () => !currentContent || currentContent.trim() === '',
277
- getIssueCounts: () => {
278
- // Check if validation is ready and has getAllIssues method
279
- if (!validation || typeof validation.getAllIssues !== 'function') {
280
- return {
281
- html: 0, label: 0, liquid: 0, total: 0,
282
- };
283
- }
284
- const allIssues = validation.getAllIssues();
285
- const ISSUE_SOURCES = {
286
- HTMLHINT: 'htmlhint',
287
- CSS_VALIDATOR: 'css-validator',
288
- CUSTOM: 'custom',
289
- SECURITY: 'security',
290
- LIQUID: 'liquid-validator',
291
- };
292
- const LABEL_ISSUE_PATTERNS = [
293
- 'tag must be paired',
294
- 'open tag match failed',
295
- 'closed tag match failed',
296
- 'unclosed',
297
- 'missing required',
298
- 'tag-pair',
299
- 'attr-value-not-empty',
300
- 'attr-no-duplication',
301
- 'tag-self-close',
302
- 'spec-char-escape',
303
- 'tagname-lowercase',
304
- 'attr-lowercase',
305
- 'src-not-empty',
306
- 'alt-require',
307
- ];
308
-
309
- let htmlCount = 0;
310
- let labelCount = 0;
311
- let liquidCount = 0;
312
-
313
- allIssues.forEach((issue) => {
314
- const { source, rule, message } = issue;
315
- const messageLower = (message || '').toLowerCase();
316
- const ruleLower = (rule || '').toLowerCase();
317
-
318
- // Check if it's a Liquid issue
319
- if (source === ISSUE_SOURCES.LIQUID) {
320
- liquidCount++;
321
- return;
322
- }
323
-
324
- // Check if it's a Label (tag syntax) issue
325
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
326
- (pattern) => messageLower.includes(pattern.toLowerCase())
327
- || ruleLower.includes(pattern.toLowerCase()),
328
- );
329
-
330
- if (isLabelIssue) {
331
- labelCount++;
332
- return;
333
- }
196
+ // Handle label insertion at cursor position
197
+ const handleLabelInsert = useCallback((label, position) => {
198
+ // With injectIntl({ forwardRef: true }), ref points directly to CodeEditorPane
199
+ const activeEditorRef = getActiveEditorRef();
200
+ const editor = activeEditorRef.current;
334
201
 
335
- // Default to HTML issues
336
- htmlCount++;
202
+ if (!editor) {
203
+ CapNotification.warning({
204
+ message: intl.formatMessage(messages.labelInsertError),
205
+ description: intl.formatMessage(messages.editorNotReady),
206
+ duration: 3
337
207
  });
338
-
339
- return {
340
- html: htmlCount,
341
- label: labelCount,
342
- liquid: liquidCount,
343
- total: allIssues.length,
344
- };
345
- },
346
- getValidationState: () =>
347
- // Return validation state including whether validation is complete
348
- ({
349
- isValidating: validation?.isValidating || false,
350
- hasErrors: validation?.hasErrors || false,
351
- issueCounts: (() => {
352
- if (!validation || typeof validation.getAllIssues !== 'function') {
353
- return {
354
- html: 0, label: 0, liquid: 0, total: 0,
355
- };
356
- }
357
- const allIssues = validation.getAllIssues();
358
- // Use same logic as getIssueCounts
359
- const ISSUE_SOURCES = {
360
- HTMLHINT: 'htmlhint',
361
- CSS_VALIDATOR: 'css-validator',
362
- CUSTOM: 'custom',
363
- SECURITY: 'security',
364
- LIQUID: 'liquid-validator',
365
- };
366
- const LABEL_ISSUE_PATTERNS = [
367
- 'tag must be paired',
368
- 'open tag match failed',
369
- 'closed tag match failed',
370
- 'unclosed',
371
- 'missing required',
372
- 'tag-pair',
373
- 'attr-value-not-empty',
374
- 'attr-no-duplication',
375
- 'tag-self-close',
376
- 'spec-char-escape',
377
- 'tagname-lowercase',
378
- 'attr-lowercase',
379
- 'src-not-empty',
380
- 'alt-require',
381
- ];
382
-
383
- let htmlCount = 0;
384
- let labelCount = 0;
385
- let liquidCount = 0;
386
-
387
- allIssues.forEach((issue) => {
388
- const { source, rule, message } = issue;
389
- const messageLower = (message || '').toLowerCase();
390
- const ruleLower = (rule || '').toLowerCase();
391
-
392
- if (source === ISSUE_SOURCES.LIQUID) {
393
- liquidCount++;
394
- return;
395
- }
396
-
397
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
398
- (pattern) => messageLower.includes(pattern.toLowerCase())
399
- || ruleLower.includes(pattern.toLowerCase()),
400
- );
401
-
402
- if (isLabelIssue) {
403
- labelCount++;
404
- return;
405
- }
406
-
407
- htmlCount++;
408
- });
409
-
410
- return {
411
- html: htmlCount,
412
- label: labelCount,
413
- liquid: liquidCount,
414
- total: allIssues.length,
415
- };
416
- })(),
417
- })
418
- ,
419
- }), [validation, currentContent, apiValidationErrors]); // Include apiValidationErrors so ref methods return updated counts
420
-
421
- // Use ref to store callback to avoid infinite loops (callback in deps would cause re-runs)
422
- const onValidationChangeRef = useRef(onValidationChange);
423
- useEffect(() => {
424
- onValidationChangeRef.current = onValidationChange;
425
- }, [onValidationChange]);
426
-
427
- // Track last sent validation state to prevent duplicate updates
428
- const lastSentValidationStateRef = useRef(null);
429
-
430
- // Store validation ref to access current value without triggering re-renders
431
- const validationRef = useRef(validation);
432
- validationRef.current = validation;
433
-
434
- // Extract STABLE primitive values from validation for dependency array
435
- // This prevents the useEffect from running on every render due to validation object recreation
436
- const isValidating = validation?.isValidating;
437
- const validationTotalErrors = validation?.summary?.totalErrors || 0;
438
- const validationTotalWarnings = validation?.summary?.totalWarnings || 0;
439
- const validationLastValidated = validation?.lastValidated?.getTime?.() || null;
440
- // Track total issues (errors + warnings + info) for detecting changes
441
- const validationHasErrors = validation?.hasErrors || false;
442
-
443
- // Notify parent component when validation state changes
444
- useEffect(() => {
445
- if (!onValidationChangeRef.current) {
446
208
  return;
447
209
  }
448
210
 
449
- // Skip if still validating (wait for validation to complete)
450
- if (isValidating) {
451
- return;
452
- }
453
-
454
- // Calculate issue counts from validation using ref (avoid stale closure)
455
- const calculateIssueCounts = () => {
456
- const currentValidation = validationRef.current;
457
- if (!currentValidation || typeof currentValidation.getAllIssues !== 'function') {
458
- return {
459
- html: 0, label: 0, liquid: 0, total: 0,
460
- };
461
- }
462
- const allIssues = currentValidation.getAllIssues();
463
- const ISSUE_SOURCES = {
464
- HTMLHINT: 'htmlhint',
465
- CSS_VALIDATOR: 'css-validator',
466
- CUSTOM: 'custom',
467
- SECURITY: 'security',
468
- LIQUID: 'liquid-validator',
469
- };
470
- const LABEL_ISSUE_PATTERNS = [
471
- 'tag must be paired',
472
- 'open tag match failed',
473
- 'closed tag match failed',
474
- 'unclosed',
475
- 'missing required',
476
- 'tag-pair',
477
- 'attr-value-not-empty',
478
- 'attr-no-duplication',
479
- 'tag-self-close',
480
- 'spec-char-escape',
481
- 'tagname-lowercase',
482
- 'attr-lowercase',
483
- 'src-not-empty',
484
- 'alt-require',
485
- ];
486
-
487
- let htmlCount = 0;
488
- let labelCount = 0;
489
- let liquidCount = 0;
490
-
491
- allIssues.forEach((issue) => {
492
- const { source, rule, message } = issue;
493
- const messageLower = (message || '').toLowerCase();
494
- const ruleLower = (rule || '').toLowerCase();
495
-
496
- if (source === ISSUE_SOURCES.LIQUID) {
497
- liquidCount += 1;
498
- return;
499
- }
500
-
501
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
502
- (pattern) => messageLower.includes(pattern.toLowerCase())
503
- || ruleLower.includes(pattern.toLowerCase()),
504
- );
505
-
506
- if (isLabelIssue) {
507
- labelCount += 1;
508
- return;
509
- }
510
-
511
- htmlCount += 1;
211
+ // Check if the required methods exist
212
+ if (typeof editor?.insertText !== 'function') {
213
+ CapNotification.error({
214
+ message: intl.formatMessage(messages.labelInsertError),
215
+ description: intl.formatMessage(messages.editorMethodNotAvailable),
216
+ duration: 4
512
217
  });
513
-
514
- return {
515
- html: htmlCount,
516
- label: labelCount,
517
- liquid: liquidCount,
518
- total: allIssues.length,
519
- };
520
- };
521
-
522
- const issueCounts = calculateIssueCounts();
523
- const isContentEmpty = !currentContent || currentContent.trim() === '';
524
-
525
- // Create new state to compare
526
- const newState = {
527
- isContentEmpty,
528
- issueCounts,
529
- validationComplete: true,
530
- hasErrors: issueCounts.total > 0,
531
- };
532
-
533
- // Only call callback if state actually changed (prevent infinite loops)
534
- const lastState = lastSentValidationStateRef.current;
535
- const hasChanged = !lastState
536
- || lastState.isContentEmpty !== newState.isContentEmpty
537
- || lastState.validationComplete !== newState.validationComplete
538
- || lastState.hasErrors !== newState.hasErrors
539
- || lastState.issueCounts?.total !== newState.issueCounts?.total
540
- || lastState.issueCounts?.html !== newState.issueCounts?.html
541
- || lastState.issueCounts?.label !== newState.issueCounts?.label
542
- || lastState.issueCounts?.liquid !== newState.issueCounts?.liquid;
543
-
544
- if (hasChanged) {
545
- lastSentValidationStateRef.current = newState;
546
- onValidationChangeRef.current(newState);
547
- }
548
- }, [isValidating, validationTotalErrors, validationTotalWarnings, validationLastValidated, validationHasErrors, currentContent, apiValidationErrors]); // Include apiValidationErrors to trigger recalculation when API errors change
549
-
550
- // Send initial state on mount to ensure parent has correct initial button state
551
- const hasInitializedRef = useRef(false);
552
- useEffect(() => {
553
- if (hasInitializedRef.current || !onValidationChangeRef.current) {
554
218
  return;
555
219
  }
556
- hasInitializedRef.current = true;
557
-
558
- // Send initial state with validationComplete=false to indicate validation pending
559
- const isContentEmpty = !currentContent || currentContent.trim() === '';
560
- onValidationChangeRef.current({
561
- isContentEmpty,
562
- issueCounts: {
563
- html: 0, label: 0, liquid: 0, total: 0,
564
- },
565
- validationComplete: false, // Validation hasn't run yet
566
- hasErrors: false,
567
- });
568
- }, [currentContent]); // Only depend on currentContent to run on initial content load
569
-
570
- // Force validation state recalculation when API validation errors change
571
- // This ensures that API errors are included in issue counts and displayed in ValidationErrorDisplay
572
- useEffect(() => {
573
- if (!onValidationChangeRef.current) {
574
- return;
575
- }
576
-
577
- // Skip if still validating (wait for validation to complete)
578
- if (validation?.isValidating) {
579
- return;
580
- }
581
-
582
- // Recalculate issue counts including API errors
583
- const calculateIssueCounts = () => {
584
- if (!validation || typeof validation.getAllIssues !== 'function') {
585
- return {
586
- html: 0, label: 0, liquid: 0, total: 0,
587
- };
588
- }
589
- const allIssues = validation.getAllIssues();
590
- const ISSUE_SOURCES = {
591
- HTMLHINT: 'htmlhint',
592
- CSS_VALIDATOR: 'css-validator',
593
- CUSTOM: 'custom',
594
- SECURITY: 'security',
595
- LIQUID: 'liquid-validator',
596
- };
597
- const LABEL_ISSUE_PATTERNS = [
598
- 'tag must be paired',
599
- 'open tag match failed',
600
- 'closed tag match failed',
601
- 'unclosed',
602
- 'missing required',
603
- 'tag-pair',
604
- 'attr-value-not-empty',
605
- 'attr-no-duplication',
606
- 'tag-self-close',
607
- 'spec-char-escape',
608
- 'tagname-lowercase',
609
- 'attr-lowercase',
610
- 'src-not-empty',
611
- 'alt-require',
612
- ];
613
-
614
- let htmlCount = 0;
615
- let labelCount = 0;
616
- let liquidCount = 0;
617
-
618
- allIssues.forEach((issue) => {
619
- const { source, rule, message } = issue;
620
- const messageLower = (message || '').toLowerCase();
621
- const ruleLower = (rule || '').toLowerCase();
622
-
623
- if (source === ISSUE_SOURCES.LIQUID) {
624
- liquidCount += 1;
625
- return;
626
- }
627
-
628
- const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
629
- (pattern) => messageLower.includes(pattern.toLowerCase())
630
- || ruleLower.includes(pattern.toLowerCase()),
631
- );
632
-
633
- if (isLabelIssue) {
634
- labelCount += 1;
635
- return;
636
- }
637
-
638
- htmlCount += 1;
639
- });
640
-
641
- return {
642
- html: htmlCount,
643
- label: labelCount,
644
- liquid: liquidCount,
645
- total: allIssues.length,
646
- };
647
- };
648
-
649
- const issueCounts = calculateIssueCounts();
650
- const isContentEmpty = !currentContent || currentContent.trim() === '';
651
-
652
- const newState = {
653
- isContentEmpty,
654
- issueCounts,
655
- validationComplete: true,
656
- hasErrors: issueCounts.total > 0,
657
- };
658
220
 
659
- // Always notify parent when API errors change (they might not trigger validation state change)
660
- const lastState = lastSentValidationStateRef.current;
661
- const hasChanged = !lastState
662
- || lastState.issueCounts?.total !== newState.issueCounts?.total
663
- || lastState.issueCounts?.html !== newState.issueCounts?.html
664
- || lastState.issueCounts?.label !== newState.issueCounts?.label
665
- || lastState.issueCounts?.liquid !== newState.issueCounts?.liquid;
666
-
667
- if (hasChanged) {
668
- lastSentValidationStateRef.current = newState;
669
- onValidationChangeRef.current(newState);
670
- }
671
- }, [apiValidationErrors, validation, currentContent, onValidationChangeRef]); // Trigger when API errors change
672
-
673
- // Handle label insertion at cursor position
674
- // Note: This is called for notification purposes only when tag is inserted via CodeEditorPane
675
- // The actual insertion happens in CodeEditorPane.handleTagSelect
676
- const handleLabelInsert = useCallback((label, position) => {
677
- // If position is explicitly null, it means the editor wasn't ready when tag was selected
678
- // In this case, CodeEditorPane couldn't insert the tag, so we should try here
679
- if (position === null) {
680
- // With injectIntl({ forwardRef: true }), ref points directly to CodeEditorPane
681
- const activeEditorRef = getActiveEditorRef();
682
- const editor = activeEditorRef.current;
683
-
684
- if (!editor) {
685
- CapNotification.warning({
686
- message: intl.formatMessage(messages.labelInsertError),
687
- description: intl.formatMessage(messages.editorNotReady),
688
- duration: 3,
689
- });
690
- return;
691
- }
692
-
693
- // Check if the required methods exist
694
- if (typeof editor?.insertText !== 'function') {
695
- CapNotification.error({
696
- message: intl.formatMessage(messages.labelInsertError),
697
- description: intl.formatMessage(messages.editorMethodNotAvailable),
698
- duration: 4,
699
- });
700
- return;
701
- }
702
-
703
- try {
704
- // Get current cursor position
705
- const cursor = typeof editor?.getCursor === 'function' ? editor.getCursor() : 0;
221
+ try {
222
+ // Get current cursor position or use provided position
223
+ const cursor = position !== undefined
224
+ ? position
225
+ : (typeof editor?.getCursor === 'function' ? editor.getCursor() : 0);
706
226
 
707
- // Insert label at cursor position
708
- editor.insertText(label, cursor);
227
+ // Insert label at cursor position
228
+ editor.insertText(label, cursor);
709
229
 
710
- // Focus the editor if focus method is available
711
- editor?.focus?.();
230
+ // Focus the editor if focus method is available
231
+ editor?.focus?.();
712
232
 
713
- // Show success notification
714
- CapNotification.success({
715
- message: intl.formatMessage(messages.labelInserted),
716
- description: intl.formatMessage(messages.labelInsertedDescription, { label }),
717
- duration: 2,
718
- });
719
- } catch (error) {
720
- CapNotification.error({
721
- message: intl.formatMessage(messages.labelInsertError),
722
- description: error.message,
723
- duration: 4,
724
- });
725
- }
726
- } else {
727
- // Tag was already inserted by CodeEditorPane (position is a valid number)
728
- // Just show success notification - no need to access editor
233
+ // Show success notification
729
234
  CapNotification.success({
730
235
  message: intl.formatMessage(messages.labelInserted),
731
236
  description: intl.formatMessage(messages.labelInsertedDescription, { label }),
732
- duration: 2,
237
+ duration: 2
238
+ });
239
+ } catch (error) {
240
+ CapNotification.error({
241
+ message: intl.formatMessage(messages.labelInsertError),
242
+ description: error.message,
243
+ duration: 4
733
244
  });
734
245
  }
735
246
  }, [intl, getActiveEditorRef]);
@@ -748,13 +259,13 @@ const HTMLEditor = forwardRef(({
748
259
 
749
260
  CapNotification.success({
750
261
  message: intl.formatMessage(messages.contentSaved),
751
- duration: 2,
262
+ duration: 2
752
263
  });
753
264
  } catch (error) {
754
265
  CapNotification.error({
755
266
  message: intl.formatMessage(messages.saveError),
756
267
  description: error.message,
757
- duration: 4,
268
+ duration: 4
758
269
  });
759
270
  }
760
271
  }, [content, onSave, intl, markAsSaved]);
@@ -765,27 +276,21 @@ const HTMLEditor = forwardRef(({
765
276
  const editorInstance = activeEditorRef.current;
766
277
  const { line, column = 1 } = error || {};
767
278
 
768
- // Notify parent that user acknowledged errors by clicking redirection icon
769
- // This enables the buttons even if we can't navigate to a specific line
770
- if (onErrorAcknowledged) {
771
- onErrorAcknowledged();
772
- }
773
-
774
- if (editorInstance) {
279
+ if (editorInstance && line) {
775
280
  try {
776
- // If line number exists, navigate to it; otherwise just focus the editor
777
- if (line && editorInstance?.navigateToLine) {
281
+ // Access the CodeMirror view through the exposed ref methods
282
+ if (editorInstance?.navigateToLine) {
778
283
  editorInstance.navigateToLine(line, column);
779
284
  } else {
780
- // For API errors without line numbers, just focus the editor
781
285
  editorInstance?.focus?.();
286
+ // Fallback: just focus the editor if navigation isn't available
782
287
  }
783
288
  } catch (err) {
784
289
  // Fallback: just focus the editor
785
290
  editorInstance?.focus?.();
786
291
  }
787
292
  }
788
- }, [getActiveEditorRef, onErrorAcknowledged]);
293
+ }, [getActiveEditorRef]);
789
294
 
790
295
  // Handle fullscreen modal
791
296
  const handleOpenFullscreen = useCallback(() => {
@@ -802,7 +307,6 @@ const HTMLEditor = forwardRef(({
802
307
  content,
803
308
  layout,
804
309
  validation,
805
- isLiquidEnabled,
806
310
  editorRef: getActiveEditorRef(),
807
311
  handleLabelInsert,
808
312
  handleSave,
@@ -815,14 +319,13 @@ const HTMLEditor = forwardRef(({
815
319
  switchDevice,
816
320
  toggleContentSync,
817
321
  getDeviceContent,
818
- layoutType,
819
- }),
322
+ layoutType
323
+ })
820
324
  }), [
821
325
  variant,
822
326
  content,
823
327
  layout,
824
328
  validation,
825
- isLiquidEnabled,
826
329
  getActiveEditorRef,
827
330
  handleLabelInsert,
828
331
  handleSave,
@@ -833,7 +336,7 @@ const HTMLEditor = forwardRef(({
833
336
  switchDevice,
834
337
  toggleContentSync,
835
338
  getDeviceContent,
836
- layoutType,
339
+ layoutType
837
340
  ]);
838
341
 
839
342
  // Loading state
@@ -845,14 +348,9 @@ const HTMLEditor = forwardRef(({
845
348
  );
846
349
  }
847
350
 
848
- // Add library-mode class when not in full mode
849
- // Note: isFullMode defaults to true, so library mode is when isFullMode === false
850
- const isLibraryMode = isFullMode === false;
851
- const editorClassName = `html-editor html-editor--${variant} ${isLibraryMode ? 'html-editor--library-mode' : ''} ${className}`.trim();
852
-
853
351
  return (
854
352
  <EditorProvider value={contextValue}>
855
- <div className={editorClassName} {...props}>
353
+ <div className={`html-editor html-editor--${variant} ${className}`} {...props}>
856
354
  {/* Editor Toolbar - Conditional based on variant */}
857
355
  {variant === HTML_EDITOR_VARIANTS.EMAIL ? (
858
356
  <EditorToolbar
@@ -889,23 +387,19 @@ const HTMLEditor = forwardRef(({
889
387
  ref={mainEditorRef}
890
388
  readOnly={readOnly}
891
389
  onLabelInsert={handleLabelInsert}
892
- onErrorClick={handleValidationErrorClick}
893
- tags={tags}
894
- injectedTags={injectedTags}
895
- location={location}
896
- eventContextTags={eventContextTags}
897
- selectedOfferDetails={selectedOfferDetails}
898
- channel={channel}
899
- userLocale={userLocale}
900
- moduleFilterEnabled={moduleFilterEnabled}
901
- onTagContextChange={onTagContextChange}
902
- onTagSelect={onTagSelect}
903
- onContextChange={handleContextChange}
904
390
  />
905
391
 
906
392
  {/* Preview Pane */}
907
393
  <PreviewPane />
908
394
  </SplitContainer>
395
+
396
+ {/* Validation Display - Full Width Below Split Container */}
397
+ <ValidationErrorDisplay
398
+ validation={validation}
399
+ onErrorClick={handleValidationErrorClick}
400
+ variant={variant}
401
+ className="html-editor-validation"
402
+ />
909
403
  </CapRow>
910
404
 
911
405
  {/* Fullscreen Modal */}
@@ -917,17 +411,17 @@ const HTMLEditor = forwardRef(({
917
411
  maskClosable={false}
918
412
  centered
919
413
  closable={false}
920
- width="90vw"
414
+ width={"90vw"}
921
415
  className="html-editor-fullscreen-modal"
922
416
  >
923
- <CapRow className={`html-editor-fullscreen html-editor-fullscreen--${variant} ${isLibraryMode ? 'html-editor-fullscreen--library-mode' : ''}`}>
417
+ <CapRow className="html-editor-fullscreen">
924
418
  {/* Editor Toolbar - Conditional based on variant */}
925
419
  {variant === HTML_EDITOR_VARIANTS.EMAIL ? (
926
420
  <EditorToolbar
927
- showFullscreenButton // Show fullscreen button in modal to allow closing
421
+ showFullscreenButton={true} // Show fullscreen button in modal to allow closing
928
422
  onLabelInsert={handleLabelInsert}
929
423
  onSave={handleSave}
930
- isFullscreenMode
424
+ isFullscreenMode={true}
931
425
  onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
932
426
  />
933
427
  ) : (
@@ -940,10 +434,10 @@ const HTMLEditor = forwardRef(({
940
434
  onKeepContentSameChange={toggleContentSync}
941
435
  />
942
436
  <EditorToolbar
943
- showFullscreenButton // Show fullscreen button in modal to allow closing
437
+ showFullscreenButton={true} // Show fullscreen button in modal to allow closing
944
438
  onLabelInsert={handleLabelInsert}
945
439
  onSave={handleSave}
946
- isFullscreenMode
440
+ isFullscreenMode={true}
947
441
  onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
948
442
  variant={variant}
949
443
  showTitle={false} // Hide title in InApp variant
@@ -958,30 +452,28 @@ const HTMLEditor = forwardRef(({
958
452
  <CodeEditorPane
959
453
  ref={modalEditorRef}
960
454
  readOnly={readOnly}
961
- isFullscreenMode
455
+ isFullscreenMode={true}
962
456
  onLabelInsert={handleLabelInsert}
963
- onErrorClick={handleValidationErrorClick}
964
- tags={tags}
965
- injectedTags={injectedTags}
966
- location={location}
967
- eventContextTags={eventContextTags}
968
- selectedOfferDetails={selectedOfferDetails}
969
- channel={channel}
970
- userLocale={userLocale}
971
- moduleFilterEnabled={moduleFilterEnabled}
972
- onTagContextChange={onTagContextChange}
973
457
  />
974
458
 
975
- {/* Preview Pane */}
976
- <PreviewPane isFullscreenMode isModalContext />
977
- </SplitContainer>
459
+ {/* Preview Pane */}
460
+ <PreviewPane isFullscreenMode={true} isModalContext={true} />
461
+ </SplitContainer>
462
+
463
+ {/* Validation Display in Modal */}
464
+ <ValidationErrorDisplay
465
+ validation={validation}
466
+ onErrorClick={handleValidationErrorClick}
467
+ variant={variant}
468
+ className="html-editor-validation"
469
+ />
978
470
  </CapRow>
979
471
  </CapRow>
980
472
  </CapModal>
981
473
  </div>
982
474
  </EditorProvider>
983
475
  );
984
- });
476
+ };
985
477
 
986
478
  HTMLEditor.propTypes = {
987
479
  intl: intlShape.isRequired,
@@ -989,7 +481,7 @@ HTMLEditor.propTypes = {
989
481
  layoutType: PropTypes.string, // Layout type for InApp variant
990
482
  initialContent: PropTypes.oneOfType([
991
483
  PropTypes.string,
992
- PropTypes.objectOf(PropTypes.string), // Per-device content for INAPP variant
484
+ PropTypes.objectOf(PropTypes.string) // Per-device content for INAPP variant
993
485
  ]),
994
486
  onSave: PropTypes.func,
995
487
  onContentChange: PropTypes.func,
@@ -997,33 +489,11 @@ HTMLEditor.propTypes = {
997
489
  readOnly: PropTypes.bool,
998
490
  showFullscreenButton: PropTypes.bool,
999
491
  autoSave: PropTypes.bool,
1000
- autoSaveInterval: PropTypes.number,
1001
- // Tag-related props - tags are fetched and managed by parent component
1002
- tags: PropTypes.array,
1003
- injectedTags: PropTypes.object,
1004
- location: PropTypes.object,
1005
- eventContextTags: PropTypes.array,
1006
- selectedOfferDetails: PropTypes.array,
1007
- channel: PropTypes.string,
1008
- userLocale: PropTypes.string,
1009
- moduleFilterEnabled: PropTypes.bool,
1010
- onTagContextChange: PropTypes.func, // Required - parent must handle tag fetching
1011
- onTagSelect: PropTypes.func,
1012
- onContextChange: PropTypes.func, // Deprecated: use globalActions instead
1013
- globalActions: PropTypes.object,
1014
- isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
1015
- isFullMode: PropTypes.bool, // Full mode vs library mode
1016
- onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
1017
- onValidationChange: PropTypes.func, // Callback when validation state changes
1018
- apiValidationErrors: PropTypes.shape({
1019
- liquidErrors: PropTypes.arrayOf(PropTypes.string),
1020
- standardErrors: PropTypes.arrayOf(PropTypes.string),
1021
- }), // API validation errors from validateLiquidTemplateContent
492
+ autoSaveInterval: PropTypes.number
1022
493
  };
1023
494
 
1024
495
  HTMLEditor.defaultProps = {
1025
496
  variant: HTML_EDITOR_VARIANTS.EMAIL, // Default to email variant
1026
- layoutType: null,
1027
497
  initialContent: null, // Will use default from useEditorContent hook
1028
498
  onSave: null,
1029
499
  onContentChange: null,
@@ -1031,26 +501,8 @@ HTMLEditor.defaultProps = {
1031
501
  readOnly: false,
1032
502
  showFullscreenButton: true,
1033
503
  autoSave: true,
1034
- autoSaveInterval: 30000,
1035
- // Tag-related defaults - tags are fetched and managed by parent component
1036
- tags: [],
1037
- injectedTags: {},
1038
- location: null,
1039
- eventContextTags: [],
1040
- selectedOfferDetails: [],
1041
- channel: null,
1042
- userLocale: 'en',
1043
- moduleFilterEnabled: true,
1044
- onTagContextChange: null, // Parent component should provide this
1045
- onTagSelect: null,
1046
- onContextChange: null,
1047
- globalActions: null, // Redux actions for API calls
1048
- isLiquidEnabled: false,
1049
- isFullMode: true, // Default to full mode
1050
- onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
1051
- onValidationChange: null, // Callback when validation state changes
1052
- apiValidationErrors: null, // API validation errors from validateLiquidTemplateContent
504
+ autoSaveInterval: 30000
1053
505
  };
1054
506
 
1055
- // Export with injectIntl - HTMLEditor now uses forwardRef internally
1056
- export default injectIntl(HTMLEditor);
507
+ // Export with forwardRef to allow direct access to CodeEditorPane via ref
508
+ export default injectIntl(HTMLEditor, { forwardRef: true });