@capillarytech/creatives-library 8.0.208 → 8.0.209
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/config/app.js +1 -2
- package/package.json +16 -2
- package/services/api.js +0 -2
- package/v2Components/HtmlEditor/HTMLEditor.js +508 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1809 -0
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +532 -0
- package/v2Components/HtmlEditor/_htmlEditor.scss +304 -0
- package/v2Components/HtmlEditor/_index.lazy.scss +26 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +376 -0
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +331 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/__tests__/index.test.js +314 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +244 -0
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +111 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/PreviewModeGroup.js +72 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/__tests__/PreviewModeGroup.test.js +1594 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +113 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/_previewModeGroup.scss +82 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +115 -0
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +57 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/ContentOverlay.js +90 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +60 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/LayoutSelector.js +58 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/ContentOverlay.test.js +389 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +424 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/LayoutSelector.test.js +248 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +253 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +104 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +179 -0
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +220 -0
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +229 -0
- package/v2Components/HtmlEditor/components/SplitContainer/SplitContainer.js +276 -0
- package/v2Components/HtmlEditor/components/SplitContainer/__tests__/SplitContainer.test.js +295 -0
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +257 -0
- package/v2Components/HtmlEditor/components/SplitContainer/index.js +7 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +31 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +70 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/__tests__/index.test.js +98 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +311 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +297 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/messages.js +57 -0
- package/v2Components/HtmlEditor/components/common/EditorContext.js +84 -0
- package/v2Components/HtmlEditor/components/common/__tests__/EditorContext.test.js +660 -0
- package/v2Components/HtmlEditor/constants.js +241 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useEditorContent.test.js +450 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +785 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useLayoutState.test.js +580 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.enhanced.test.js +768 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +590 -0
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +274 -0
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +407 -0
- package/v2Components/HtmlEditor/hooks/useLayoutState.js +247 -0
- package/v2Components/HtmlEditor/hooks/useValidation.js +325 -0
- package/v2Components/HtmlEditor/index.js +29 -0
- package/v2Components/HtmlEditor/index.lazy.js +114 -0
- package/v2Components/HtmlEditor/messages.js +389 -0
- package/v2Components/HtmlEditor/utils/__tests__/contentSanitizer.test.js +741 -0
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +1042 -0
- package/v2Components/HtmlEditor/utils/__tests__/liquidTemplateSupport.test.js +515 -0
- package/v2Components/HtmlEditor/utils/__tests__/properSyntaxHighlighting.test.js +473 -0
- package/v2Components/HtmlEditor/utils/__tests__/simplePerformance.test.js +1109 -0
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +240 -0
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +433 -0
- package/v2Components/HtmlEditor/utils/htmlValidator.js +508 -0
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +524 -0
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +163 -0
- package/v2Components/HtmlEditor/utils/simplePerformance.js +145 -0
- package/v2Components/HtmlEditor/utils/validationAdapter.js +130 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +0 -2
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +200 -0
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +545 -0
- package/v2Containers/EmailWrapper/index.js +8 -1
- package/v2Containers/Templates/constants.js +8 -0
- package/v2Containers/Templates/index.js +56 -28
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +5 -14
|
Binary file
|
package/assets/iOS.png
ADDED
|
Binary file
|
package/config/app.js
CHANGED
|
@@ -20,8 +20,7 @@ const config = {
|
|
|
20
20
|
accountConfig: (strs, accountId) => `${window.location.origin}/org/config/AccountAdd?q=a&channelId=2&accountId=${accountId}&edit=1`,
|
|
21
21
|
},
|
|
22
22
|
development: {
|
|
23
|
-
|
|
24
|
-
api_endpoint: 'http://localhost:2022/arya/api/v1/creatives',
|
|
23
|
+
api_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1/creatives',
|
|
25
24
|
campaigns_api_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/iris/v2/campaigns',
|
|
26
25
|
campaigns_api_org_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/iris/v2/org/campaign',
|
|
27
26
|
auth_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1/auth',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capillarytech/creatives-library",
|
|
3
3
|
"author": "meharaj",
|
|
4
|
-
"version": "8.0.
|
|
4
|
+
"version": "8.0.209",
|
|
5
5
|
"description": "Capillary creatives ui",
|
|
6
6
|
"main": "./index.js",
|
|
7
7
|
"module": "./index.es.js",
|
|
@@ -18,16 +18,30 @@
|
|
|
18
18
|
"@bugsnag/plugin-react": "7.2.1",
|
|
19
19
|
"@capillarytech/cap-ui-utils": "3.0.4",
|
|
20
20
|
"@capillarytech/vulcan-react-sdk": "^2.3.5",
|
|
21
|
-
"@
|
|
21
|
+
"@codemirror/autocomplete": "^6.19.0",
|
|
22
|
+
"@codemirror/lang-css": "^6.3.1",
|
|
23
|
+
"@codemirror/lang-html": "^6.4.11",
|
|
24
|
+
"@codemirror/lang-javascript": "^6.2.4",
|
|
25
|
+
"@codemirror/language": "^6.11.3",
|
|
26
|
+
"@codemirror/language-data": "^6.5.1",
|
|
27
|
+
"@codemirror/lint": "^6.9.0",
|
|
28
|
+
"@codemirror/search": "^6.5.11",
|
|
29
|
+
"@codemirror/state": "^6.5.2",
|
|
30
|
+
"@codemirror/theme-one-dark": "^6.1.3",
|
|
31
|
+
"@codemirror/view": "^6.38.5",
|
|
32
|
+
"@lezer/highlight": "^1.2.1",
|
|
22
33
|
"@mailupinc/bee-plugin": "^1.2.0",
|
|
34
|
+
"@newrelic/browser-agent": "^1.293.0",
|
|
23
35
|
"babel-cli": "^6.26.0",
|
|
24
36
|
"chalk": "^2.4.2",
|
|
25
37
|
"cheerio": "^1.0.0-rc.3",
|
|
26
38
|
"connected-react-router": "4.5.0",
|
|
39
|
+
"dompurify": "^3.2.7",
|
|
27
40
|
"flow": "^0.2.3",
|
|
28
41
|
"git": "^0.1.5",
|
|
29
42
|
"history": "4.9.0",
|
|
30
43
|
"html-to-text": "^8.2.1",
|
|
44
|
+
"htmlhint": "^1.7.1",
|
|
31
45
|
"jest-date-mock": "^1.0.8",
|
|
32
46
|
"jest-environment-jsdom": "27.5.1",
|
|
33
47
|
"jquery": "^3.3.1",
|
package/services/api.js
CHANGED
|
@@ -264,7 +264,6 @@ export const getUserData = () => {
|
|
|
264
264
|
|
|
265
265
|
export const createTemplate = ({template}) => {
|
|
266
266
|
const url = `${API_ENDPOINT}/templates/SMS`;
|
|
267
|
-
console.log("creating template",template);
|
|
268
267
|
return request(url, getAPICallObject('POST', template));
|
|
269
268
|
};
|
|
270
269
|
|
|
@@ -347,7 +346,6 @@ export const getAllTemplates = async ({channel, queryParams = {}}) => {
|
|
|
347
346
|
|
|
348
347
|
export const deleteTemplate = ({channel, id}) => {
|
|
349
348
|
const url = `${API_ENDPOINT}/templates/${id}/${channel}`;
|
|
350
|
-
console.log("deleting template", url);
|
|
351
349
|
return request(url, getAPICallObject('DELETE'));
|
|
352
350
|
//return API.deleteResource(url);
|
|
353
351
|
};
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTMLEditor - Comprehensive HTML Editor with Split-Screen Layout
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Split-screen layout with code editor and live preview
|
|
6
|
+
* - CodeMirror 6 integration for HTML/CSS/JavaScript editing
|
|
7
|
+
* - Real-time preview with sandboxed iframe
|
|
8
|
+
* - Mobile/Desktop responsive preview modes
|
|
9
|
+
* - Comprehensive validation and error handling
|
|
10
|
+
* - Security-first approach with XSS protection
|
|
11
|
+
*
|
|
12
|
+
* Note: Uses injectIntl with forwardRef to provide direct access to CodeEditorPane via ref
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React, { useRef, useCallback, useMemo, useState } from 'react';
|
|
16
|
+
import PropTypes from 'prop-types';
|
|
17
|
+
import { Layout } from 'antd'; // Fallback - no Cap UI equivalent
|
|
18
|
+
import { injectIntl, intlShape } from 'react-intl';
|
|
19
|
+
|
|
20
|
+
// Cap UI Components (First Preference)
|
|
21
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
22
|
+
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
23
|
+
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
24
|
+
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
25
|
+
import CapModal from '@capillarytech/cap-ui-library/CapModal';
|
|
26
|
+
|
|
27
|
+
// Component imports
|
|
28
|
+
import EditorToolbar from './components/EditorToolbar';
|
|
29
|
+
import DeviceToggle from './components/DeviceToggle';
|
|
30
|
+
import SplitContainer from './components/SplitContainer';
|
|
31
|
+
import CodeEditorPane from './components/CodeEditorPane';
|
|
32
|
+
import PreviewPane from './components/PreviewPane';
|
|
33
|
+
import ValidationErrorDisplay from './components/ValidationErrorDisplay';
|
|
34
|
+
import { EditorProvider } from './components/common/EditorContext';
|
|
35
|
+
|
|
36
|
+
// Hooks and utilities
|
|
37
|
+
import { useEditorContent } from './hooks/useEditorContent';
|
|
38
|
+
import { useInAppContent } from './hooks/useInAppContent';
|
|
39
|
+
import { useLayoutState } from './hooks/useLayoutState';
|
|
40
|
+
import { useValidation } from './hooks/useValidation';
|
|
41
|
+
|
|
42
|
+
// Constants
|
|
43
|
+
import { HTML_EDITOR_VARIANTS, DEVICE_TYPES, DEFAULT_HTML_CONTENT } from './constants';
|
|
44
|
+
|
|
45
|
+
// Styles
|
|
46
|
+
import './_htmlEditor.scss';
|
|
47
|
+
import './components/FullscreenModal/_fullscreenModal.scss';
|
|
48
|
+
|
|
49
|
+
// Messages
|
|
50
|
+
import messages from './messages';
|
|
51
|
+
|
|
52
|
+
const HTMLEditor = ({
|
|
53
|
+
intl,
|
|
54
|
+
variant = HTML_EDITOR_VARIANTS.EMAIL, // New prop: 'email' or 'inapp'
|
|
55
|
+
layoutType, // Layout type for InApp variant
|
|
56
|
+
initialContent = DEFAULT_HTML_CONTENT,
|
|
57
|
+
onSave,
|
|
58
|
+
onContentChange,
|
|
59
|
+
className = '',
|
|
60
|
+
readOnly = false,
|
|
61
|
+
showFullscreenButton = true,
|
|
62
|
+
autoSave = true,
|
|
63
|
+
autoSaveInterval = 30000, // 30 seconds
|
|
64
|
+
...props
|
|
65
|
+
}) => {
|
|
66
|
+
// Separate refs for main and modal editors to avoid conflicts
|
|
67
|
+
const mainEditorRef = useRef(null);
|
|
68
|
+
const modalEditorRef = useRef(null);
|
|
69
|
+
const [isFullscreenModalOpen, setIsFullscreenModalOpen] = useState(false);
|
|
70
|
+
|
|
71
|
+
// Get the currently active editor ref based on fullscreen state
|
|
72
|
+
const getActiveEditorRef = useCallback(() => {
|
|
73
|
+
return isFullscreenModalOpen ? modalEditorRef : mainEditorRef;
|
|
74
|
+
}, [isFullscreenModalOpen]);
|
|
75
|
+
|
|
76
|
+
// Initialize custom hooks for state management - always call both hooks to follow Rules of Hooks
|
|
77
|
+
const isEmailVariant = variant === HTML_EDITOR_VARIANTS.EMAIL;
|
|
78
|
+
|
|
79
|
+
const emailOptions = {
|
|
80
|
+
autoSave: isEmailVariant ? autoSave : false,
|
|
81
|
+
autoSaveInterval,
|
|
82
|
+
onSave: isEmailVariant ? onSave : null,
|
|
83
|
+
onChange: isEmailVariant ? onContentChange : null
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const emailContent = useEditorContent(
|
|
87
|
+
isEmailVariant ? initialContent : '',
|
|
88
|
+
emailOptions
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Prepare content for InApp variant
|
|
92
|
+
const isInAppVariant = variant === HTML_EDITOR_VARIANTS.INAPP;
|
|
93
|
+
|
|
94
|
+
let inAppInitialContent = {};
|
|
95
|
+
if (isInAppVariant) {
|
|
96
|
+
if (typeof initialContent === 'string') {
|
|
97
|
+
// Convert string content to device-specific format
|
|
98
|
+
inAppInitialContent = {
|
|
99
|
+
[DEVICE_TYPES.ANDROID]: initialContent,
|
|
100
|
+
[DEVICE_TYPES.IOS]: initialContent
|
|
101
|
+
};
|
|
102
|
+
} else {
|
|
103
|
+
// Use provided device-specific content
|
|
104
|
+
inAppInitialContent = initialContent;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const inAppOptions = {
|
|
109
|
+
autoSave: isInAppVariant ? autoSave : false,
|
|
110
|
+
autoSaveInterval,
|
|
111
|
+
onSave: isInAppVariant ? onSave : null,
|
|
112
|
+
onChange: isInAppVariant ? onContentChange : null
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const inAppContent = useInAppContent(inAppInitialContent, inAppOptions);
|
|
116
|
+
|
|
117
|
+
// Use appropriate content hook based on variant
|
|
118
|
+
const content = variant === HTML_EDITOR_VARIANTS.EMAIL ? emailContent : inAppContent;
|
|
119
|
+
|
|
120
|
+
// Destructure content properties for cleaner access throughout component
|
|
121
|
+
const {
|
|
122
|
+
activeDevice,
|
|
123
|
+
keepContentSame,
|
|
124
|
+
switchDevice,
|
|
125
|
+
toggleContentSync,
|
|
126
|
+
getDeviceContent,
|
|
127
|
+
markAsSaved
|
|
128
|
+
} = content || {};
|
|
129
|
+
|
|
130
|
+
const layout = useLayoutState({
|
|
131
|
+
splitSizes: [50, 50],
|
|
132
|
+
viewMode: 'desktop',
|
|
133
|
+
mobileWidth: 375,
|
|
134
|
+
isFullscreen: false
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Get current content for validation based on variant
|
|
138
|
+
const currentContent = variant === HTML_EDITOR_VARIANTS.EMAIL
|
|
139
|
+
? content?.content
|
|
140
|
+
: getDeviceContent?.(activeDevice);
|
|
141
|
+
|
|
142
|
+
// Create message formatter for sanitizer
|
|
143
|
+
const formatSanitizerMessage = useCallback((messageKey, values = {}) => {
|
|
144
|
+
// Map sanitizer message keys to our message structure
|
|
145
|
+
const messageMap = {
|
|
146
|
+
'sanitizer.invalidInput': messages.sanitizer.invalidInput,
|
|
147
|
+
'sanitizer.invalidInputNonEmpty': messages.sanitizer.invalidInputNonEmpty,
|
|
148
|
+
'sanitizer.sanitizationFailed': messages.sanitizer.sanitizationFailed,
|
|
149
|
+
'sanitizer.dangerousProtocolDetected': messages.sanitizer.dangerousProtocolDetected,
|
|
150
|
+
'sanitizer.emailMultimediaNotSupported': messages.sanitizer.emailMultimediaNotSupported,
|
|
151
|
+
'sanitizer.emailPositioningNotSupported': messages.sanitizer.emailPositioningNotSupported,
|
|
152
|
+
'sanitizer.emailModernCssLimited': messages.sanitizer.emailModernCssLimited,
|
|
153
|
+
'sanitizer.emailViewportUnitsNotSupported': messages.sanitizer.emailViewportUnitsNotSupported,
|
|
154
|
+
'sanitizer.mobileTablesNotFriendly': messages.sanitizer.mobileTablesNotFriendly,
|
|
155
|
+
'sanitizer.mobileRelativeFontSizes': messages.sanitizer.mobileRelativeFontSizes,
|
|
156
|
+
'sanitizer.mobileFixedWidthsProblematic': messages.sanitizer.mobileFixedWidthsProblematic,
|
|
157
|
+
'sanitizer.mobileRelativeUnits': messages.sanitizer.mobileRelativeUnits,
|
|
158
|
+
'sanitizer.productionValidHtml': messages.sanitizer.productionValidHtml,
|
|
159
|
+
'sanitizer.productionSanitized': messages.sanitizer.productionSanitized,
|
|
160
|
+
'sanitizer.productionInlineCss': messages.sanitizer.productionInlineCss,
|
|
161
|
+
'sanitizer.productionLargeContent': messages.sanitizer.productionLargeContent
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const messageObj = messageMap[messageKey];
|
|
165
|
+
return messageObj ? intl.formatMessage(messageObj, values) : messageKey;
|
|
166
|
+
}, [intl]);
|
|
167
|
+
|
|
168
|
+
// Create message formatter for validator
|
|
169
|
+
const formatValidatorMessage = useCallback((messageKey, values = {}) => {
|
|
170
|
+
// Map validator message keys to our message structure
|
|
171
|
+
const messageMap = {
|
|
172
|
+
'validator.validationFailed': messages.validator.validationFailed,
|
|
173
|
+
'validator.liquidValidationFailed': messages.validator.liquidValidationFailed,
|
|
174
|
+
'validator.unsafeProtocolDetected': messages.validator.unsafeProtocolDetected,
|
|
175
|
+
'validator.scriptTagsDetected': messages.validator.scriptTagsDetected,
|
|
176
|
+
'validator.outlookIncompatible': messages.validator.outlookIncompatible,
|
|
177
|
+
'validator.emailCssUnsupported': messages.validator.emailCssUnsupported,
|
|
178
|
+
'validator.mobileIncompatible': messages.validator.mobileIncompatible,
|
|
179
|
+
'validator.largeImageDetected': messages.validator.largeImageDetected,
|
|
180
|
+
'validator.unclosedCssRule': messages.validator.unclosedCssRule,
|
|
181
|
+
'validator.emptyCssRule': messages.validator.emptyCssRule,
|
|
182
|
+
'validator.cssValidationFailed': messages.validator.cssValidationFailed
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const messageObj = messageMap[messageKey];
|
|
186
|
+
return messageObj ? intl.formatMessage(messageObj, values) : messageKey;
|
|
187
|
+
}, [intl]);
|
|
188
|
+
|
|
189
|
+
const validation = useValidation(currentContent, variant, {
|
|
190
|
+
enableRealTime: true,
|
|
191
|
+
debounceMs: 500,
|
|
192
|
+
enableSanitization: true,
|
|
193
|
+
securityLevel: 'standard'
|
|
194
|
+
}, formatSanitizerMessage, formatValidatorMessage);
|
|
195
|
+
|
|
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;
|
|
201
|
+
|
|
202
|
+
if (!editor) {
|
|
203
|
+
CapNotification.warning({
|
|
204
|
+
message: intl.formatMessage(messages.labelInsertError),
|
|
205
|
+
description: intl.formatMessage(messages.editorNotReady),
|
|
206
|
+
duration: 3
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
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
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
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);
|
|
226
|
+
|
|
227
|
+
// Insert label at cursor position
|
|
228
|
+
editor.insertText(label, cursor);
|
|
229
|
+
|
|
230
|
+
// Focus the editor if focus method is available
|
|
231
|
+
editor?.focus?.();
|
|
232
|
+
|
|
233
|
+
// Show success notification
|
|
234
|
+
CapNotification.success({
|
|
235
|
+
message: intl.formatMessage(messages.labelInserted),
|
|
236
|
+
description: intl.formatMessage(messages.labelInsertedDescription, { label }),
|
|
237
|
+
duration: 2
|
|
238
|
+
});
|
|
239
|
+
} catch (error) {
|
|
240
|
+
CapNotification.error({
|
|
241
|
+
message: intl.formatMessage(messages.labelInsertError),
|
|
242
|
+
description: error.message,
|
|
243
|
+
duration: 4
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}, [intl, getActiveEditorRef]);
|
|
247
|
+
|
|
248
|
+
// Handle save action
|
|
249
|
+
const handleSave = useCallback(() => {
|
|
250
|
+
try {
|
|
251
|
+
const { html, css, javascript } = content?.content || {};
|
|
252
|
+
const currentContent = { html, css, javascript };
|
|
253
|
+
|
|
254
|
+
if (onSave) {
|
|
255
|
+
onSave(currentContent);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
markAsSaved?.();
|
|
259
|
+
|
|
260
|
+
CapNotification.success({
|
|
261
|
+
message: intl.formatMessage(messages.contentSaved),
|
|
262
|
+
duration: 2
|
|
263
|
+
});
|
|
264
|
+
} catch (error) {
|
|
265
|
+
CapNotification.error({
|
|
266
|
+
message: intl.formatMessage(messages.saveError),
|
|
267
|
+
description: error.message,
|
|
268
|
+
duration: 4
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}, [content, onSave, intl, markAsSaved]);
|
|
272
|
+
|
|
273
|
+
// Handle validation error click - navigate to error line in editor
|
|
274
|
+
const handleValidationErrorClick = useCallback((error) => {
|
|
275
|
+
const activeEditorRef = getActiveEditorRef();
|
|
276
|
+
const editorInstance = activeEditorRef.current;
|
|
277
|
+
const { line, column = 1 } = error || {};
|
|
278
|
+
|
|
279
|
+
if (editorInstance && line) {
|
|
280
|
+
try {
|
|
281
|
+
// Access the CodeMirror view through the exposed ref methods
|
|
282
|
+
if (editorInstance?.navigateToLine) {
|
|
283
|
+
editorInstance.navigateToLine(line, column);
|
|
284
|
+
} else {
|
|
285
|
+
editorInstance?.focus?.();
|
|
286
|
+
// Fallback: just focus the editor if navigation isn't available
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
// Fallback: just focus the editor
|
|
290
|
+
editorInstance?.focus?.();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}, [getActiveEditorRef]);
|
|
294
|
+
|
|
295
|
+
// Handle fullscreen modal
|
|
296
|
+
const handleOpenFullscreen = useCallback(() => {
|
|
297
|
+
setIsFullscreenModalOpen(true);
|
|
298
|
+
}, []);
|
|
299
|
+
|
|
300
|
+
const handleCloseFullscreen = useCallback(() => {
|
|
301
|
+
setIsFullscreenModalOpen(false);
|
|
302
|
+
}, []);
|
|
303
|
+
|
|
304
|
+
// Context value for providers
|
|
305
|
+
const contextValue = useMemo(() => ({
|
|
306
|
+
variant,
|
|
307
|
+
content,
|
|
308
|
+
layout,
|
|
309
|
+
validation,
|
|
310
|
+
editorRef: getActiveEditorRef(),
|
|
311
|
+
handleLabelInsert,
|
|
312
|
+
handleSave,
|
|
313
|
+
readOnly,
|
|
314
|
+
isFullscreenMode: isFullscreenModalOpen,
|
|
315
|
+
// InApp specific context
|
|
316
|
+
...(variant === HTML_EDITOR_VARIANTS.INAPP && {
|
|
317
|
+
activeDevice,
|
|
318
|
+
keepContentSame,
|
|
319
|
+
switchDevice,
|
|
320
|
+
toggleContentSync,
|
|
321
|
+
getDeviceContent,
|
|
322
|
+
layoutType
|
|
323
|
+
})
|
|
324
|
+
}), [
|
|
325
|
+
variant,
|
|
326
|
+
content,
|
|
327
|
+
layout,
|
|
328
|
+
validation,
|
|
329
|
+
getActiveEditorRef,
|
|
330
|
+
handleLabelInsert,
|
|
331
|
+
handleSave,
|
|
332
|
+
readOnly,
|
|
333
|
+
isFullscreenModalOpen,
|
|
334
|
+
activeDevice,
|
|
335
|
+
keepContentSame,
|
|
336
|
+
switchDevice,
|
|
337
|
+
toggleContentSync,
|
|
338
|
+
getDeviceContent,
|
|
339
|
+
layoutType
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
// Loading state
|
|
343
|
+
if (!content || !layout) {
|
|
344
|
+
return (
|
|
345
|
+
<CapRow className="html-editor-loading">
|
|
346
|
+
<CapSpin size="large" tip={intl.formatMessage(messages.initializing)} />
|
|
347
|
+
</CapRow>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<EditorProvider value={contextValue}>
|
|
353
|
+
<div className={`html-editor html-editor--${variant} ${className}`} {...props}>
|
|
354
|
+
{/* Editor Toolbar - Conditional based on variant */}
|
|
355
|
+
{variant === HTML_EDITOR_VARIANTS.EMAIL ? (
|
|
356
|
+
<EditorToolbar
|
|
357
|
+
showFullscreenButton={showFullscreenButton}
|
|
358
|
+
onLabelInsert={handleLabelInsert}
|
|
359
|
+
onSave={handleSave}
|
|
360
|
+
onToggleFullscreen={handleOpenFullscreen}
|
|
361
|
+
/>
|
|
362
|
+
) : (
|
|
363
|
+
/* InApp variant: Device Toggle + Toolbar combined */
|
|
364
|
+
<CapRow className="html-editor__header">
|
|
365
|
+
<DeviceToggle
|
|
366
|
+
activeDevice={activeDevice}
|
|
367
|
+
onDeviceChange={switchDevice}
|
|
368
|
+
keepContentSame={keepContentSame}
|
|
369
|
+
onKeepContentSameChange={toggleContentSync}
|
|
370
|
+
/>
|
|
371
|
+
<EditorToolbar
|
|
372
|
+
showFullscreenButton={showFullscreenButton}
|
|
373
|
+
onLabelInsert={handleLabelInsert}
|
|
374
|
+
onSave={handleSave}
|
|
375
|
+
onToggleFullscreen={handleOpenFullscreen}
|
|
376
|
+
variant={variant}
|
|
377
|
+
showTitle={false} // Hide title in InApp variant
|
|
378
|
+
/>
|
|
379
|
+
</CapRow>
|
|
380
|
+
)}
|
|
381
|
+
|
|
382
|
+
{/* Main Content Area - Split Container */}
|
|
383
|
+
<CapRow className="html-editor-content">
|
|
384
|
+
<SplitContainer className="html-editor-split-container">
|
|
385
|
+
{/* Code Editor Pane */}
|
|
386
|
+
<CodeEditorPane
|
|
387
|
+
ref={mainEditorRef}
|
|
388
|
+
readOnly={readOnly}
|
|
389
|
+
onLabelInsert={handleLabelInsert}
|
|
390
|
+
/>
|
|
391
|
+
|
|
392
|
+
{/* Preview Pane */}
|
|
393
|
+
<PreviewPane />
|
|
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
|
+
/>
|
|
403
|
+
</CapRow>
|
|
404
|
+
|
|
405
|
+
{/* Fullscreen Modal */}
|
|
406
|
+
<CapModal
|
|
407
|
+
title={null} // Remove modal header to avoid duplication
|
|
408
|
+
visible={isFullscreenModalOpen}
|
|
409
|
+
onCancel={handleCloseFullscreen}
|
|
410
|
+
footer={null}
|
|
411
|
+
maskClosable={false}
|
|
412
|
+
centered
|
|
413
|
+
closable={false}
|
|
414
|
+
width={"90vw"}
|
|
415
|
+
className="html-editor-fullscreen-modal"
|
|
416
|
+
>
|
|
417
|
+
<CapRow className="html-editor-fullscreen">
|
|
418
|
+
{/* Editor Toolbar - Conditional based on variant */}
|
|
419
|
+
{variant === HTML_EDITOR_VARIANTS.EMAIL ? (
|
|
420
|
+
<EditorToolbar
|
|
421
|
+
showFullscreenButton={true} // Show fullscreen button in modal to allow closing
|
|
422
|
+
onLabelInsert={handleLabelInsert}
|
|
423
|
+
onSave={handleSave}
|
|
424
|
+
isFullscreenMode={true}
|
|
425
|
+
onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
|
|
426
|
+
/>
|
|
427
|
+
) : (
|
|
428
|
+
/* InApp variant: Device Toggle + Toolbar combined in fullscreen */
|
|
429
|
+
<CapRow className="html-editor__header html-editor__header--fullscreen">
|
|
430
|
+
<DeviceToggle
|
|
431
|
+
activeDevice={activeDevice}
|
|
432
|
+
onDeviceChange={switchDevice}
|
|
433
|
+
keepContentSame={keepContentSame}
|
|
434
|
+
onKeepContentSameChange={toggleContentSync}
|
|
435
|
+
/>
|
|
436
|
+
<EditorToolbar
|
|
437
|
+
showFullscreenButton={true} // Show fullscreen button in modal to allow closing
|
|
438
|
+
onLabelInsert={handleLabelInsert}
|
|
439
|
+
onSave={handleSave}
|
|
440
|
+
isFullscreenMode={true}
|
|
441
|
+
onToggleFullscreen={handleCloseFullscreen} // Close modal when clicked in fullscreen mode
|
|
442
|
+
variant={variant}
|
|
443
|
+
showTitle={false} // Hide title in InApp variant
|
|
444
|
+
/>
|
|
445
|
+
</CapRow>
|
|
446
|
+
)}
|
|
447
|
+
|
|
448
|
+
{/* Main Content Area - Split Container */}
|
|
449
|
+
<CapRow className="html-editor-content-fullscreen">
|
|
450
|
+
<SplitContainer className="html-editor-split-container-fullscreen">
|
|
451
|
+
{/* Code Editor Pane */}
|
|
452
|
+
<CodeEditorPane
|
|
453
|
+
ref={modalEditorRef}
|
|
454
|
+
readOnly={readOnly}
|
|
455
|
+
isFullscreenMode={true}
|
|
456
|
+
onLabelInsert={handleLabelInsert}
|
|
457
|
+
/>
|
|
458
|
+
|
|
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
|
+
/>
|
|
470
|
+
</CapRow>
|
|
471
|
+
</CapRow>
|
|
472
|
+
</CapModal>
|
|
473
|
+
</div>
|
|
474
|
+
</EditorProvider>
|
|
475
|
+
);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
HTMLEditor.propTypes = {
|
|
479
|
+
intl: intlShape.isRequired,
|
|
480
|
+
variant: PropTypes.oneOf(Object.values(HTML_EDITOR_VARIANTS)), // Added variant prop
|
|
481
|
+
layoutType: PropTypes.string, // Layout type for InApp variant
|
|
482
|
+
initialContent: PropTypes.oneOfType([
|
|
483
|
+
PropTypes.string,
|
|
484
|
+
PropTypes.objectOf(PropTypes.string) // Per-device content for INAPP variant
|
|
485
|
+
]),
|
|
486
|
+
onSave: PropTypes.func,
|
|
487
|
+
onContentChange: PropTypes.func,
|
|
488
|
+
className: PropTypes.string,
|
|
489
|
+
readOnly: PropTypes.bool,
|
|
490
|
+
showFullscreenButton: PropTypes.bool,
|
|
491
|
+
autoSave: PropTypes.bool,
|
|
492
|
+
autoSaveInterval: PropTypes.number
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
HTMLEditor.defaultProps = {
|
|
496
|
+
variant: HTML_EDITOR_VARIANTS.EMAIL, // Default to email variant
|
|
497
|
+
initialContent: null, // Will use default from useEditorContent hook
|
|
498
|
+
onSave: null,
|
|
499
|
+
onContentChange: null,
|
|
500
|
+
className: '',
|
|
501
|
+
readOnly: false,
|
|
502
|
+
showFullscreenButton: true,
|
|
503
|
+
autoSave: true,
|
|
504
|
+
autoSaveInterval: 30000
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// Export with forwardRef to allow direct access to CodeEditorPane via ref
|
|
508
|
+
export default injectIntl(HTMLEditor, { forwardRef: true });
|