@shoutem/ui 6.2.0 → 6.3.1-rc.0
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.
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { resolveDimensions } from '../services/Dimensions';
|
|
5
|
+
import Image from './Image';
|
|
6
|
+
|
|
7
|
+
const AttachmentRenderer = ({ id, type, style, attachments }) => {
|
|
8
|
+
if (!attachments) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (type === 'image') {
|
|
13
|
+
const image = _.find(attachments, { id });
|
|
14
|
+
|
|
15
|
+
if (image && image.src) {
|
|
16
|
+
const source = { uri: image.src };
|
|
17
|
+
const imageSize = { width: image.width, height: image.height };
|
|
18
|
+
const { height, width } = resolveDimensions(imageSize, style);
|
|
19
|
+
const resolvedStyle = { alignSelf: 'center', height, width };
|
|
20
|
+
|
|
21
|
+
return <Image source={source} key={id} style={resolvedStyle} />;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
AttachmentRenderer.propTypes = {
|
|
29
|
+
attachments: PropTypes.oneOf([PropTypes.array, PropTypes.object]),
|
|
30
|
+
id: PropTypes.any,
|
|
31
|
+
style: PropTypes.any,
|
|
32
|
+
type: PropTypes.any,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
AttachmentRenderer.defaultProps = {
|
|
36
|
+
id: undefined,
|
|
37
|
+
type: undefined,
|
|
38
|
+
attachments: undefined,
|
|
39
|
+
style: undefined,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default AttachmentRenderer;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Vimeo } from 'react-native-vimeo-iframe';
|
|
3
|
+
import YoutubePlayer from 'react-native-youtube-iframe';
|
|
4
|
+
import iframe from '@native-html/iframe-plugin';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
import { Text } from '../../components/Text';
|
|
7
|
+
import { View } from '../../components/View';
|
|
8
|
+
import isValidVideoFormat from '../services/isValidVideoFormat';
|
|
9
|
+
|
|
10
|
+
const IframeRenderer = props => {
|
|
11
|
+
const { tnode, shoutemStyle, unsupportedVideoFormatMessage } = props;
|
|
12
|
+
const url = tnode?.domNode?.attribs?.src;
|
|
13
|
+
|
|
14
|
+
if (url && !isValidVideoFormat(url)) {
|
|
15
|
+
const message =
|
|
16
|
+
unsupportedVideoFormatMessage || 'Unsupported video format.';
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<View
|
|
20
|
+
style={shoutemStyle.fallback}
|
|
21
|
+
styleName="vertical h-center v-center"
|
|
22
|
+
>
|
|
23
|
+
<Text>{message}</Text>
|
|
24
|
+
</View>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (url && (url.includes('youtube') || url.includes('youtu.be'))) {
|
|
29
|
+
const youtubeIdRegEx = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
|
30
|
+
const regExMatch = url.match(youtubeIdRegEx);
|
|
31
|
+
const youtubeId =
|
|
32
|
+
regExMatch && regExMatch[7].length === 11 ? regExMatch[7] : false;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<YoutubePlayer
|
|
36
|
+
webViewProps={{
|
|
37
|
+
renderToHardwareTextureAndroid: true,
|
|
38
|
+
}}
|
|
39
|
+
height={shoutemStyle.video.height}
|
|
40
|
+
videoId={youtubeId}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (url && url.includes('vimeo')) {
|
|
46
|
+
const vimeoIdRegEx = /(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|video\/|)(\d+)(?:[a-zA-Z0-9_\-]+)?/i;
|
|
47
|
+
const regExMatches = url.match(vimeoIdRegEx);
|
|
48
|
+
const vimeoId = regExMatches[1];
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Vimeo
|
|
52
|
+
videoId={vimeoId}
|
|
53
|
+
style={{ height: shoutemStyle.vimeoVideo.height }}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return iframe(props);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
IframeRenderer.propTypes = {
|
|
62
|
+
tnode: PropTypes.shape({
|
|
63
|
+
children: PropTypes.any,
|
|
64
|
+
}).isRequired,
|
|
65
|
+
shoutemStyle: PropTypes.any,
|
|
66
|
+
unsupportedVideoFormatMessage: PropTypes.string,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
IframeRenderer.defaultProps = {
|
|
70
|
+
shoutemStyle: undefined,
|
|
71
|
+
unsupportedVideoFormatMessage: undefined,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default IframeRenderer;
|
|
@@ -1,57 +1,23 @@
|
|
|
1
1
|
import React, { PureComponent } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import Html from 'react-native-render-html';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
cssStringToObject,
|
|
8
|
-
} from 'react-native-render-html/src/HTMLStyles';
|
|
9
|
-
import {
|
|
10
|
-
alterNode as tableAlterNode,
|
|
2
|
+
import { Linking } from 'react-native';
|
|
3
|
+
import Html, { defaultSystemFonts } from 'react-native-render-html';
|
|
4
|
+
import WebView from 'react-native-webview';
|
|
5
|
+
import { iframeModel } from '@native-html/iframe-plugin';
|
|
6
|
+
import table, {
|
|
11
7
|
cssRulesFromSpecs,
|
|
12
8
|
defaultTableStylesSpecs,
|
|
13
9
|
IGNORED_TAGS,
|
|
14
|
-
|
|
15
|
-
} from '
|
|
16
|
-
import WebView from 'react-native-webview';
|
|
17
|
-
import YoutubePlayer from 'react-native-youtube-iframe';
|
|
10
|
+
tableModel,
|
|
11
|
+
} from '@native-html/table-plugin';
|
|
18
12
|
import autoBindReact from 'auto-bind/react';
|
|
19
13
|
import _ from 'lodash';
|
|
20
14
|
import PropTypes from 'prop-types';
|
|
21
15
|
import { connectStyle } from '@shoutem/theme';
|
|
22
|
-
import { Text } from '../../components/Text';
|
|
23
16
|
import { View } from '../../components/View';
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
function resolveMaxWidth(style) {
|
|
29
|
-
// parentContainerPadding - padding between screen & SimpleHtml. Parent container
|
|
30
|
-
// can have it's own padding. If it does, we have to include it in calculation,
|
|
31
|
-
// otherwise maxWidth will be more than max & images will go over the edge
|
|
32
|
-
const parentContainerPadding = style.outerPadding || 0;
|
|
33
|
-
const paddingValue =
|
|
34
|
-
style.container.paddingLeft ||
|
|
35
|
-
0 + style.container.paddingRight ||
|
|
36
|
-
0 + parentContainerPadding;
|
|
37
|
-
|
|
38
|
-
return Dimensions.get('window').width - paddingValue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function resolveDimensions(objectToResize, style) {
|
|
42
|
-
const { width, height } = objectToResize;
|
|
43
|
-
|
|
44
|
-
if (!width || !height) {
|
|
45
|
-
return { width, height };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const maxWidth = resolveMaxWidth(style);
|
|
49
|
-
const objectToResizeRatio = height / width;
|
|
50
|
-
const resolvedWidth = width > maxWidth ? maxWidth : width;
|
|
51
|
-
const resolvedHeight = Math.round(resolvedWidth * objectToResizeRatio);
|
|
52
|
-
|
|
53
|
-
return { width: resolvedWidth, height: resolvedHeight };
|
|
54
|
-
}
|
|
17
|
+
import { resolveMaxWidth } from '../services/Dimensions';
|
|
18
|
+
import { onElement } from '../services/DomVisitors';
|
|
19
|
+
import AttachmentRenderer from './AttachmentRenderer';
|
|
20
|
+
import IframeRenderer from './IframeRenderer';
|
|
55
21
|
|
|
56
22
|
class SimpleHtml extends PureComponent {
|
|
57
23
|
constructor(props) {
|
|
@@ -60,7 +26,7 @@ class SimpleHtml extends PureComponent {
|
|
|
60
26
|
autoBindReact(this);
|
|
61
27
|
}
|
|
62
28
|
|
|
63
|
-
onLinkPress(
|
|
29
|
+
onLinkPress(_evt, href) {
|
|
64
30
|
const { customHandleLinkPress } = this.props;
|
|
65
31
|
|
|
66
32
|
return customHandleLinkPress
|
|
@@ -68,190 +34,16 @@ class SimpleHtml extends PureComponent {
|
|
|
68
34
|
: Linking.openURL(href);
|
|
69
35
|
}
|
|
70
36
|
|
|
71
|
-
/**
|
|
72
|
-
* Removes empty (therefore invalid) style attribute properties
|
|
73
|
-
* Scales down objects with specified width and height if too large
|
|
74
|
-
* Removes padding and height from suneditor for 'figure' tag that it nests
|
|
75
|
-
* video iframe tags in when video format is unsupported
|
|
76
|
-
*/
|
|
77
|
-
alterNode(node) {
|
|
78
|
-
const { customAlterNode, style } = this.props;
|
|
79
|
-
|
|
80
|
-
const styleAttrib = _.get(node, 'attribs.style', '').trim();
|
|
81
|
-
|
|
82
|
-
if (customAlterNode && _.isFunction(customAlterNode)) {
|
|
83
|
-
const resolvedNode = customAlterNode(
|
|
84
|
-
node,
|
|
85
|
-
cssObjectToString,
|
|
86
|
-
cssStringToObject,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
if (resolvedNode) {
|
|
90
|
-
return resolvedNode;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (node.name === 'table') {
|
|
95
|
-
return tableAlterNode(node);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (node.name === 'figure') {
|
|
99
|
-
const firstChild = _.head(node.children);
|
|
100
|
-
|
|
101
|
-
if (firstChild.name === 'iframe') {
|
|
102
|
-
const nodeStyle = cssStringToObject(styleAttrib);
|
|
103
|
-
const source = _.get(firstChild, 'attribs.src', '');
|
|
104
|
-
|
|
105
|
-
const resolvedNodeStyle = isValidVideoFormat(source)
|
|
106
|
-
? _.omit(nodeStyle, ['height', 'padding-bottom'])
|
|
107
|
-
: nodeStyle;
|
|
108
|
-
|
|
109
|
-
node.attribs.style = cssObjectToString(resolvedNodeStyle);
|
|
110
|
-
|
|
111
|
-
return node;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const nodeWidth = node.attribs?.width;
|
|
116
|
-
|
|
117
|
-
if (!styleAttrib && !nodeWidth) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const nodeHeight = node.attribus?.height;
|
|
122
|
-
const nodeDimensions = { width: nodeWidth, height: nodeHeight };
|
|
123
|
-
const { width, height } = resolveDimensions(nodeDimensions, style);
|
|
124
|
-
|
|
125
|
-
const nodeStyle = cssStringToObject(styleAttrib);
|
|
126
|
-
const invalidKeys = getEmptyObjectKeys(nodeStyle);
|
|
127
|
-
|
|
128
|
-
if (invalidKeys.length || nodeWidth) {
|
|
129
|
-
const styleFiltered = _.omit(style, invalidKeys);
|
|
130
|
-
node.attribs.style = cssObjectToString(styleFiltered);
|
|
131
|
-
node.attribs.width = width;
|
|
132
|
-
node.attribs.height = height;
|
|
133
|
-
|
|
134
|
-
return node;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Removes child break node in case when it's positioned at the end
|
|
142
|
-
* of parent div node and div has content in it.
|
|
143
|
-
* Example:
|
|
144
|
-
* <div>first line<br></div>
|
|
145
|
-
* <div>second line<br></div>
|
|
146
|
-
*
|
|
147
|
-
* If br is left as is, <Html> will show empty line under div, not just
|
|
148
|
-
* break into new line
|
|
149
|
-
*/
|
|
150
|
-
alterChildren(node) {
|
|
151
|
-
const { children, name } = node;
|
|
152
|
-
|
|
153
|
-
if ((name === 'div' || name === 'p') && children && children.length > 1) {
|
|
154
|
-
const brNodes = _.filter(children, { name: 'br' });
|
|
155
|
-
const lastBrNode = _.last(brNodes);
|
|
156
|
-
|
|
157
|
-
if (lastBrNode && lastBrNode.next === null) {
|
|
158
|
-
const lastBrNodeIndex = _.indexOf(children, lastBrNode);
|
|
159
|
-
return children.splice(0, lastBrNodeIndex);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return children;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
renderUnorderedListPrefix() {
|
|
167
|
-
const { style } = this.props;
|
|
168
|
-
|
|
169
|
-
return <Text style={style.prefix}>• </Text>;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
renderOrderedListPrefix(
|
|
173
|
-
htmlAttribs,
|
|
174
|
-
children,
|
|
175
|
-
convertedCSSStyles,
|
|
176
|
-
passProps,
|
|
177
|
-
) {
|
|
178
|
-
const { style } = this.props;
|
|
179
|
-
|
|
180
|
-
return <Text style={style.prefix}>{passProps.index + 1}. </Text>;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Custom rendered method for handling <attachment> tags
|
|
185
|
-
* Currently only supports rendering image attachments.
|
|
186
|
-
*/
|
|
187
|
-
renderAttachments({ id, type }) {
|
|
188
|
-
const { attachments, style } = this.props;
|
|
189
|
-
|
|
190
|
-
if (!attachments) {
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (type === 'image') {
|
|
195
|
-
const image = _.find(attachments, { id });
|
|
196
|
-
|
|
197
|
-
if (image && image.src) {
|
|
198
|
-
const source = { uri: image.src };
|
|
199
|
-
const imageSize = { width: image.width, height: image.height };
|
|
200
|
-
const { height, width } = resolveDimensions(imageSize, style);
|
|
201
|
-
const resolvedStyle = { alignSelf: 'center', height, width };
|
|
202
|
-
|
|
203
|
-
return <Image source={source} key={id} style={resolvedStyle} />;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
renderIframe(htmlAttribs, children, convertedCSSStyles, passProps) {
|
|
211
|
-
const { style } = this.props;
|
|
212
|
-
|
|
213
|
-
const url = htmlAttribs?.src;
|
|
214
|
-
|
|
215
|
-
if (url && !isValidVideoFormat(url)) {
|
|
216
|
-
const { unsupportedVideoFormatMessage } = this.props;
|
|
217
|
-
|
|
218
|
-
const message =
|
|
219
|
-
unsupportedVideoFormatMessage || 'Unsupported video format.';
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
<View
|
|
223
|
-
style={style.fallback}
|
|
224
|
-
styleName="vertical h-center v-center"
|
|
225
|
-
key={passProps.key}
|
|
226
|
-
>
|
|
227
|
-
<Text>{message}</Text>
|
|
228
|
-
</View>
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (url && (url.includes('youtube') || url.includes('youtu.be'))) {
|
|
233
|
-
const youtubeIdRegEx = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
|
234
|
-
const regExMatch = url.match(youtubeIdRegEx);
|
|
235
|
-
const youtubeId =
|
|
236
|
-
regExMatch && regExMatch[7].length === 11 ? regExMatch[7] : false;
|
|
237
|
-
|
|
238
|
-
return (
|
|
239
|
-
<YoutubePlayer
|
|
240
|
-
webViewProps={{
|
|
241
|
-
renderToHardwareTextureAndroid: true,
|
|
242
|
-
}}
|
|
243
|
-
height={style.video.height}
|
|
244
|
-
key={passProps.key}
|
|
245
|
-
videoId={youtubeId}
|
|
246
|
-
/>
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return iframe(htmlAttribs, children, convertedCSSStyles, passProps);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
37
|
render() {
|
|
254
|
-
const {
|
|
38
|
+
const {
|
|
39
|
+
style,
|
|
40
|
+
body,
|
|
41
|
+
customTagStyles,
|
|
42
|
+
unsupportedVideoFormatMessage,
|
|
43
|
+
attachments,
|
|
44
|
+
domVisitors,
|
|
45
|
+
...otherProps
|
|
46
|
+
} = this.props;
|
|
255
47
|
|
|
256
48
|
const maxWidth = resolveMaxWidth(style);
|
|
257
49
|
|
|
@@ -260,11 +52,6 @@ class SimpleHtml extends PureComponent {
|
|
|
260
52
|
...customTagStyles,
|
|
261
53
|
};
|
|
262
54
|
|
|
263
|
-
const listPrefixRenderers = {
|
|
264
|
-
ul: this.renderUnorderedListPrefix,
|
|
265
|
-
ol: this.renderOrderedListPrefix,
|
|
266
|
-
};
|
|
267
|
-
|
|
268
55
|
const tableStyle = _.get(style, 'table', {});
|
|
269
56
|
const tableCssStyle = _.get(style, 'tableCss', '');
|
|
270
57
|
const cssStyle = cssRulesFromSpecs({
|
|
@@ -274,24 +61,50 @@ class SimpleHtml extends PureComponent {
|
|
|
274
61
|
const cssRules = `${cssStyle}${tableCssStyle}`;
|
|
275
62
|
|
|
276
63
|
const customRenderers = {
|
|
277
|
-
iframe:
|
|
278
|
-
|
|
279
|
-
|
|
64
|
+
iframe: props => (
|
|
65
|
+
<IframeRenderer
|
|
66
|
+
{...props}
|
|
67
|
+
shoutemStyle={style}
|
|
68
|
+
unsupportedVideoFormatMessage={unsupportedVideoFormatMessage}
|
|
69
|
+
/>
|
|
70
|
+
),
|
|
71
|
+
table,
|
|
72
|
+
attachment: props => (
|
|
73
|
+
<AttachmentRenderer
|
|
74
|
+
{...props}
|
|
75
|
+
attachments={attachments}
|
|
76
|
+
style={style}
|
|
77
|
+
/>
|
|
78
|
+
),
|
|
280
79
|
};
|
|
281
80
|
|
|
282
81
|
const htmlProps = {
|
|
283
|
-
html: body,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
tagsStyles: tagStyles,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
alterNode: this.alterNode,
|
|
291
|
-
alterChildren: this.alterChildren,
|
|
292
|
-
listsPrefixesRenderers: listPrefixRenderers,
|
|
82
|
+
source: { html: body },
|
|
83
|
+
computeEmbeddedMaxWidth: () => maxWidth,
|
|
84
|
+
contentWidth: maxWidth,
|
|
85
|
+
tagsStyles: _.omitBy(tagStyles, tagStyle => !tagStyle),
|
|
86
|
+
systemFonts: [...defaultSystemFonts, style.baseFont.fontFamily],
|
|
87
|
+
baseStyle: style.baseFont,
|
|
88
|
+
ignoredStyles: ['fontFamily', 'letterSpacing', 'transform'],
|
|
293
89
|
renderers: customRenderers,
|
|
90
|
+
renderersProps: {
|
|
91
|
+
table: {
|
|
92
|
+
cssRules,
|
|
93
|
+
},
|
|
94
|
+
a: { onPress: this.onLinkPress },
|
|
95
|
+
iframe: {
|
|
96
|
+
webViewProps: {
|
|
97
|
+
renderToHardwareTextureAndroid: true,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
customHTMLElementModels: {
|
|
102
|
+
table: tableModel,
|
|
103
|
+
iframe: iframeModel,
|
|
104
|
+
},
|
|
105
|
+
WebView,
|
|
294
106
|
ignoredTags: IGNORED_TAGS,
|
|
107
|
+
domVisitors: { onElement },
|
|
295
108
|
};
|
|
296
109
|
|
|
297
110
|
return (
|
|
@@ -309,6 +122,7 @@ SimpleHtml.propTypes = {
|
|
|
309
122
|
customAlterNode: PropTypes.func,
|
|
310
123
|
customHandleLinkPress: PropTypes.func,
|
|
311
124
|
customTagStyles: PropTypes.object,
|
|
125
|
+
domVisitors: PropTypes.object,
|
|
312
126
|
unsupportedVideoFormatMessage: PropTypes.string,
|
|
313
127
|
};
|
|
314
128
|
|
|
@@ -319,6 +133,7 @@ SimpleHtml.defaultProps = {
|
|
|
319
133
|
customHandleLinkPress: undefined,
|
|
320
134
|
customTagStyles: undefined,
|
|
321
135
|
unsupportedVideoFormatMessage: undefined,
|
|
136
|
+
domVisitors: { onElement },
|
|
322
137
|
};
|
|
323
138
|
|
|
324
139
|
export default connectStyle('shoutem.ui.SimpleHtml')(SimpleHtml);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Dimensions } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export function resolveMaxWidth(style) {
|
|
4
|
+
// parentContainerPadding - padding between screen & SimpleHtml. Parent container
|
|
5
|
+
// can have it's own padding. If it does, we have to include it in calculation,
|
|
6
|
+
// otherwise maxWidth will be more than max & images will go over the edge
|
|
7
|
+
const parentContainerPadding = style.outerPadding || 0;
|
|
8
|
+
const paddingValue =
|
|
9
|
+
style.container.paddingLeft ||
|
|
10
|
+
0 + style.container.paddingRight ||
|
|
11
|
+
0 + parentContainerPadding;
|
|
12
|
+
|
|
13
|
+
return Dimensions.get('window').width - paddingValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveDimensions(objectToResize, style) {
|
|
17
|
+
const { width, height } = objectToResize;
|
|
18
|
+
|
|
19
|
+
if (!width || !height) {
|
|
20
|
+
return { width, height };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const maxWidth = resolveMaxWidth(style);
|
|
24
|
+
const objectToResizeRatio = height / width;
|
|
25
|
+
const resolvedWidth = width > maxWidth ? maxWidth : width;
|
|
26
|
+
const resolvedHeight = Math.round(resolvedWidth * objectToResizeRatio);
|
|
27
|
+
|
|
28
|
+
return { width: resolvedWidth, height: resolvedHeight };
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
|
|
3
|
+
// Shoutem RTE component wraps any video attachment (hosted inside iframe) with <figure>
|
|
4
|
+
// element. That element contains style that breaks the SimpleHtml UI - enormous blank
|
|
5
|
+
// space below attachment.
|
|
6
|
+
// This function removes height and padding-bottom styles from figure to fix this UI issue.
|
|
7
|
+
export const removeShoutemRteVideoAttachmentWrapper = element => {
|
|
8
|
+
if (
|
|
9
|
+
element.name === 'figure' &&
|
|
10
|
+
element.children.length > 0 &&
|
|
11
|
+
// <figure> can have multiple generated children too - type:text. Probably caused by empty space between
|
|
12
|
+
// figure and iframe elements in generated HTML.
|
|
13
|
+
_.some(element.children, ({ name }) => name === 'iframe')
|
|
14
|
+
) {
|
|
15
|
+
// Remove height and padding-bottom styles because they break the UI
|
|
16
|
+
element.attribs.style = element.attribs.style
|
|
17
|
+
? element.attribs.style
|
|
18
|
+
.replace(/height:[^;]+;?/, '')
|
|
19
|
+
.replace(/padding-bottom:[^;]+;?/, '')
|
|
20
|
+
: '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return element;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Collection of node (HTML element) alteration callbacks. Define any html element
|
|
28
|
+
* manipulations inside.
|
|
29
|
+
* @param {*} element - HTML element to check against and manipulate if necessary
|
|
30
|
+
* @returns Manipulated HTML element
|
|
31
|
+
*/
|
|
32
|
+
export const onElement = element => {
|
|
33
|
+
return removeShoutemRteVideoAttachmentWrapper(element);
|
|
34
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shoutem/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.1-rc.0",
|
|
4
4
|
"description": "Styleable set of components for React Native applications",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint .",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"@shoutem/animation": "~0.15.0",
|
|
17
17
|
"@shoutem/eslint-config-react": "~1.0.6",
|
|
18
18
|
"@shoutem/theme": "~0.13.0",
|
|
19
|
+
"@native-html/iframe-plugin": "2.6.1",
|
|
20
|
+
"@native-html/table-plugin": "5.3.1",
|
|
19
21
|
"auto-bind": "4.0.0",
|
|
20
22
|
"babel-plugin-transform-decorators-legacy": "1.3.5",
|
|
21
23
|
"buffer": "5.6.0",
|
|
@@ -33,8 +35,7 @@
|
|
|
33
35
|
"react-native-lightbox-v2": "0.9.0",
|
|
34
36
|
"react-native-linear-gradient": "2.6.2",
|
|
35
37
|
"react-native-modal": "13.0.1",
|
|
36
|
-
"react-native-render-html": "
|
|
37
|
-
"react-native-render-html-table-bridge": "0.6.0",
|
|
38
|
+
"react-native-render-html": "6.3.4",
|
|
38
39
|
"react-native-svg": "12.3.0",
|
|
39
40
|
"react-native-svg-transformer": "1.0.0",
|
|
40
41
|
"react-native-toast-message": "2.1.5",
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"path": "0.12.7",
|
|
67
68
|
"prettier": "1.19.1",
|
|
68
69
|
"react": "17.0.2",
|
|
69
|
-
"react-dom": "
|
|
70
|
+
"react-dom": "17.0.2",
|
|
70
71
|
"react-native": "0.68.2",
|
|
71
72
|
"react-test-renderer": "17.0.2"
|
|
72
73
|
},
|
package/theme.js
CHANGED
|
@@ -2327,6 +2327,10 @@ export default () => {
|
|
|
2327
2327
|
height: responsiveWidth(194),
|
|
2328
2328
|
paddingBottom: resolveVariable('smallGutter'),
|
|
2329
2329
|
},
|
|
2330
|
+
// Just enough to display portrait mode Vimeo videos and all Vimeo player controls
|
|
2331
|
+
vimeoVideo: {
|
|
2332
|
+
height: responsiveHeight(220),
|
|
2333
|
+
},
|
|
2330
2334
|
fallback: {
|
|
2331
2335
|
width: window.width,
|
|
2332
2336
|
height: 40,
|