@capillarytech/creatives-library 8.0.250-alpha.2 → 8.0.251

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 (276) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +1 -3
  4. package/initialReducer.js +0 -2
  5. package/package.json +1 -1
  6. package/services/api.js +0 -15
  7. package/services/tests/api.test.js +0 -34
  8. package/translations/en.json +3 -4
  9. package/utils/common.js +0 -11
  10. package/utils/commonUtils.js +5 -28
  11. package/utils/tests/commonUtil.test.js +0 -224
  12. package/utils/tests/transformerUtils.test.js +0 -297
  13. package/utils/transformTemplateConfig.js +10 -0
  14. package/utils/transformerUtils.js +0 -40
  15. package/v2Components/CapDeviceContent/index.js +56 -61
  16. package/v2Components/CapImageUpload/constants.js +0 -2
  17. package/v2Components/CapImageUpload/index.js +16 -65
  18. package/v2Components/CapImageUpload/index.scss +1 -4
  19. package/v2Components/CapImageUpload/messages.js +1 -5
  20. package/v2Components/CapTagList/index.js +1 -6
  21. package/v2Components/CapTagListWithInput/index.js +1 -5
  22. package/v2Components/CapTagListWithInput/messages.js +1 -1
  23. package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
  24. package/v2Components/ErrorInfoNote/index.js +72 -455
  25. package/v2Components/ErrorInfoNote/messages.js +6 -36
  26. package/v2Components/ErrorInfoNote/style.scss +4 -280
  27. package/v2Components/FormBuilder/tests/index.test.js +4 -13
  28. package/v2Components/HtmlEditor/HTMLEditor.js +94 -547
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1358
  30. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +16 -27
  31. package/v2Components/HtmlEditor/_htmlEditor.scss +45 -108
  32. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  33. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -22
  34. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +140 -146
  35. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
  36. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  37. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +0 -9
  38. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  39. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -22
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
  43. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  44. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
  45. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +6 -3
  46. package/v2Components/HtmlEditor/components/PreviewPane/index.js +34 -24
  47. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  48. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
  49. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -49
  50. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +34 -50
  51. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +41 -70
  52. package/v2Components/HtmlEditor/constants.js +20 -42
  53. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
  54. package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
  55. package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
  56. package/v2Components/HtmlEditor/hooks/useValidation.js +53 -189
  57. package/v2Components/HtmlEditor/index.js +1 -1
  58. package/v2Components/HtmlEditor/messages.js +85 -95
  59. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +45 -94
  60. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +0 -134
  61. package/v2Components/HtmlEditor/utils/contentSanitizer.js +41 -40
  62. package/v2Components/HtmlEditor/utils/htmlValidator.js +72 -71
  63. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +102 -134
  64. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
  65. package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -66
  66. package/v2Components/MobilePushPreviewV2/index.js +7 -32
  67. package/v2Components/TemplatePreview/_templatePreview.scss +24 -55
  68. package/v2Components/TemplatePreview/index.js +32 -47
  69. package/v2Components/TemplatePreview/messages.js +0 -4
  70. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +0 -1
  71. package/v2Containers/App/constants.js +0 -5
  72. package/v2Containers/BeeEditor/index.js +90 -172
  73. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +3 -4
  74. package/v2Containers/CreativesContainer/SlideBoxContent.js +53 -184
  75. package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -163
  76. package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -3
  77. package/v2Containers/CreativesContainer/constants.js +0 -4
  78. package/v2Containers/CreativesContainer/index.js +46 -407
  79. package/v2Containers/CreativesContainer/messages.js +0 -12
  80. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +0 -210
  81. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +2 -11
  82. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +50 -342
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -106
  84. package/v2Containers/Email/actions.js +0 -7
  85. package/v2Containers/Email/constants.js +1 -5
  86. package/v2Containers/Email/index.js +29 -234
  87. package/v2Containers/Email/messages.js +0 -32
  88. package/v2Containers/Email/reducer.js +1 -12
  89. package/v2Containers/Email/sagas.js +7 -61
  90. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
  91. package/v2Containers/Email/tests/reducer.test.js +0 -46
  92. package/v2Containers/Email/tests/sagas.test.js +29 -320
  93. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +19 -207
  94. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
  95. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
  96. package/v2Containers/EmailWrapper/constants.js +0 -2
  97. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +77 -629
  98. package/v2Containers/EmailWrapper/index.js +23 -103
  99. package/v2Containers/EmailWrapper/messages.js +1 -61
  100. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
  101. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -594
  102. package/v2Containers/InApp/actions.js +0 -7
  103. package/v2Containers/InApp/constants.js +4 -20
  104. package/v2Containers/InApp/index.js +359 -802
  105. package/v2Containers/InApp/index.scss +3 -4
  106. package/v2Containers/InApp/messages.js +3 -7
  107. package/v2Containers/InApp/reducer.js +3 -21
  108. package/v2Containers/InApp/sagas.js +9 -29
  109. package/v2Containers/InApp/selectors.js +5 -25
  110. package/v2Containers/InApp/tests/index.test.js +50 -154
  111. package/v2Containers/InApp/tests/reducer.test.js +0 -34
  112. package/v2Containers/InApp/tests/sagas.test.js +9 -61
  113. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +9 -15
  114. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +6 -10
  115. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +75 -102
  116. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +54 -81
  117. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +174 -244
  118. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +12 -16
  119. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +36 -52
  120. package/v2Containers/TagList/index.js +19 -62
  121. package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
  122. package/v2Containers/Templates/_templates.scss +1 -265
  123. package/v2Containers/Templates/actions.js +1 -2
  124. package/v2Containers/Templates/constants.js +0 -1
  125. package/v2Containers/Templates/index.js +38 -363
  126. package/v2Containers/Templates/messages.js +0 -28
  127. package/v2Containers/Templates/reducer.js +0 -4
  128. package/v2Containers/Templates/tests/index.test.js +0 -10
  129. package/v2Containers/TemplatesV2/index.js +7 -15
  130. package/v2Containers/TemplatesV2/messages.js +0 -4
  131. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +486 -682
  132. package/utils/imageUrlUpload.js +0 -141
  133. package/v2Components/CapImageUrlUpload/constants.js +0 -26
  134. package/v2Components/CapImageUrlUpload/index.js +0 -365
  135. package/v2Components/CapImageUrlUpload/index.scss +0 -35
  136. package/v2Components/CapImageUrlUpload/messages.js +0 -47
  137. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -874
  138. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +0 -6
  139. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -254
  140. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -364
  141. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
  142. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +0 -795
  143. package/v2Components/HtmlEditor/utils/validationConstants.js +0 -40
  144. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +0 -14
  145. package/v2Containers/BeePopupEditor/constants.js +0 -10
  146. package/v2Containers/BeePopupEditor/index.js +0 -194
  147. package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
  148. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1285
  149. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -1870
  150. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +0 -520
  151. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +0 -643
  152. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
  153. package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
  154. package/v2Containers/InApp/tests/selectors.test.js +0 -612
  155. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -151
  156. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
  157. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -23
  158. package/v2Containers/InAppWrapper/constants.js +0 -16
  159. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
  160. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
  161. package/v2Containers/InAppWrapper/index.js +0 -148
  162. package/v2Containers/InAppWrapper/messages.js +0 -49
  163. package/v2Containers/InappAdvance/index.js +0 -1099
  164. package/v2Containers/InappAdvance/index.scss +0 -10
  165. package/v2Containers/InappAdvance/tests/index.test.js +0 -448
  166. package/v2Containers/WebPush/Create/components/BrandIconSection.js +0 -108
  167. package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -172
  168. package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
  169. package/v2Containers/WebPush/Create/components/ButtonList.js +0 -107
  170. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +0 -160
  171. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +0 -476
  172. package/v2Containers/WebPush/Create/components/FormActions.js +0 -54
  173. package/v2Containers/WebPush/Create/components/FormActions.test.js +0 -163
  174. package/v2Containers/WebPush/Create/components/MediaSection.js +0 -143
  175. package/v2Containers/WebPush/Create/components/MediaSection.test.js +0 -341
  176. package/v2Containers/WebPush/Create/components/MessageSection.js +0 -103
  177. package/v2Containers/WebPush/Create/components/MessageSection.test.js +0 -268
  178. package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +0 -87
  179. package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +0 -210
  180. package/v2Containers/WebPush/Create/components/TemplateNameSection.js +0 -54
  181. package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +0 -143
  182. package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +0 -82
  183. package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +0 -16
  184. package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +0 -41
  185. package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +0 -54
  186. package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +0 -37
  187. package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +0 -21
  188. package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
  189. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
  190. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
  191. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -515
  192. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
  193. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
  194. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -46
  195. package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +0 -150
  196. package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +0 -406
  197. package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +0 -30
  198. package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +0 -151
  199. package/v2Containers/WebPush/Create/hooks/useImageUpload.js +0 -104
  200. package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +0 -538
  201. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -122
  202. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -633
  203. package/v2Containers/WebPush/Create/index.js +0 -1148
  204. package/v2Containers/WebPush/Create/index.scss +0 -134
  205. package/v2Containers/WebPush/Create/messages.js +0 -203
  206. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -228
  207. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -294
  208. package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -90
  209. package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -305
  210. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -23
  211. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -155
  212. package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
  213. package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
  214. package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +0 -9
  215. package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +0 -9
  216. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  217. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  218. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
  219. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
  220. package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +0 -9
  221. package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +0 -9
  222. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
  223. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
  224. package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +0 -9
  225. package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +0 -9
  226. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -47
  227. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -141
  228. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
  229. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -68
  230. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -61
  231. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -99
  232. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -733
  233. package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +0 -571
  234. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -81
  235. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +0 -81
  236. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -50
  237. package/v2Containers/WebPush/Create/preview/constants.js +0 -637
  238. package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -79
  239. package/v2Containers/WebPush/Create/preview/preview.scss +0 -351
  240. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -370
  241. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
  242. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
  243. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
  244. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -47
  245. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
  246. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
  247. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
  248. package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -207
  249. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -153
  250. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
  251. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -101
  252. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -229
  253. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
  254. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1081
  255. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
  256. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -1327
  257. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -131
  258. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -112
  259. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
  260. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -129
  261. package/v2Containers/WebPush/Create/utils/payloadBuilder.js +0 -96
  262. package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +0 -396
  263. package/v2Containers/WebPush/Create/utils/previewUtils.js +0 -89
  264. package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -115
  265. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
  266. package/v2Containers/WebPush/Create/utils/validation.js +0 -75
  267. package/v2Containers/WebPush/Create/utils/validation.test.js +0 -283
  268. package/v2Containers/WebPush/actions.js +0 -60
  269. package/v2Containers/WebPush/constants.js +0 -132
  270. package/v2Containers/WebPush/index.js +0 -2
  271. package/v2Containers/WebPush/reducer.js +0 -104
  272. package/v2Containers/WebPush/sagas.js +0 -119
  273. package/v2Containers/WebPush/selectors.js +0 -65
  274. package/v2Containers/WebPush/tests/reducer.test.js +0 -863
  275. package/v2Containers/WebPush/tests/sagas.test.js +0 -566
  276. package/v2Containers/WebPush/tests/selectors.test.js +0 -960
@@ -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,10 +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';
45
- import { ISSUE_SOURCES, LABEL_ISSUE_PATTERNS } from './utils/validationConstants';
43
+ import { HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT } from './constants';
46
44
 
47
45
  // Styles
48
46
  import './_htmlEditor.scss';
@@ -51,7 +49,7 @@ import './components/FullscreenModal/_fullscreenModal.scss';
51
49
  // Messages
52
50
  import messages from './messages';
53
51
 
54
- const HTMLEditor = forwardRef(({
52
+ const HTMLEditor = ({
55
53
  intl,
56
54
  variant = HTML_EDITOR_VARIANTS.EMAIL, // New prop: 'email' or 'inapp'
57
55
  layoutType, // Layout type for InApp variant
@@ -63,33 +61,17 @@ const HTMLEditor = forwardRef(({
63
61
  showFullscreenButton = true,
64
62
  autoSave = true,
65
63
  autoSaveInterval = 30000, // 30 seconds
66
- // Tag-related props - tags are fetched and managed by parent component (EmailHTMLEditor, INAPP, etc.)
67
- tags = [],
68
- injectedTags = {},
69
- location,
70
- eventContextTags = [],
71
- selectedOfferDetails = [],
72
- channel,
73
- userLocale = 'en',
74
- moduleFilterEnabled = true,
75
- onTagContextChange, // Parent component handles tag fetching
76
- onTagSelect = null,
77
- onContextChange = null,
78
- globalActions = null,
79
- isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
80
- isFullMode = true, // Full mode vs library mode - controls layout and visibility
81
- onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
82
- onValidationChange = null, // Callback when validation state changes (for parent to track errors)
83
- apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent { liquidErrors: [], standardErrors: [] }
84
64
  ...props
85
- }, ref) => {
65
+ }) => {
86
66
  // Separate refs for main and modal editors to avoid conflicts
87
67
  const mainEditorRef = useRef(null);
88
68
  const modalEditorRef = useRef(null);
89
69
  const [isFullscreenModalOpen, setIsFullscreenModalOpen] = useState(false);
90
70
 
91
71
  // Get the currently active editor ref based on fullscreen state
92
- const getActiveEditorRef = useCallback(() => isFullscreenModalOpen ? modalEditorRef : mainEditorRef, [isFullscreenModalOpen]);
72
+ const getActiveEditorRef = useCallback(() => {
73
+ return isFullscreenModalOpen ? modalEditorRef : mainEditorRef;
74
+ }, [isFullscreenModalOpen]);
93
75
 
94
76
  // Initialize custom hooks for state management - always call both hooks to follow Rules of Hooks
95
77
  const isEmailVariant = variant === HTML_EDITOR_VARIANTS.EMAIL;
@@ -98,7 +80,7 @@ const HTMLEditor = forwardRef(({
98
80
  autoSave: isEmailVariant ? autoSave : false,
99
81
  autoSaveInterval,
100
82
  onSave: isEmailVariant ? onSave : null,
101
- onChange: isEmailVariant ? onContentChange : null,
83
+ onChange: isEmailVariant ? onContentChange : null
102
84
  };
103
85
 
104
86
  const emailContent = useEditorContent(
@@ -115,7 +97,7 @@ const HTMLEditor = forwardRef(({
115
97
  // Convert string content to device-specific format
116
98
  inAppInitialContent = {
117
99
  [DEVICE_TYPES.ANDROID]: initialContent,
118
- [DEVICE_TYPES.IOS]: initialContent,
100
+ [DEVICE_TYPES.IOS]: initialContent
119
101
  };
120
102
  } else {
121
103
  // Use provided device-specific content
@@ -127,7 +109,7 @@ const HTMLEditor = forwardRef(({
127
109
  autoSave: isInAppVariant ? autoSave : false,
128
110
  autoSaveInterval,
129
111
  onSave: isInAppVariant ? onSave : null,
130
- onChange: isInAppVariant ? onContentChange : null,
112
+ onChange: isInAppVariant ? onContentChange : null
131
113
  };
132
114
 
133
115
  const inAppContent = useInAppContent(inAppInitialContent, inAppOptions);
@@ -135,64 +117,6 @@ const HTMLEditor = forwardRef(({
135
117
  // Use appropriate content hook based on variant
136
118
  const content = variant === HTML_EDITOR_VARIANTS.EMAIL ? emailContent : inAppContent;
137
119
 
138
- // Update content when initialContent prop changes (for edit mode)
139
- // This ensures the editor updates when template data loads
140
- useEffect(() => {
141
- if (isEmailVariant && emailContent && initialContent !== undefined && initialContent !== null) {
142
- // Only update if content is different to avoid unnecessary updates
143
- if (emailContent.content !== initialContent) {
144
- emailContent.updateContent(initialContent, true); // immediate update
145
- }
146
- } else if (isInAppVariant && inAppContent && initialContent !== undefined && initialContent !== null) {
147
- // Handle InApp variant updates
148
- const contentToUpdate = typeof initialContent === 'string'
149
- ? { [DEVICE_TYPES.ANDROID]: initialContent, [DEVICE_TYPES.IOS]: initialContent }
150
- : initialContent;
151
- if (inAppContent.updateContent) {
152
- const currentContent = inAppContent.getDeviceContent?.(inAppContent.activeDevice);
153
- const newContent = contentToUpdate[inAppContent.activeDevice] || contentToUpdate[DEVICE_TYPES.ANDROID] || '';
154
- if (currentContent !== newContent) {
155
- inAppContent.updateContent(newContent, true);
156
- }
157
- }
158
- }
159
- }, [initialContent, isEmailVariant, isInAppVariant]);
160
- // Handle context change for tag API calls
161
- // If variant is INAPP, use SMS layout; otherwise use the channel (EMAIL)
162
- const handleContextChange = useCallback((contextData) => {
163
- // If onContextChange is provided, use it instead of making our own API call
164
- // This prevents duplicate API calls when parent component handles tag fetching
165
- if (onContextChange) {
166
- onContextChange(contextData);
167
- return;
168
- }
169
-
170
- // Only make API call if onContextChange is not provided and globalActions is available
171
- if (!globalActions || !location) {
172
- return;
173
- }
174
-
175
- const { type } = location.query || {};
176
- const tempData = (contextData || '').toLowerCase();
177
- const isEmbedded = type === EMBEDDED;
178
- const embedded = isEmbedded ? type : FULL;
179
- const context = tempData === ALL ? DEFAULT : tempData;
180
-
181
- // Determine layout: INAPP variant uses SMS, EMAIL variant uses EMAIL
182
- const layout = variant === HTML_EDITOR_VARIANTS.INAPP ? SMS : EMAIL;
183
-
184
- const query = {
185
- layout,
186
- type: TAG,
187
- context,
188
- embedded,
189
- };
190
-
191
- // Call the API via Redux action - this will trigger the saga which calls Api.fetchSchemaForEntity
192
- // The API endpoint will be: /meta/TAG?query={...}
193
- globalActions.fetchSchemaForEntity(query);
194
- }, [variant, globalActions, location, onContextChange]);
195
-
196
120
  // Destructure content properties for cleaner access throughout component
197
121
  const {
198
122
  activeDevice,
@@ -200,14 +124,14 @@ const HTMLEditor = forwardRef(({
200
124
  switchDevice,
201
125
  toggleContentSync,
202
126
  getDeviceContent,
203
- markAsSaved,
127
+ markAsSaved
204
128
  } = content || {};
205
129
 
206
130
  const layout = useLayoutState({
207
131
  splitSizes: [50, 50],
208
132
  viewMode: 'desktop',
209
133
  mobileWidth: 375,
210
- isFullscreen: false,
134
+ isFullscreen: false
211
135
  });
212
136
 
213
137
  // Get current content for validation based on variant
@@ -234,7 +158,7 @@ const HTMLEditor = forwardRef(({
234
158
  'sanitizer.productionValidHtml': messages.sanitizer.productionValidHtml,
235
159
  'sanitizer.productionSanitized': messages.sanitizer.productionSanitized,
236
160
  'sanitizer.productionInlineCss': messages.sanitizer.productionInlineCss,
237
- 'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent,
161
+ 'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent
238
162
  };
239
163
 
240
164
  const messageObj = messageMap[messageKey];
@@ -255,7 +179,7 @@ const HTMLEditor = forwardRef(({
255
179
  'validator.largeImageDetected': messages.validator.largeImageDetected,
256
180
  'validator.unclosedCssRule': messages.validator.unclosedCssRule,
257
181
  'validator.emptyCssRule': messages.validator.emptyCssRule,
258
- 'validator.cssValidationFailed': messages.validator.cssValidationFailed,
182
+ 'validator.cssValidationFailed': messages.validator.cssValidationFailed
259
183
  };
260
184
 
261
185
  const messageObj = messageMap[messageKey];
@@ -266,375 +190,57 @@ const HTMLEditor = forwardRef(({
266
190
  enableRealTime: true,
267
191
  debounceMs: 500,
268
192
  enableSanitization: true,
269
- securityLevel: 'standard',
270
- apiValidationErrors, // Pass API validation errors to merge with client-side validation
193
+ securityLevel: 'standard'
271
194
  }, formatSanitizerMessage, formatValidatorMessage);
272
195
 
273
- // Expose validation and content state via ref
274
- useImperativeHandle(ref, () => ({
275
- getValidation: () => validation,
276
- getContent: () => currentContent,
277
- isContentEmpty: () => !currentContent || currentContent.trim() === '',
278
- getIssueCounts: () => {
279
- // Check if validation is ready and has getAllIssues method
280
- if (!validation || typeof validation.getAllIssues !== 'function') {
281
- return {
282
- html: 0, label: 0, liquid: 0, total: 0,
283
- };
284
- }
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
- }
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;
312
201
 
313
- // Default to HTML issues
314
- htmlCount++;
202
+ if (!editor) {
203
+ CapNotification.warning({
204
+ message: intl.formatMessage(messages.labelInsertError),
205
+ description: intl.formatMessage(messages.editorNotReady),
206
+ duration: 3
315
207
  });
316
-
317
- return {
318
- html: htmlCount,
319
- label: labelCount,
320
- liquid: liquidCount,
321
- total: allIssues.length,
322
- };
323
- },
324
- getValidationState: () => ({
325
- isValidating: validation?.isValidating || false,
326
- 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
- })(),
369
- })
370
- ,
371
- }), [validation, currentContent, apiValidationErrors]); // Include apiValidationErrors so ref methods return updated counts
372
-
373
- // Use ref to store callback to avoid infinite loops (callback in deps would cause re-runs)
374
- const onValidationChangeRef = useRef(onValidationChange);
375
- useEffect(() => {
376
- onValidationChangeRef.current = onValidationChange;
377
- }, [onValidationChange]);
378
-
379
- // Track last sent validation state to prevent duplicate updates
380
- const lastSentValidationStateRef = useRef(null);
381
-
382
- // Store validation ref to access current value without triggering re-renders
383
- const validationRef = useRef(validation);
384
- validationRef.current = validation;
385
-
386
- // Extract STABLE primitive values from validation for dependency array
387
- const isValidating = validation?.isValidating;
388
- const validationTotalErrors = validation?.summary?.totalErrors || 0;
389
- const validationTotalWarnings = validation?.summary?.totalWarnings || 0;
390
- const validationLastValidated = validation?.lastValidated?.getTime?.() || null;
391
- // Only Rule Group #1 (Input & Sanitization) blocks; use for UI gating (Done/Update/Preview/Test)
392
- const validationHasBlockingErrors = validation?.hasBlockingErrors || false;
393
-
394
- // Notify parent component when validation state changes
395
- useEffect(() => {
396
- if (!onValidationChangeRef.current) {
397
208
  return;
398
209
  }
399
210
 
400
- // Skip if still validating (wait for validation to complete)
401
- if (isValidating) {
402
- return;
403
- }
404
-
405
- // Calculate issue counts from validation using ref (avoid stale closure)
406
- const calculateIssueCounts = () => {
407
- const currentValidation = validationRef.current;
408
- if (!currentValidation || typeof currentValidation.getAllIssues !== 'function') {
409
- return {
410
- html: 0, label: 0, liquid: 0, total: 0,
411
- };
412
- }
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;
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
440
217
  });
441
-
442
- return {
443
- html: htmlCount,
444
- label: labelCount,
445
- liquid: liquidCount,
446
- total: allIssues.length,
447
- };
448
- };
449
-
450
- const issueCounts = calculateIssueCounts();
451
- const isContentEmpty = !currentContent || currentContent.trim() === '';
452
-
453
- // hasErrors = only Rule Group #1 (Input & Sanitization) – gates Done/Update/Preview/Test
454
- const newState = {
455
- isContentEmpty,
456
- issueCounts,
457
- validationComplete: true,
458
- hasErrors: validationRef.current?.hasBlockingErrors ?? false,
459
- };
460
-
461
- // Only call callback if state actually changed (prevent infinite loops)
462
- const lastState = lastSentValidationStateRef.current;
463
- const hasChanged = !lastState
464
- || lastState.isContentEmpty !== newState.isContentEmpty
465
- || lastState.validationComplete !== newState.validationComplete
466
- || lastState.hasErrors !== newState.hasErrors
467
- || 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;
471
-
472
- if (hasChanged) {
473
- lastSentValidationStateRef.current = newState;
474
- onValidationChangeRef.current(newState);
475
- }
476
- }, [isValidating, validationTotalErrors, validationTotalWarnings, validationLastValidated, validationHasBlockingErrors, currentContent, apiValidationErrors]);
477
-
478
- // Send initial state on mount to ensure parent has correct initial button state
479
- const hasInitializedRef = useRef(false);
480
- useEffect(() => {
481
- if (hasInitializedRef.current || !onValidationChangeRef.current) {
482
- return;
483
- }
484
- hasInitializedRef.current = true;
485
-
486
- // Send initial state with validationComplete=false to indicate validation pending
487
- const isContentEmpty = !currentContent || currentContent.trim() === '';
488
- onValidationChangeRef.current({
489
- isContentEmpty,
490
- issueCounts: {
491
- html: 0, label: 0, liquid: 0, total: 0,
492
- },
493
- validationComplete: false, // Validation hasn't run yet
494
- hasErrors: false,
495
- });
496
- }, [currentContent]); // Only depend on currentContent to run on initial content load
497
-
498
- // Force validation state recalculation when API validation errors change
499
- // This ensures that API errors are included in issue counts and displayed in ValidationErrorDisplay
500
- useEffect(() => {
501
- if (!onValidationChangeRef.current) {
502
- return;
503
- }
504
-
505
- // Skip if still validating (wait for validation to complete)
506
- if (validation?.isValidating) {
507
218
  return;
508
219
  }
509
220
 
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();
555
- const isContentEmpty = !currentContent || currentContent.trim() === '';
556
-
557
- const newState = {
558
- isContentEmpty,
559
- issueCounts,
560
- validationComplete: true,
561
- hasErrors: validation?.hasBlockingErrors ?? false,
562
- };
563
-
564
- const lastState = lastSentValidationStateRef.current;
565
- const hasChanged = !lastState
566
- || lastState.hasErrors !== newState.hasErrors
567
- || 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;
571
-
572
- if (hasChanged) {
573
- lastSentValidationStateRef.current = newState;
574
- onValidationChangeRef.current(newState);
575
- }
576
- }, [apiValidationErrors, validation, currentContent, onValidationChangeRef]);
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);
577
226
 
578
- // Handle label insertion at cursor position
579
- // Note: This is called for notification purposes only when tag is inserted via CodeEditorPane
580
- // The actual insertion happens in CodeEditorPane.handleTagSelect
581
- const handleLabelInsert = useCallback((label, position) => {
582
- // If position is explicitly null, it means the editor wasn't ready when tag was selected
583
- // In this case, CodeEditorPane couldn't insert the tag, so we should try here
584
- if (position === null) {
585
- // With injectIntl({ forwardRef: true }), ref points directly to CodeEditorPane
586
- const activeEditorRef = getActiveEditorRef();
587
- const editor = activeEditorRef.current;
588
-
589
- if (!editor) {
590
- CapNotification.warning({
591
- message: intl.formatMessage(messages.labelInsertError),
592
- description: intl.formatMessage(messages.editorNotReady),
593
- duration: 3,
594
- });
595
- return;
596
- }
227
+ // Insert label at cursor position
228
+ editor.insertText(label, cursor);
597
229
 
598
- // Check if the required methods exist
599
- if (typeof editor?.insertText !== 'function') {
600
- CapNotification.error({
601
- message: intl.formatMessage(messages.labelInsertError),
602
- description: intl.formatMessage(messages.editorMethodNotAvailable),
603
- duration: 4,
604
- });
605
- return;
606
- }
230
+ // Focus the editor if focus method is available
231
+ editor?.focus?.();
607
232
 
608
- try {
609
- // Get current cursor position
610
- const cursor = typeof editor?.getCursor === 'function' ? editor.getCursor() : 0;
611
-
612
- // Insert label at cursor position
613
- editor.insertText(label, cursor);
614
-
615
- // Focus the editor if focus method is available
616
- editor?.focus?.();
617
-
618
- // Show success notification
619
- CapNotification.success({
620
- message: intl.formatMessage(messages.labelInserted),
621
- description: intl.formatMessage(messages.labelInsertedDescription, { label }),
622
- duration: 2,
623
- });
624
- } catch (error) {
625
- CapNotification.error({
626
- message: intl.formatMessage(messages.labelInsertError),
627
- description: error.message,
628
- duration: 4,
629
- });
630
- }
631
- } else {
632
- // Tag was already inserted by CodeEditorPane (position is a valid number)
633
- // Just show success notification - no need to access editor
233
+ // Show success notification
634
234
  CapNotification.success({
635
235
  message: intl.formatMessage(messages.labelInserted),
636
236
  description: intl.formatMessage(messages.labelInsertedDescription, { label }),
637
- 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
638
244
  });
639
245
  }
640
246
  }, [intl, getActiveEditorRef]);
@@ -653,13 +259,13 @@ const HTMLEditor = forwardRef(({
653
259
 
654
260
  CapNotification.success({
655
261
  message: intl.formatMessage(messages.contentSaved),
656
- duration: 2,
262
+ duration: 2
657
263
  });
658
264
  } catch (error) {
659
265
  CapNotification.error({
660
266
  message: intl.formatMessage(messages.saveError),
661
267
  description: error.message,
662
- duration: 4,
268
+ duration: 4
663
269
  });
664
270
  }
665
271
  }, [content, onSave, intl, markAsSaved]);
@@ -670,27 +276,21 @@ const HTMLEditor = forwardRef(({
670
276
  const editorInstance = activeEditorRef.current;
671
277
  const { line, column = 1 } = error || {};
672
278
 
673
- // Notify parent that user acknowledged errors by clicking redirection icon
674
- // This enables the buttons even if we can't navigate to a specific line
675
- if (onErrorAcknowledged) {
676
- onErrorAcknowledged();
677
- }
678
-
679
- if (editorInstance) {
279
+ if (editorInstance && line) {
680
280
  try {
681
- // If line number exists, navigate to it; otherwise just focus the editor
682
- if (line && editorInstance?.navigateToLine) {
281
+ // Access the CodeMirror view through the exposed ref methods
282
+ if (editorInstance?.navigateToLine) {
683
283
  editorInstance.navigateToLine(line, column);
684
284
  } else {
685
- // For API errors without line numbers, just focus the editor
686
285
  editorInstance?.focus?.();
286
+ // Fallback: just focus the editor if navigation isn't available
687
287
  }
688
288
  } catch (err) {
689
289
  // Fallback: just focus the editor
690
290
  editorInstance?.focus?.();
691
291
  }
692
292
  }
693
- }, [getActiveEditorRef, onErrorAcknowledged]);
293
+ }, [getActiveEditorRef]);
694
294
 
695
295
  // Handle fullscreen modal
696
296
  const handleOpenFullscreen = useCallback(() => {
@@ -707,7 +307,6 @@ const HTMLEditor = forwardRef(({
707
307
  content,
708
308
  layout,
709
309
  validation,
710
- isLiquidEnabled,
711
310
  editorRef: getActiveEditorRef(),
712
311
  handleLabelInsert,
713
312
  handleSave,
@@ -720,14 +319,13 @@ const HTMLEditor = forwardRef(({
720
319
  switchDevice,
721
320
  toggleContentSync,
722
321
  getDeviceContent,
723
- layoutType,
724
- }),
322
+ layoutType
323
+ })
725
324
  }), [
726
325
  variant,
727
326
  content,
728
327
  layout,
729
328
  validation,
730
- isLiquidEnabled,
731
329
  getActiveEditorRef,
732
330
  handleLabelInsert,
733
331
  handleSave,
@@ -738,7 +336,7 @@ const HTMLEditor = forwardRef(({
738
336
  switchDevice,
739
337
  toggleContentSync,
740
338
  getDeviceContent,
741
- layoutType,
339
+ layoutType
742
340
  ]);
743
341
 
744
342
  // Loading state
@@ -750,14 +348,9 @@ const HTMLEditor = forwardRef(({
750
348
  );
751
349
  }
752
350
 
753
- // Add library-mode class when not in full mode
754
- // Note: isFullMode defaults to true, so library mode is when isFullMode === false
755
- const isLibraryMode = isFullMode === false;
756
- const editorClassName = `html-editor html-editor--${variant} ${isLibraryMode ? 'html-editor--library-mode' : ''} ${className}`.trim();
757
-
758
351
  return (
759
352
  <EditorProvider value={contextValue}>
760
- <div className={editorClassName} {...props}>
353
+ <div className={`html-editor html-editor--${variant} ${className}`} {...props}>
761
354
  {/* Editor Toolbar - Conditional based on variant */}
762
355
  {variant === HTML_EDITOR_VARIANTS.EMAIL ? (
763
356
  <EditorToolbar
@@ -794,23 +387,19 @@ const HTMLEditor = forwardRef(({
794
387
  ref={mainEditorRef}
795
388
  readOnly={readOnly}
796
389
  onLabelInsert={handleLabelInsert}
797
- onErrorClick={handleValidationErrorClick}
798
- tags={tags}
799
- injectedTags={injectedTags}
800
- location={location}
801
- eventContextTags={eventContextTags}
802
- selectedOfferDetails={selectedOfferDetails}
803
- channel={channel}
804
- userLocale={userLocale}
805
- moduleFilterEnabled={moduleFilterEnabled}
806
- onTagContextChange={onTagContextChange}
807
- onTagSelect={onTagSelect}
808
- onContextChange={handleContextChange}
809
390
  />
810
391
 
811
392
  {/* Preview Pane */}
812
393
  <PreviewPane />
813
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
+ />
814
403
  </CapRow>
815
404
 
816
405
  {/* Fullscreen Modal */}
@@ -822,17 +411,17 @@ const HTMLEditor = forwardRef(({
822
411
  maskClosable={false}
823
412
  centered
824
413
  closable={false}
825
- width="90vw"
414
+ width={"90vw"}
826
415
  className="html-editor-fullscreen-modal"
827
416
  >
828
- <CapRow className={`html-editor-fullscreen html-editor-fullscreen--${variant} ${isLibraryMode ? 'html-editor-fullscreen--library-mode' : ''}`}>
417
+ <CapRow className="html-editor-fullscreen">
829
418
  {/* Editor Toolbar - Conditional based on variant */}
830
419
  {variant === HTML_EDITOR_VARIANTS.EMAIL ? (
831
420
  <EditorToolbar
832
- showFullscreenButton // Show fullscreen button in modal to allow closing
421
+ showFullscreenButton={true} // Show fullscreen button in modal to allow closing
833
422
  onLabelInsert={handleLabelInsert}
834
423
  onSave={handleSave}
835
- isFullscreenMode
424
+ isFullscreenMode={true}
836
425
  onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
837
426
  />
838
427
  ) : (
@@ -845,10 +434,10 @@ const HTMLEditor = forwardRef(({
845
434
  onKeepContentSameChange={toggleContentSync}
846
435
  />
847
436
  <EditorToolbar
848
- showFullscreenButton // Show fullscreen button in modal to allow closing
437
+ showFullscreenButton={true} // Show fullscreen button in modal to allow closing
849
438
  onLabelInsert={handleLabelInsert}
850
439
  onSave={handleSave}
851
- isFullscreenMode
440
+ isFullscreenMode={true}
852
441
  onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
853
442
  variant={variant}
854
443
  showTitle={false} // Hide title in InApp variant
@@ -863,30 +452,28 @@ const HTMLEditor = forwardRef(({
863
452
  <CodeEditorPane
864
453
  ref={modalEditorRef}
865
454
  readOnly={readOnly}
866
- isFullscreenMode
455
+ isFullscreenMode={true}
867
456
  onLabelInsert={handleLabelInsert}
868
- onErrorClick={handleValidationErrorClick}
869
- tags={tags}
870
- injectedTags={injectedTags}
871
- location={location}
872
- eventContextTags={eventContextTags}
873
- selectedOfferDetails={selectedOfferDetails}
874
- channel={channel}
875
- userLocale={userLocale}
876
- moduleFilterEnabled={moduleFilterEnabled}
877
- onTagContextChange={onTagContextChange}
878
457
  />
879
458
 
880
- {/* Preview Pane */}
881
- <PreviewPane isFullscreenMode isModalContext />
882
- </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
+ />
883
470
  </CapRow>
884
471
  </CapRow>
885
472
  </CapModal>
886
473
  </div>
887
474
  </EditorProvider>
888
475
  );
889
- });
476
+ };
890
477
 
891
478
  HTMLEditor.propTypes = {
892
479
  intl: intlShape.isRequired,
@@ -894,7 +481,7 @@ HTMLEditor.propTypes = {
894
481
  layoutType: PropTypes.string, // Layout type for InApp variant
895
482
  initialContent: PropTypes.oneOfType([
896
483
  PropTypes.string,
897
- PropTypes.objectOf(PropTypes.string), // Per-device content for INAPP variant
484
+ PropTypes.objectOf(PropTypes.string) // Per-device content for INAPP variant
898
485
  ]),
899
486
  onSave: PropTypes.func,
900
487
  onContentChange: PropTypes.func,
@@ -902,33 +489,11 @@ HTMLEditor.propTypes = {
902
489
  readOnly: PropTypes.bool,
903
490
  showFullscreenButton: PropTypes.bool,
904
491
  autoSave: PropTypes.bool,
905
- autoSaveInterval: PropTypes.number,
906
- // Tag-related props - tags are fetched and managed by parent component
907
- tags: PropTypes.array,
908
- injectedTags: PropTypes.object,
909
- location: PropTypes.object,
910
- eventContextTags: PropTypes.array,
911
- selectedOfferDetails: PropTypes.array,
912
- channel: PropTypes.string,
913
- userLocale: PropTypes.string,
914
- moduleFilterEnabled: PropTypes.bool,
915
- onTagContextChange: PropTypes.func, // Required - parent must handle tag fetching
916
- onTagSelect: PropTypes.func,
917
- onContextChange: PropTypes.func, // Deprecated: use globalActions instead
918
- globalActions: PropTypes.object,
919
- isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
920
- isFullMode: PropTypes.bool, // Full mode vs library mode
921
- onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
922
- onValidationChange: PropTypes.func, // Callback when validation state changes
923
- apiValidationErrors: PropTypes.shape({
924
- liquidErrors: PropTypes.arrayOf(PropTypes.string),
925
- standardErrors: PropTypes.arrayOf(PropTypes.string),
926
- }), // API validation errors from validateLiquidTemplateContent
492
+ autoSaveInterval: PropTypes.number
927
493
  };
928
494
 
929
495
  HTMLEditor.defaultProps = {
930
496
  variant: HTML_EDITOR_VARIANTS.EMAIL, // Default to email variant
931
- layoutType: null,
932
497
  initialContent: null, // Will use default from useEditorContent hook
933
498
  onSave: null,
934
499
  onContentChange: null,
@@ -936,26 +501,8 @@ HTMLEditor.defaultProps = {
936
501
  readOnly: false,
937
502
  showFullscreenButton: true,
938
503
  autoSave: true,
939
- autoSaveInterval: 30000,
940
- // Tag-related defaults - tags are fetched and managed by parent component
941
- tags: [],
942
- injectedTags: {},
943
- location: null,
944
- eventContextTags: [],
945
- selectedOfferDetails: [],
946
- channel: null,
947
- userLocale: 'en',
948
- moduleFilterEnabled: true,
949
- onTagContextChange: null, // Parent component should provide this
950
- onTagSelect: null,
951
- onContextChange: null,
952
- globalActions: null, // Redux actions for API calls
953
- isLiquidEnabled: false,
954
- isFullMode: true, // Default to full mode
955
- onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
956
- onValidationChange: null, // Callback when validation state changes
957
- apiValidationErrors: null, // API validation errors from validateLiquidTemplateContent
504
+ autoSaveInterval: 30000
958
505
  };
959
506
 
960
- // Export with injectIntl - HTMLEditor now uses forwardRef internally
961
- export default injectIntl(HTMLEditor);
507
+ // Export with forwardRef to allow direct access to CodeEditorPane via ref
508
+ export default injectIntl(HTMLEditor, { forwardRef: true });