Add new parser components
This commit is contained in:
parent
559f804073
commit
ff9b0fec9c
|
@ -0,0 +1,18 @@
|
|||
/* eslint-disable react/no-array-index-key */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Emoji from './Emoji';
|
||||
|
||||
|
||||
const BigEmoji = ({ value }) => (
|
||||
<>
|
||||
{value.map((block, index) => <Emoji key={index} value={block.value.value} isBigEmoji />)}
|
||||
</>
|
||||
);
|
||||
|
||||
BigEmoji.propTypes = {
|
||||
value: PropTypes.object
|
||||
};
|
||||
|
||||
export default BigEmoji;
|
|
@ -0,0 +1,36 @@
|
|||
/* eslint-disable react/no-array-index-key */
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Plain from './Plain';
|
||||
import Strike from './Strike';
|
||||
import Italic from './Italic';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
});
|
||||
|
||||
const Bold = ({ value }) => (
|
||||
<Text style={styles.text}>
|
||||
{value.map((block, index) => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return <Plain key={index} value={block.value} />;
|
||||
case 'STRIKE':
|
||||
return <Strike key={index} value={block.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic key={index} value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
|
||||
Bold.propTypes = {
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
export default Bold;
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { PropTypes, Text } from 'react-native';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
|
@ -11,7 +12,7 @@ const Code = ({
|
|||
<Text
|
||||
style={[
|
||||
{
|
||||
...(type === 'CODE_INLINE' ? styles.codeInline : styles.codeBlock),
|
||||
...(type === 'INLINE_CODE' ? styles.codeInline : styles.codeBlock),
|
||||
color: themes[theme].bodyText,
|
||||
backgroundColor: themes[theme].bannerBackground,
|
||||
borderColor: themes[theme].bannerBackground
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
import styles from '../styles';
|
||||
|
||||
const Emoji = ({ emojiHandle, style, isBigEmoji }) => {
|
||||
const { theme } = useTheme();
|
||||
const emojiUnicode = shortnameToUnicode(emojiHandle);
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
{ color: themes[theme].bodyText },
|
||||
isBigEmoji ? styles.textBig : styles.text,
|
||||
style
|
||||
]}
|
||||
>
|
||||
{emojiUnicode}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
Emoji.propTypes = {
|
||||
emojiHandle: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
isBigEmoji: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Emoji;
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import styles from '../styles';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
const Heading = ({ value, level }) => {
|
||||
const { theme } = useTheme();
|
||||
const textStyle = styles[`heading${ level }`];
|
||||
|
||||
return (
|
||||
<>
|
||||
{value.map((block) => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return (
|
||||
<Text style={[textStyle, { color: themes[theme].bodyText }]}>
|
||||
{block.value}
|
||||
</Text>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Heading.propTypes = {
|
||||
value: PropTypes.string,
|
||||
level: PropTypes.number
|
||||
};
|
||||
|
||||
export default Heading;
|
|
@ -0,0 +1,48 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Link from './Link';
|
||||
import Plain from './Plain';
|
||||
import Code from './Code';
|
||||
import Bold from './Bold';
|
||||
import Strike from './Strike';
|
||||
import Italic from './Italic';
|
||||
import Emoji from './Emoji';
|
||||
|
||||
const Inline = ({ value }) => (
|
||||
<Text>
|
||||
{value.map((block) => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return <Plain value={block.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold value={block.value} />;
|
||||
case 'STRIKE':
|
||||
return <Strike value={block.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic value={block.value} />;
|
||||
case 'LINK':
|
||||
// eslint-disable-next-line jsx-a11y/anchor-is-valid
|
||||
return <Link value={block.value} />;
|
||||
// case 'MENTION_USER':
|
||||
// return <Mention value={block.value} mentions={mentions} />;
|
||||
case 'EMOJI':
|
||||
return <Emoji emojiHandle={`:${ block.value.value }:`} />;
|
||||
// case 'MENTION_CHANNEL':
|
||||
// // case 'COLOR':
|
||||
// return <Plain value={block.value} />;
|
||||
case 'INLINE_CODE':
|
||||
return <Code value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
|
||||
Inline.propTypes = {
|
||||
value: PropTypes.object
|
||||
};
|
||||
|
||||
export default Inline;
|
|
@ -0,0 +1,37 @@
|
|||
/* eslint-disable react/no-array-index-key */
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Plain from './Plain';
|
||||
import Strike from './Strike';
|
||||
import Bold from './Bold';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
fontStyle: 'italic'
|
||||
}
|
||||
});
|
||||
|
||||
const Italic = ({ value }) => (
|
||||
<Text style={styles.text}>
|
||||
{value.map((block, index) => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return <Plain key={index} value={block.value} />;
|
||||
case 'STRIKE':
|
||||
return <Strike key={index} value={block.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold key={index} value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
|
||||
Italic.propTypes = {
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
export default Italic;
|
|
@ -0,0 +1,63 @@
|
|||
import React from 'react';
|
||||
import { Text, Clipboard } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Strike from './Strike';
|
||||
import Italic from './Italic';
|
||||
import Bold from './Bold';
|
||||
import styles from '../styles';
|
||||
import I18n from '../../../i18n';
|
||||
import { LISTENER } from '../../Toast';
|
||||
import { useTheme } from '../../../theme';
|
||||
import openLink from '../../../utils/openLink';
|
||||
import EventEmitter from '../../../utils/events';
|
||||
import { themes } from '../../../constants/colors';
|
||||
|
||||
const Link = ({ value }) => {
|
||||
const { theme } = useTheme();
|
||||
const { src, label } = value;
|
||||
const handlePress = () => {
|
||||
if (!src.value) {
|
||||
return;
|
||||
}
|
||||
openLink(src.value, theme);
|
||||
};
|
||||
|
||||
const onLongPress = () => {
|
||||
Clipboard.setString(src.value);
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||
};
|
||||
|
||||
return (
|
||||
<Text
|
||||
onPress={handlePress}
|
||||
onLongPress={onLongPress}
|
||||
o
|
||||
style={{ ...styles.link, color: themes[theme].actionTintColor }}
|
||||
>
|
||||
{((block) => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return block.value;
|
||||
case 'STRIKE':
|
||||
return <Strike value={block.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic value={block.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})(label)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
Link.propTypes = {
|
||||
value: {
|
||||
src: PropTypes.string,
|
||||
label: PropTypes.string
|
||||
}
|
||||
};
|
||||
|
||||
export default Link;
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Inline from './Inline';
|
||||
|
||||
const Paragraph = ({ value, mentions }) => <Inline value={value} mentions={mentions} />;
|
||||
|
||||
Paragraph.propTypes = {
|
||||
value: PropTypes.string,
|
||||
mentions: PropTypes.string
|
||||
};
|
||||
|
||||
export default Paragraph;
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Plain = ({ value }) => (
|
||||
<Text accessibilityLabel={value}>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
|
||||
Plain.propTypes = {
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
export default Plain;
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { useTheme } from '../../../theme';
|
||||
import styles from '../styles';
|
||||
|
||||
const Quote = ({ value }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<>
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.quote, { backgroundColor: themes[theme].borderColor }]} />
|
||||
<View style={styles.childContainer}>
|
||||
{value}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Quote.propTypes = {
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
export default Quote;
|
|
@ -0,0 +1,37 @@
|
|||
/* eslint-disable react/no-array-index-key */
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Plain from './Plain';
|
||||
import Bold from './Bold';
|
||||
import Italic from './Italic';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
textDecorationLine: 'line-through'
|
||||
}
|
||||
});
|
||||
|
||||
const Strike = ({ value }) => (
|
||||
<Text style={styles.text}>
|
||||
{value.map((block, index) => {
|
||||
switch (block.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return <Plain key={index} value={block.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold key={index} value={block.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic key={index} value={block.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
|
||||
Strike.propTypes = {
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
export default Strike;
|
|
@ -0,0 +1,56 @@
|
|||
/* eslint-disable react/no-array-index-key */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import List from './List';
|
||||
import Quote from './Quote';
|
||||
import Paragraph from './Paragraph';
|
||||
import Heading from './Heading';
|
||||
import Code from './Code';
|
||||
import Link from './Link';
|
||||
import BigEmoji from './BigEmoji';
|
||||
import { useTheme } from '../../../theme';
|
||||
|
||||
const isBigEmoji = tokens => tokens.length === 1 && tokens[0].type === 'BIG_EMOJI';
|
||||
|
||||
const Body = ({ tokens, mentions }) => {
|
||||
const { theme } = useTheme();
|
||||
if (isBigEmoji(tokens)) {
|
||||
return <BigEmoji value={tokens[0].value} theme={theme} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{tokens.map((block, index) => {
|
||||
switch (block.type) {
|
||||
case 'UNORDERED_LIST':
|
||||
return <List type={block.type} value={block.value} key={index} />;
|
||||
case 'ORDERED_LIST':
|
||||
return <List type={block.type} value={block.value} key={index} />;
|
||||
case 'TASK':
|
||||
return <List type={block.type} value={block.value} key={index} />;
|
||||
case 'QUOTE':
|
||||
return <Quote key={index} value={block.value} />;
|
||||
case 'PARAGRAPH':
|
||||
return <Paragraph key={index} value={block.value} mentions={mentions} />;
|
||||
case 'CODE':
|
||||
return <Code key={index} value={block.value} />;
|
||||
case 'LINK':
|
||||
// eslint-disable-next-line jsx-a11y/anchor-is-valid
|
||||
return <Link key={index} value={block.value} />;
|
||||
case 'HEADING':
|
||||
return <Heading key={index} value={block.value} level={block.level} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Body.propTypes = {
|
||||
tokens: PropTypes.array,
|
||||
mentions: PropTypes.array
|
||||
};
|
||||
|
||||
export default Body;
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react';
|
||||
import { PropTypes } from 'react-native';
|
||||
|
||||
import MarkdownEmoji from '../Emoji';
|
||||
|
||||
const BigEmoji = ({ value }) => (
|
||||
<>
|
||||
<MarkdownEmoji literal={value} />
|
||||
</>
|
||||
);
|
||||
|
||||
BigEmoji.propTypes = {
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
export default BigEmoji;
|
|
@ -1,53 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Text, PropTypes } from 'react-native';
|
||||
import MarkdownLink from '../Link';
|
||||
import MarkdownList from '../List';
|
||||
// import MarkdownListItem from '../ListItem';
|
||||
// import MarkdownAtMention from '../AtMention';
|
||||
// import MarkdownHashtag from '../Hashtag';
|
||||
import MarkdownBlockQuote from '../BlockQuote';
|
||||
// import MarkdownTable from '../Table';
|
||||
// import MarkdownTableRow from '../TableRow';
|
||||
// import MarkdownTableCell from '../TableCell';
|
||||
import styles from '../styles';
|
||||
import { withTheme } from '../../../theme';
|
||||
import { themes } from '../../../constants/colors';
|
||||
|
||||
|
||||
const Body = ({
|
||||
md, style, numberOfLines, theme
|
||||
}) => (
|
||||
<>
|
||||
{md.map((block, index) => {
|
||||
switch (block.type) {
|
||||
case 'UNORDERED_LIST':
|
||||
return <MarkdownList ordered={false} />;
|
||||
case 'ORDERED_LIST':
|
||||
return <MarkdownList ordered />;
|
||||
case 'QUOTE':
|
||||
return <MarkdownBlockQuote />;
|
||||
case 'PARAGRAPH':
|
||||
return (
|
||||
<Text style={[styles.text, style, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines} index={index}>
|
||||
{block.value?.value}
|
||||
</Text>
|
||||
);
|
||||
case 'CODE':
|
||||
return <MarkdownLink />;
|
||||
case 'LINK':
|
||||
return <MarkdownLink />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
Body.propTypes = {
|
||||
md: PropTypes.array,
|
||||
theme: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
numberOfLines: PropTypes.number
|
||||
};
|
||||
|
||||
export default withTheme(Body);
|
|
@ -4,6 +4,7 @@ import { Parser, Node } from 'commonmark';
|
|||
import Renderer from 'commonmark-react-renderer';
|
||||
import PropTypes from 'prop-types';
|
||||
import removeMarkdown from 'remove-markdown';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||
import I18n from '../../i18n';
|
||||
|
@ -23,6 +24,8 @@ import mergeTextNodes from './mergeTextNodes';
|
|||
|
||||
import styles from './styles';
|
||||
import { isValidURL } from '../../utils/url';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import MessageBody from './MessageBody';
|
||||
|
||||
// Support <http://link|Text>
|
||||
const formatText = text => text.replace(
|
||||
|
@ -69,6 +72,7 @@ class Markdown extends PureComponent {
|
|||
static propTypes = {
|
||||
msg: PropTypes.string,
|
||||
md: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
getCustomEmoji: PropTypes.func,
|
||||
baseUrl: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
|
@ -372,13 +376,17 @@ class Markdown extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {
|
||||
msg, numberOfLines, preview = false, theme, style = [], testID
|
||||
msg, md, numberOfLines, preview = false, theme, style = [], testID, user, mentions
|
||||
} = this.props;
|
||||
|
||||
if (!msg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (user.enableMessageParserEarlyAdoption && md) {
|
||||
return <MessageBody tokens={md} theme={theme} style={style} mentions={mentions} />;
|
||||
}
|
||||
|
||||
let m = formatText(msg);
|
||||
|
||||
// Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
|
||||
|
@ -406,4 +414,8 @@ class Markdown extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
export default Markdown;
|
||||
const mapStateToProps = state => ({
|
||||
user: getUserSelector(state)
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(Markdown);
|
||||
|
|
|
@ -19,10 +19,10 @@ const changeMessageStatus = async(id, tmid, status, message) => {
|
|||
if (message) {
|
||||
m.mentions = message.mentions;
|
||||
m.channels = message.channels;
|
||||
}
|
||||
|
||||
if (message.md) {
|
||||
m.md = message.md;
|
||||
if (message.md) {
|
||||
m.md = message.md;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@ -35,6 +35,10 @@ const changeMessageStatus = async(id, tmid, status, message) => {
|
|||
if (message) {
|
||||
tm.mentions = message.mentions;
|
||||
tm.channels = message.channels;
|
||||
|
||||
if (message.md) {
|
||||
tm.md = message.md;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
|
@ -54,7 +54,6 @@ const LastMessage = React.memo(({
|
|||
msg={formatMsg({
|
||||
lastMessage, type, showLastMessage, username, useRealName
|
||||
})}
|
||||
md={lastMessage?.md}
|
||||
style={[styles.markdownText, { color: alert ? themes[theme].bodyText : themes[theme].auxiliaryText }]}
|
||||
customEmojis={false}
|
||||
useRealName={useRealName}
|
||||
|
|
Loading…
Reference in New Issue