New markdown (#306)

Our current markdown is causing a lot of issues on Android devices, since it wraps everything inside a Text component.
On Android, Text doesn't support View as a child.
This PR adds react-native-markdown-renderer, that uses View as wrapper and may be better.
This commit is contained in:
Diego Mello 2018-05-29 14:09:20 -03:00 committed by Guilherme Gazzo
parent 061c313e3f
commit f61a57bb30
17 changed files with 309 additions and 229 deletions

View File

@ -268,7 +268,6 @@ workflows:
- ios-build:
requires:
- lint-testunit
- e2e-test
- ios-testflight:
requires:
- ios-build
@ -285,4 +284,3 @@ workflows:
- android-build:
requires:
- lint-testunit
- e2e-test

View File

@ -160,9 +160,9 @@ exports[`render channel 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>
@ -332,9 +332,9 @@ exports[`render no icon 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>
@ -504,9 +504,9 @@ exports[`render private group 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>
@ -744,9 +744,9 @@ exports[`render unread +999 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
>
@ -1006,9 +1006,9 @@ exports[`render unread 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
>
@ -1268,9 +1268,9 @@ exports[`renders correctly 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>

View File

@ -551,9 +551,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>
@ -789,9 +789,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>
@ -1023,9 +1023,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
>
@ -1284,9 +1284,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
>
@ -1541,9 +1541,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
>
@ -1798,9 +1798,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
>
@ -2055,9 +2055,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
>
@ -2312,9 +2312,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
>
@ -2569,9 +2569,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>
@ -2803,9 +2803,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>
@ -3037,9 +3037,9 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = `
<View
style={
Object {
"alignItems": "flex-end",
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "flex-end",
}
}
/>

View File

@ -1,98 +1,13 @@
import React from 'react';
import { Text, StyleSheet, ViewPropTypes } from 'react-native';
import { Text, Platform } from 'react-native';
import PropTypes from 'prop-types';
import EasyMarkdown from 'react-native-easy-markdown'; // eslint-disable-line
import SimpleMarkdown from 'simple-markdown';
import { emojify } from 'react-emojione';
import { connect } from 'react-redux';
import MarkdownRenderer, { PluginContainer } from 'react-native-markdown-renderer';
import MarkdownFlowdock from 'markdown-it-flowdock';
import styles from './styles';
import CustomEmoji from '../EmojiPicker/CustomEmoji';
const BlockCode = ({ node, state }) => (
<Text
key={state.key}
style={styles.codeStyle}
>
{node.content}
</Text>
);
const mentionStyle = { color: '#13679a' };
const defaultRules = {
username: {
order: -1,
match: SimpleMarkdown.inlineRegex(/^@[0-9a-zA-Z-_.]+/),
parse: capture => ({ content: capture[0] }),
react: (node, output, state) => ({
type: 'custom',
key: state.key,
props: {
children: (
<Text
key={state.key}
style={mentionStyle}
onPress={() => alert('Username')}
>
{node.content}
</Text>
)
}
})
},
heading: {
order: -2,
match: SimpleMarkdown.inlineRegex(/^#[0-9a-zA-Z-_.]+/),
parse: capture => ({ content: capture[0] }),
react: (node, output, state) => ({
type: 'custom',
key: state.key,
props: {
children: (
<Text
key={state.key}
style={mentionStyle}
onPress={() => alert('Room')}
>
{node.content}
</Text>
)
}
})
},
fence: {
order: -3,
match: SimpleMarkdown.blockRegex(/^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/),
parse: capture => ({
lang: capture[2] || undefined,
content: capture[3]
}),
react: (node, output, state) => ({
type: 'custom',
key: state.key,
props: {
children: (
<BlockCode key={state.key} node={node} state={state} />
)
}
})
},
blockCode: {
order: -4,
match: SimpleMarkdown.blockRegex(/^(```)\s*([\s\S]*?[^`])\s*\1(?!```)/),
parse: capture => ({ content: capture[2] }),
react: (node, output, state) => ({
type: 'custom',
key: state.key,
props: {
children: (
<BlockCode key={state.key} node={node} state={state} />
)
}
})
}
};
const codeStyle = StyleSheet.flatten(styles.codeStyle);
import MarkdownEmojiPlugin from './MarkdownEmojiPlugin';
// Support <http://link|Text>
const formatText = text =>
@ -110,49 +25,64 @@ export default class Markdown extends React.Component {
}
render() {
const {
msg, customEmojis = {}, style, markdownStyle, customRules, renderInline
msg, customEmojis, style, rules
} = this.props;
if (!msg) {
return null;
}
let m = formatText(msg);
m = emojify(m, { output: 'unicode' });
const s = StyleSheet.flatten(style);
return (
<EasyMarkdown
style={{ marginBottom: 0, ...s }}
markdownStyles={{ code: codeStyle, ...markdownStyle }}
<MarkdownRenderer
rules={{
customEmoji: {
order: -5,
match: SimpleMarkdown.inlineRegex(/^:([0-9a-zA-Z-_.]+):/),
parse: capture => ({ content: capture }),
react: (node, output, state) => {
const element = {
type: 'custom',
key: state.key,
props: {
children: <Text key={state.key}>{node.content[0]}</Text>
}
};
const content = node.content[1];
...Platform.OS === 'android' ? {} : {
paragraph: (node, children) => (
<Text key={node.key} style={styles.paragraph}>
{children}
</Text>
)
},
mention: node => (
<Text key={node.key} onPress={() => alert(`Username @${ node.content }`)} style={styles.mention}>
@{node.content}
</Text>
),
hashtag: node => (
<Text key={node.key} onPress={() => alert(`Room #${ node.content }`)} style={styles.mention}>
#{node.content}
</Text>
),
emoji: (node) => {
if (node.children && node.children.length && node.children[0].children && node.children[0].children.length) {
const { content } = node.children[0].children[0];
const emojiExtension = customEmojis[content];
if (emojiExtension) {
const emoji = { extension: emojiExtension, content };
element.props.children = (
<CustomEmoji key={state.key} style={styles.customEmoji} emoji={emoji} />
);
return <CustomEmoji key={node.key} style={styles.customEmoji} emoji={emoji} />;
}
return element;
return <Text key={node.key}>:{content}:</Text>;
}
return null;
},
...defaultRules,
...customRules
...rules
}}
renderInline={renderInline}
style={{
paragraph: styles.paragraph,
codeInline: {
borderWidth: 1,
borderColor: '#CCCCCC',
backgroundColor: '#f5f5f5',
padding: 2,
borderRadius: 4
},
...style
}}
plugins={[
new PluginContainer(MarkdownFlowdock),
new PluginContainer(MarkdownEmojiPlugin)
]}
>{m}
</EasyMarkdown>
</MarkdownRenderer>
);
}
}
@ -160,14 +90,6 @@ export default class Markdown extends React.Component {
Markdown.propTypes = {
msg: PropTypes.string,
customEmojis: PropTypes.object,
// eslint-disable-next-line react/no-typos
style: ViewPropTypes.style,
markdownStyle: PropTypes.object,
customRules: PropTypes.object,
renderInline: PropTypes.bool
};
BlockCode.propTypes = {
node: PropTypes.object,
state: PropTypes.object
style: PropTypes.any,
rules: PropTypes.object
};

View File

@ -0,0 +1,78 @@
export default function(md) {
function tokenize(state, silent) {
let token;
const start = state.pos;
const marker = state.src.charCodeAt(start);
if (silent) {
return false;
}
// :
if (marker !== 58) {
return false;
}
const scanned = state.scanDelims(state.pos, true);
const len = scanned.length;
const ch = String.fromCharCode(marker);
for (let i = 0; i < len; i += 1) {
token = state.push('text', '', 0);
token.content = ch;
state.delimiters.push({
marker,
jump: i,
token: state.tokens.length - 1,
level: state.level,
end: -1,
open: scanned.can_open,
close: scanned.can_close
});
}
state.pos += scanned.length;
return true;
}
function postProcess(state) {
let startDelim;
let endDelim;
let token;
const { delimiters } = state;
const max = delimiters.length;
for (const i = 0; i < max; i++) { // eslint-disable-line
startDelim = delimiters[i];
// :
if (startDelim.marker !== 58) {
continue; // eslint-disable-line
}
if (startDelim.end === -1) {
continue; // eslint-disable-line
}
endDelim = delimiters[startDelim.end];
token = state.tokens[startDelim.token];
token.type = 'emoji_open';
token.tag = 'emoji';
token.nesting = 1;
token.markup = ':';
token.content = '';
token = state.tokens[endDelim.token];
token.type = 'emoji_close';
token.tag = 'emoji';
token.nesting = -1;
token.markup = ':';
token.content = '';
}
}
md.inline.ruler.before('emphasis', 'emoji', tokenize);
md.inline.ruler2.before('emphasis', 'emoji', postProcess);
}

View File

@ -97,5 +97,16 @@ export default StyleSheet.create({
},
broadcastButtonText: {
color: '#1d74f5'
},
mention: {
color: '#13679a'
},
paragraph: {
marginTop: 0,
marginBottom: 0,
flexWrap: 'wrap',
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'flex-start'
}
});

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { View, Text, StyleSheet, ViewPropTypes } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { connect } from 'react-redux';
import SimpleMarkdown from 'simple-markdown';
// import SimpleMarkdown from 'simple-markdown';
import Avatar from '../containers/Avatar';
import Status from '../containers/status';
@ -65,10 +65,10 @@ const styles = StyleSheet.create({
},
row: {
// width: '100%',
// flex: 1,
flex: 1,
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'flex-end'
alignItems: 'center'
// justifyContent: 'flex-end'
},
firstRow: {
width: '100%',
@ -98,36 +98,36 @@ const styles = StyleSheet.create({
marginTop: 3
}
});
const markdownStyle = { block: { marginBottom: 0, flexWrap: 'wrap', flexDirection: 'row' } };
// const markdownStyle = { block: { marginBottom: 0, flexWrap: 'wrap', flexDirection: 'row' } };
const parseInline = (parse, content, state) => {
const isCurrentlyInline = state.inline || false;
state.inline = true;
const result = parse(content, state);
state.inline = isCurrentlyInline;
return result;
};
const parseCaptureInline = (capture, parse, state) => ({ content: parseInline(parse, capture[1], state) });
const customRules = {
strong: {
order: -4,
match: SimpleMarkdown.inlineRegex(/^\*\*([\s\S]+?)\*\*(?!\*)/),
parse: parseCaptureInline,
react: (node, output, state) => ({
type: 'strong',
key: state.key,
props: {
children: output(node.content, state)
}
})
},
text: {
order: -3,
match: SimpleMarkdown.inlineRegex(/^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n\n| {2,}\n|\w+:\S|$)/),
parse: capture => ({ content: capture[0] }),
react: node => node.content
}
};
// const parseInline = (parse, content, state) => {
// const isCurrentlyInline = state.inline || false;
// state.inline = true;
// const result = parse(content, state);
// state.inline = isCurrentlyInline;
// return result;
// };
// const parseCaptureInline = (capture, parse, state) => ({ content: parseInline(parse, capture[1], state) });
// const customRules = {
// strong: {
// order: -4,
// match: SimpleMarkdown.inlineRegex(/^\*\*([\s\S]+?)\*\*(?!\*)/),
// parse: parseCaptureInline,
// react: (node, output, state) => ({
// type: 'strong',
// key: state.key,
// props: {
// children: output(node.content, state)
// }
// })
// },
// text: {
// order: -3,
// match: SimpleMarkdown.inlineRegex(/^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n\n| {2,}\n|\w+:\S|$)/),
// parse: capture => ({ content: capture[0] }),
// react: node => node.content
// }
// };
const renderNumber = (unread, userMentions) => {
if (!unread || unread <= 0) {
@ -283,11 +283,23 @@ export default class RoomItem extends React.Component {
<View style={styles.row}>
<Markdown
msg={this.lastMessage}
style={styles.lastMessage}
markdownStyle={markdownStyle}
customRules={customRules}
renderInline
numberOfLines={1}
style={{
view: {
flex: 1
}
}}
rules={{
mention: node => (
<Text key={node.key}>
@{node.content}
</Text>
),
hashtag: node => (
<Text key={node.key}>
#{node.content}
</Text>
)
}}
/>
{renderNumber(unread, userMentions)}
</View>

View File

@ -13,7 +13,7 @@ const openMentionedMessagesRoom = function* openMentionedMessagesRoom({ rid, lim
newSub = yield RocketChat.subscribe('mentionedMessages', rid, limit);
yield put(readyMentionedMessages());
if (sub) {
sub.unsubscribe();
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {

View File

@ -13,7 +13,7 @@ const openPinnedMessagesRoom = function* openPinnedMessagesRoom({ rid, limit })
newSub = yield RocketChat.subscribe('pinnedMessages', rid, limit);
yield put(readyPinnedMessages());
if (sub) {
sub.unsubscribe();
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {

View File

@ -13,7 +13,7 @@ const openRoomFiles = function* openRoomFiles({ rid, limit }) {
newSub = yield RocketChat.subscribe('roomFiles', rid, limit);
yield put(readyRoomFiles());
if (sub) {
sub.unsubscribe();
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {

View File

@ -13,7 +13,7 @@ const openSnippetedMessagesRoom = function* openSnippetedMessagesRoom({ rid, lim
newSub = yield RocketChat.subscribe('snippetedMessages', rid, limit);
yield put(readySnippetedMessages());
if (sub) {
sub.unsubscribe();
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {

View File

@ -13,7 +13,7 @@ const openStarredMessagesRoom = function* openStarredMessagesRoom({ rid, limit }
newSub = yield RocketChat.subscribe('starredMessages', rid, limit);
yield put(readyStarredMessages());
if (sub) {
sub.unsubscribe();
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {

View File

@ -67,7 +67,7 @@ describe('Rooms list screen', () => {
await waitFor(element(by.id('rooms-list-view-search'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view-search'))).toBeVisible();
await element(by.id('rooms-list-view-search')).replaceText('rocket.cat');
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(10000);
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible();
await element(by.id('rooms-list-view-item-rocket.cat')).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(10000);
@ -77,8 +77,9 @@ describe('Rooms list screen', () => {
await element(by.id('header-back')).atIndex(0).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible();
await element(by.id('rooms-list-view-search')).replaceText('');
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toExist().withTimeout(60000);
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toExist();
});
// Usage - Sidebar

View File

@ -90,7 +90,7 @@ describe('Room info screen', () => {
});
it('should have name input', async() => {
await expect(element(by.id('room-info-edit-view-name'))).toBeVisible();
await expect(element(by.id('room-info-edit-view-name'))).toExist();
});
it('should have description input', async() => {

View File

@ -56,6 +56,8 @@ describe('Broadcast room', () => {
await element(by.id('login-view-password')).replaceText(data.alternateUserPassword);
await element(by.id('login-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await device.reloadReactNative(); // remove after fix logout
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible();
await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();

92
package-lock.json generated
View File

@ -1229,6 +1229,27 @@
}
}
},
"@types/markdown-it": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-0.0.4.tgz",
"integrity": "sha512-FWR7QB7EqBRq1s9BMk0ccOSOuRLfVEWYpHQYpFPaXtCoqN6dJx2ttdsdQbUxLLnAlKpYeVjveGGhQ3583TTa7g=="
},
"@types/react": {
"version": "16.3.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.3.14.tgz",
"integrity": "sha512-wNUGm49fPl7eE2fnYdF0v5vSOrUMdKMQD/4NwtQRnb6mnPwtkhabmuFz37eq90+hhyfz0pWd38jkZHOcaZ6LGw==",
"requires": {
"csstype": "2.5.2"
}
},
"@types/react-native": {
"version": "0.55.15",
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.55.15.tgz",
"integrity": "sha512-AEnb2qacurrUL8A1EQknPKzJUXMtliPNRkd+xa4J/joUbsFen3aynVkYi+OPZ2cyomB+FWz+vv9uKCxURkgChQ==",
"requires": {
"@types/react": "16.3.14"
}
},
"@zamotany/react-proxy": {
"version": "3.0.0-alpha.4",
"resolved": "https://registry.npmjs.org/@zamotany/react-proxy/-/react-proxy-3.0.0-alpha.4.tgz",
@ -10746,6 +10767,14 @@
"type-check": "0.3.2"
}
},
"linkify-it": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz",
"integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=",
"requires": {
"uc.micro": "1.0.5"
}
},
"load-json-file": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
@ -11037,6 +11066,23 @@
"object-visit": "1.0.1"
}
},
"markdown-it": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.1.tgz",
"integrity": "sha512-CzzqSSNkFRUf9vlWvhK1awpJreMRqdCrBvZ8DIoDWTOkESMIF741UPAhuAmbyWmdiFPA6WARNhnu2M6Nrhwa+A==",
"requires": {
"argparse": "1.0.9",
"entities": "1.1.1",
"linkify-it": "2.0.3",
"mdurl": "1.0.1",
"uc.micro": "1.0.5"
}
},
"markdown-it-flowdock": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/markdown-it-flowdock/-/markdown-it-flowdock-0.3.7.tgz",
"integrity": "sha1-Oj6/V646BKBoTOK2Hbb+lOhZV4E="
},
"markdown-loader": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/markdown-loader/-/markdown-loader-2.0.2.tgz",
@ -11076,6 +11122,11 @@
}
}
},
"mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -14710,19 +14761,6 @@
"react-native-drawer-layout": "1.3.2"
}
},
"react-native-easy-markdown": {
"version": "git+https://github.com/diegolmello/react-native-easy-markdown.git#1cf9d2bd9683576af0bb09d80ffa286c4dc03f82",
"requires": {
"simple-markdown": "0.1.2"
},
"dependencies": {
"simple-markdown": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.1.2.tgz",
"integrity": "sha1-PBUQ/kC9nqBncXuKUzyc82MltBM="
}
}
},
"react-native-fabric": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/react-native-fabric/-/react-native-fabric-0.5.1.tgz",
@ -14760,6 +14798,14 @@
}
}
},
"react-native-fit-image": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/react-native-fit-image/-/react-native-fit-image-1.5.4.tgz",
"integrity": "sha512-wNHlGdDWsUU31qlM5SsvZrMH4eXBZt586FQNXFRFuOiXVqdA++6Xait7aiZ+5vxglgqLf+zzSnoICn0NEvDfrw==",
"requires": {
"prop-types": "15.6.1"
}
},
"react-native-image-picker": {
"version": "0.26.10",
"resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-0.26.10.tgz",
@ -14789,6 +14835,16 @@
"react-native-keyboard-tracking-view": {
"version": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git#82be12805eb3aa448c1f09f545c334e4776b3148"
},
"react-native-markdown-renderer": {
"version": "git+https://github.com/RocketChat/react-native-markdown-renderer.git#cecc6d0a2c940ac7a1e1e98c624d8b9b4d37ab68",
"requires": {
"@types/markdown-it": "0.0.4",
"@types/react-native": "0.55.15",
"markdown-it": "8.4.1",
"prop-types": "15.6.1",
"react-native-fit-image": "1.5.4"
}
},
"react-native-meteor": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-native-meteor/-/react-native-meteor-1.2.0.tgz",
@ -16320,11 +16376,6 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simple-markdown": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.4.1.tgz",
"integrity": "sha512-EkGc+efa/7qv2s9+G6sBBHPfvC6y+mq9mq0zStf8KDgHBlaRbT0U7mbA2WFUrxhUe/IDfSTilgC+G8ctc88n6A=="
},
"simple-plist": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-0.2.1.tgz",
@ -18119,6 +18170,11 @@
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz",
"integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g=="
},
"uc.micro": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz",
"integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg=="
},
"uglify-js": {
"version": "2.8.29",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",

View File

@ -34,6 +34,7 @@
"ejson": "^2.1.2",
"js-base64": "^2.4.5",
"lodash": "^4.17.10",
"markdown-it-flowdock": "^0.3.7",
"moment": "^2.22.1",
"prop-types": "^15.6.1",
"react": "^16.3.2",
@ -43,7 +44,6 @@
"react-native-action-button": "^2.8.3",
"react-native-actionsheet": "^2.4.2",
"react-native-audio": "^4.1.3",
"react-native-easy-markdown": "git+https://github.com/diegolmello/react-native-easy-markdown.git",
"react-native-fabric": "^0.5.1",
"react-native-fast-image": "^4.0.14",
"react-native-fetch-blob": "^0.10.8",
@ -51,6 +51,7 @@
"react-native-keyboard-aware-scroll-view": "^0.5.0",
"react-native-keyboard-input": "git+https://github.com/RocketChat/react-native-keyboard-input.git",
"react-native-keyboard-tracking-view": "git+https://github.com/RocketChat/react-native-keyboard-tracking-view.git",
"react-native-markdown-renderer": "git+https://github.com/RocketChat/react-native-markdown-renderer.git",
"react-native-meteor": "^1.2.0",
"react-native-modal": "^6.0.0",
"react-native-optimized-flatlist": "^1.0.4",
@ -75,7 +76,6 @@
"redux-immutable-state-invariant": "^2.1.0",
"redux-saga": "^0.16.0",
"regenerator-runtime": "^0.11.1",
"simple-markdown": "^0.4.1",
"snyk": "^1.80.1",
"strip-ansi": "^4.0.0"
},