@capillarytech/creatives-library 8.0.208 → 8.0.210
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/package.json +16 -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 +403 -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/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/Rcs/index.js +2 -0
- 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
- package/v2Containers/Whatsapp/index.js +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Pane Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
6
|
+
|
|
7
|
+
.preview-pane {
|
|
8
|
+
height: 100%;
|
|
9
|
+
max-height: 31.25rem;
|
|
10
|
+
position: relative;
|
|
11
|
+
background-color: $CAP_G09;
|
|
12
|
+
padding: 1rem;
|
|
13
|
+
overflow: auto;
|
|
14
|
+
|
|
15
|
+
&__mode-controls {
|
|
16
|
+
position: absolute;
|
|
17
|
+
top: 0.5rem;
|
|
18
|
+
right: 0.5rem;
|
|
19
|
+
z-index: 10;
|
|
20
|
+
background-color: $CAP_G07;
|
|
21
|
+
border-radius: 0.5rem;
|
|
22
|
+
padding: 0.25rem;
|
|
23
|
+
display: flex;
|
|
24
|
+
gap: 0.25rem;
|
|
25
|
+
align-items: center;
|
|
26
|
+
|
|
27
|
+
.preview-mode-group {
|
|
28
|
+
margin: 0;
|
|
29
|
+
|
|
30
|
+
.ant-btn {
|
|
31
|
+
border: none;
|
|
32
|
+
box-shadow: none;
|
|
33
|
+
padding: 0.25rem;
|
|
34
|
+
min-width: 1.75rem;
|
|
35
|
+
height: 1.75rem;
|
|
36
|
+
border-radius: 0.25rem;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
|
|
41
|
+
&.ant-btn-primary {
|
|
42
|
+
background-color: $CAP_WHITE;
|
|
43
|
+
color: $CAP_G01;
|
|
44
|
+
|
|
45
|
+
.anticon {
|
|
46
|
+
color: $CAP_G01;
|
|
47
|
+
font-size: 1.25rem;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&:not(.ant-btn-primary) {
|
|
52
|
+
background-color: transparent;
|
|
53
|
+
color: $CAP_G04;
|
|
54
|
+
|
|
55
|
+
.anticon {
|
|
56
|
+
color: $CAP_G04;
|
|
57
|
+
font-size: 1.25rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&:hover,
|
|
61
|
+
&.preview-mode-group__btn--active {
|
|
62
|
+
background-color: rgba($CAP_WHITE, 0.5);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&__content {
|
|
70
|
+
height: calc(100% - 2rem);
|
|
71
|
+
max-height: 28.125rem;
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
position: relative;
|
|
76
|
+
overflow: auto;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&__iframe {
|
|
80
|
+
border: none;
|
|
81
|
+
border-radius: 0.25rem;
|
|
82
|
+
box-shadow: 0 0.0625rem 0.1875rem rgba($CAP_G01, 0.1);
|
|
83
|
+
background-color: $CAP_WHITE;
|
|
84
|
+
width: 100%;
|
|
85
|
+
height: 28.125rem;
|
|
86
|
+
max-height: 31.25rem;
|
|
87
|
+
|
|
88
|
+
&--mobile {
|
|
89
|
+
// Width and height now controlled by React inline styles
|
|
90
|
+
border-radius: 0.75rem;
|
|
91
|
+
box-shadow: 0 0.25rem 1.25rem rgba($CAP_G01, 0.1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
iframe {
|
|
96
|
+
border: none;
|
|
97
|
+
border-radius: 0.25rem;
|
|
98
|
+
box-shadow: 0 0.0625rem 0.1875rem rgba($CAP_G01, 0.1);
|
|
99
|
+
background-color: $CAP_WHITE;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.device-frame {
|
|
103
|
+
position: relative;
|
|
104
|
+
display: inline-block;
|
|
105
|
+
|
|
106
|
+
.home-indicator {
|
|
107
|
+
position: absolute;
|
|
108
|
+
bottom: -0.25rem;
|
|
109
|
+
left: 50%;
|
|
110
|
+
transform: translateX(-50%);
|
|
111
|
+
width: 8.375rem;
|
|
112
|
+
height: 0.3125rem;
|
|
113
|
+
background-color: $CAP_G02;
|
|
114
|
+
border-radius: 0.1875rem;
|
|
115
|
+
z-index: 1070;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&::before {
|
|
119
|
+
content: '';
|
|
120
|
+
position: absolute;
|
|
121
|
+
top: -1.25rem;
|
|
122
|
+
left: 50%;
|
|
123
|
+
transform: translateX(-50%);
|
|
124
|
+
width: 3.75rem;
|
|
125
|
+
height: 0.375rem;
|
|
126
|
+
background-color: $CAP_G02;
|
|
127
|
+
border-radius: 0.1875rem;
|
|
128
|
+
z-index: 1070 + 1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&::after {
|
|
132
|
+
content: '';
|
|
133
|
+
position: absolute;
|
|
134
|
+
top: -0.9375rem;
|
|
135
|
+
right: 1.25rem;
|
|
136
|
+
width: 0.75rem;
|
|
137
|
+
height: 0.75rem;
|
|
138
|
+
background-color: $CAP_G02;
|
|
139
|
+
border-radius: 50%;
|
|
140
|
+
z-index: 1070 + 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Media queries for responsive behavior - iframe sizing now controlled by React
|
|
145
|
+
// @media (max-width: 768px) - Removed: sizing now handled by React inline styles
|
|
146
|
+
|
|
147
|
+
@media (max-width: 576px) {
|
|
148
|
+
|
|
149
|
+
.device-frame,
|
|
150
|
+
.preview-pane .device-frame {
|
|
151
|
+
transform: scale(0.8);
|
|
152
|
+
transform-origin: center top;
|
|
153
|
+
// iframe sizing now controlled by React inline styles
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.preview-loading {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: center;
|
|
161
|
+
height: 100%;
|
|
162
|
+
color: $CAP_G10;
|
|
163
|
+
|
|
164
|
+
.loading-content {
|
|
165
|
+
text-align: center;
|
|
166
|
+
|
|
167
|
+
.loading-icon {
|
|
168
|
+
font-size: 3rem;
|
|
169
|
+
margin-bottom: 1rem;
|
|
170
|
+
animation: pulse 2s infinite;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@keyframes pulse {
|
|
176
|
+
|
|
177
|
+
0%,
|
|
178
|
+
100% {
|
|
179
|
+
opacity: 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
50% {
|
|
183
|
+
opacity: 0.5;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.preview-empty {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
justify-content: center;
|
|
191
|
+
height: calc(100% - 2rem);
|
|
192
|
+
color: $CAP_G10;
|
|
193
|
+
text-align: center;
|
|
194
|
+
background-color: $CAP_WHITE;
|
|
195
|
+
border-radius: 0.25rem;
|
|
196
|
+
box-shadow: 0 0.0625rem 0.1875rem rgba($CAP_G01, 0.1);
|
|
197
|
+
margin: 0;
|
|
198
|
+
width: 100%;
|
|
199
|
+
|
|
200
|
+
.empty-content {
|
|
201
|
+
padding: 2.5rem 1.25rem;
|
|
202
|
+
|
|
203
|
+
.empty-icon {
|
|
204
|
+
font-size: 3rem;
|
|
205
|
+
margin-bottom: 1rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
p {
|
|
209
|
+
margin: 0.25rem 0;
|
|
210
|
+
color: $CAP_G10;
|
|
211
|
+
font-size: 0.875rem;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.preview-mode-info {
|
|
215
|
+
font-size: 0.75rem;
|
|
216
|
+
margin-top: 0.5rem;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreviewPane - Live preview panel with responsive device frames
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Live HTML/CSS/JavaScript preview
|
|
6
|
+
* - Responsive device frames (Desktop/Mobile/Mobile Device)
|
|
7
|
+
* - Sandboxed iframe for security
|
|
8
|
+
* - Real-time content updates
|
|
9
|
+
*
|
|
10
|
+
* Security:
|
|
11
|
+
* - Uses DOMPurify-based contentSanitizer.js for comprehensive XSS protection
|
|
12
|
+
* - Sanitizes all HTML content before rendering in iframe
|
|
13
|
+
* - Removes dangerous elements: <script>, <iframe>, <object>, <embed>, <applet>
|
|
14
|
+
* - Strips event handler attributes (onclick, onload, etc.)
|
|
15
|
+
* - Filters dangerous protocols (javascript:, data:, vbscript:)
|
|
16
|
+
* - Variant-aware sanitization (EMAIL vs INAPP with different security levels)
|
|
17
|
+
* - Iframe sandbox uses "allow-scripts" only (no allow-same-origin for security)
|
|
18
|
+
* - Content delivered via blob URLs to isolate iframe from parent origin
|
|
19
|
+
*
|
|
20
|
+
* Previous Security Issues (FIXED):
|
|
21
|
+
* - Removed insecure manual sanitizeJavaScript function that only escaped </script> tags
|
|
22
|
+
* - Replaced with robust DOMPurify-based sanitization for full XSS protection
|
|
23
|
+
* - Removed "allow-same-origin" from iframe sandbox to prevent same-origin access
|
|
24
|
+
* - Implemented blob URL delivery to maintain isolation while allowing script execution
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import React, { useMemo, useEffect, useRef } from 'react';
|
|
28
|
+
import PropTypes from 'prop-types';
|
|
29
|
+
import { injectIntl, intlShape } from 'react-intl';
|
|
30
|
+
|
|
31
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
32
|
+
|
|
33
|
+
// Security utilities
|
|
34
|
+
import { sanitizeHTML, SANITIZER_VARIANTS } from '../../utils/contentSanitizer';
|
|
35
|
+
|
|
36
|
+
// Components
|
|
37
|
+
import PreviewModeGroup from '../EditorToolbar/PreviewModeGroup';
|
|
38
|
+
import InAppPreviewPane from '../InAppPreviewPane';
|
|
39
|
+
|
|
40
|
+
// Context
|
|
41
|
+
import { useEditorContext } from '../common/EditorContext';
|
|
42
|
+
|
|
43
|
+
// Constants
|
|
44
|
+
import { PREVIEW_MODES, HTML_EDITOR_VARIANTS } from '../../constants';
|
|
45
|
+
import { LAYOUT_TYPES } from '../InAppPreviewPane/constants';
|
|
46
|
+
|
|
47
|
+
// Messages
|
|
48
|
+
import messages from '../../messages';
|
|
49
|
+
|
|
50
|
+
// Styles
|
|
51
|
+
import './_previewPane.scss';
|
|
52
|
+
|
|
53
|
+
const PreviewPane = ({
|
|
54
|
+
intl,
|
|
55
|
+
className = '',
|
|
56
|
+
isFullscreenMode = false,
|
|
57
|
+
isModalContext = false,
|
|
58
|
+
layoutType = LAYOUT_TYPES.MODAL
|
|
59
|
+
}) => {
|
|
60
|
+
const { content, layout, variant } = useEditorContext();
|
|
61
|
+
|
|
62
|
+
// Destructure for better readability
|
|
63
|
+
const { content: contentValue } = content || {};
|
|
64
|
+
const { viewMode, setViewMode, mobileWidth } = layout || {};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sanitize content for secure preview rendering
|
|
68
|
+
*
|
|
69
|
+
* This function uses the robust contentSanitizer.js utility which employs DOMPurify
|
|
70
|
+
* for comprehensive XSS protection. The sanitizer handles:
|
|
71
|
+
* - Script tag removal/escaping
|
|
72
|
+
* - Event handler attribute removal
|
|
73
|
+
* - Dangerous protocol filtering (javascript:, data:, etc.)
|
|
74
|
+
* - Malicious HTML structure prevention
|
|
75
|
+
*
|
|
76
|
+
* The variant is determined based on the editor context - EMAIL for email templates,
|
|
77
|
+
* INAPP for in-app content with more permissive multimedia support.
|
|
78
|
+
*/
|
|
79
|
+
const combinedContent = useMemo(() => {
|
|
80
|
+
if (!contentValue) {
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Determine sanitization variant based on editor variant
|
|
85
|
+
// Default to EMAIL for stricter security if variant is not available
|
|
86
|
+
const sanitizationVariant = variant === HTML_EDITOR_VARIANTS.INAPP
|
|
87
|
+
? SANITIZER_VARIANTS.INAPP
|
|
88
|
+
: SANITIZER_VARIANTS.EMAIL;
|
|
89
|
+
|
|
90
|
+
// Use the robust contentSanitizer with DOMPurify for comprehensive XSS protection
|
|
91
|
+
const sanitizationResult = sanitizeHTML(contentValue, sanitizationVariant);
|
|
92
|
+
|
|
93
|
+
// Log security warnings in development for debugging
|
|
94
|
+
if (process.env.NODE_ENV === 'development' && sanitizationResult.warnings.length > 0) {
|
|
95
|
+
console.warn('PreviewPane: Content sanitization warnings:', sanitizationResult.warnings);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Log removed elements in development for security awareness
|
|
99
|
+
if (process.env.NODE_ENV === 'development' && sanitizationResult.removedElements.length > 0) {
|
|
100
|
+
console.warn('PreviewPane: Removed potentially unsafe elements:', sanitizationResult.removedElements);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return sanitizationResult.sanitized;
|
|
104
|
+
}, [contentValue, variant]);
|
|
105
|
+
|
|
106
|
+
// Create blob URL for secure iframe content delivery
|
|
107
|
+
// This isolates the iframe content from the parent origin while allowing script execution
|
|
108
|
+
const blobUrlRef = useRef(null);
|
|
109
|
+
const blobUrl = useMemo(() => {
|
|
110
|
+
// Clean up previous blob URL to prevent memory leaks
|
|
111
|
+
if (blobUrlRef.current) {
|
|
112
|
+
URL.revokeObjectURL(blobUrlRef.current);
|
|
113
|
+
blobUrlRef.current = null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Create new blob URL if we have content
|
|
117
|
+
if (combinedContent) {
|
|
118
|
+
const blob = new Blob([combinedContent], { type: 'text/html' });
|
|
119
|
+
blobUrlRef.current = URL.createObjectURL(blob);
|
|
120
|
+
return blobUrlRef.current;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
}, [combinedContent]);
|
|
125
|
+
|
|
126
|
+
// Cleanup blob URL on unmount
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
return () => {
|
|
129
|
+
if (blobUrlRef.current) {
|
|
130
|
+
URL.revokeObjectURL(blobUrlRef.current);
|
|
131
|
+
blobUrlRef.current = null;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
// Generate CSS classes based on view mode
|
|
137
|
+
const getIframeClasses = () => {
|
|
138
|
+
const baseClass = 'preview-pane__iframe';
|
|
139
|
+
const classes = [baseClass];
|
|
140
|
+
|
|
141
|
+
if (viewMode === PREVIEW_MODES.MOBILE) {
|
|
142
|
+
classes.push(`${baseClass}--mobile`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return classes.join(' ');
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Generate inline styles for responsive sizing based on view mode
|
|
149
|
+
const getPreviewStyles = () => {
|
|
150
|
+
const baseStyles = {};
|
|
151
|
+
|
|
152
|
+
// Apply sizing based on current view mode
|
|
153
|
+
if (viewMode === PREVIEW_MODES.MOBILE || viewMode === PREVIEW_MODES.MOBILE_DEVICE) {
|
|
154
|
+
// Use layout.mobileWidth or fallback to 375px
|
|
155
|
+
const width = mobileWidth || 375;
|
|
156
|
+
baseStyles.width = `${width}px`;
|
|
157
|
+
baseStyles.height = '100%'; // Fill container height
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return baseStyles;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const renderDeviceFrame = () => (
|
|
164
|
+
<iframe
|
|
165
|
+
src={blobUrl || 'about:blank'}
|
|
166
|
+
title={intl.formatMessage(messages.htmlPreview)}
|
|
167
|
+
className={getIframeClasses()}
|
|
168
|
+
style={getPreviewStyles()}
|
|
169
|
+
sandbox="allow-scripts"
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const renderEmptyState = () => (
|
|
174
|
+
<div className="preview-empty">
|
|
175
|
+
<div className="empty-content">
|
|
176
|
+
<div className="empty-icon">📝</div>
|
|
177
|
+
<p>{intl.formatMessage(messages.startTypingHtml)}</p>
|
|
178
|
+
<p className="preview-mode-info">
|
|
179
|
+
{intl.formatMessage(messages.previewMode)}: {
|
|
180
|
+
viewMode === PREVIEW_MODES.DESKTOP
|
|
181
|
+
? intl.formatMessage(messages.desktop)
|
|
182
|
+
: viewMode === PREVIEW_MODES.MOBILE
|
|
183
|
+
? intl.formatMessage(messages.mobile)
|
|
184
|
+
: intl.formatMessage(messages.mobileDevice)
|
|
185
|
+
}
|
|
186
|
+
</p>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Render InApp preview for InApp variant
|
|
192
|
+
if (variant === HTML_EDITOR_VARIANTS.INAPP) {
|
|
193
|
+
return (
|
|
194
|
+
<InAppPreviewPane
|
|
195
|
+
intl={intl}
|
|
196
|
+
className={className}
|
|
197
|
+
isFullscreenMode={isFullscreenMode}
|
|
198
|
+
isModalContext={isModalContext}
|
|
199
|
+
layoutType={layoutType}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Render Email preview for Email variant
|
|
205
|
+
return (
|
|
206
|
+
<div className={`preview-pane ${className}`}>
|
|
207
|
+
<CapRow className="preview-pane__mode-controls">
|
|
208
|
+
<PreviewModeGroup
|
|
209
|
+
value={viewMode}
|
|
210
|
+
onChange={setViewMode}
|
|
211
|
+
/>
|
|
212
|
+
</CapRow>
|
|
213
|
+
|
|
214
|
+
<CapRow className="preview-pane__content">
|
|
215
|
+
{blobUrl ? renderDeviceFrame() : renderEmptyState()}
|
|
216
|
+
</CapRow>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
PreviewPane.propTypes = {
|
|
222
|
+
intl: intlShape.isRequired,
|
|
223
|
+
className: PropTypes.string,
|
|
224
|
+
isFullscreenMode: PropTypes.bool,
|
|
225
|
+
isModalContext: PropTypes.bool,
|
|
226
|
+
layoutType: PropTypes.oneOf(Object.values(LAYOUT_TYPES))
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export default injectIntl(PreviewPane);
|