mirror of https://github.com/status-im/chat.git
refactor ChatBox component into a Message components and other sub components
This commit is contained in:
parent
d50cb5e7c2
commit
cea7f32b0a
|
@ -21,3 +21,5 @@
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React, { Fragment, PureComponent } from 'react';
|
|
||||||
import ListItem from '@material-ui/core/ListItem';
|
|
||||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
|
||||||
import ListItemText from '@material-ui/core/ListItemText';
|
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
|
||||||
import YouTube from 'react-youtube';
|
|
||||||
import Linkify from 'react-linkify';
|
|
||||||
import SpotifyPlayer from 'react-spotify-player';
|
|
||||||
import { Emoji } from 'emoji-mart';
|
|
||||||
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon'
|
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
||||||
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
||||||
import { Matcher } from '@areknawo/rex'
|
|
||||||
import SyntaxLookup from '../utils/syntaxLookup';
|
|
||||||
import { getFile } from '../utils/ipfs';
|
|
||||||
|
|
||||||
const ipfsMatcher = new Matcher().begin().find('/ipfs/');
|
|
||||||
|
|
||||||
// TODO: not exactly bulletproof right now, needs proper regex
|
|
||||||
function hasYoutubeLink(text) {
|
|
||||||
return text.indexOf('http://www.youtube.com') >= 0 || text.indexOf('https://www.youtube.com') >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: not exactly bulletproof right now, needs proper regex
|
|
||||||
function isSpotifyLink(text) {
|
|
||||||
return text.indexOf('spotify:') >= 0 ;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://gist.github.com/takien/4077195#
|
|
||||||
function getYoutubeId(url) {
|
|
||||||
let ID = '';
|
|
||||||
url = url.replace(/(>|<)/gi,'').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
|
|
||||||
if (url[2] !== undefined) {
|
|
||||||
ID = url[2].split(/[^0-9a-z_\-]/i);
|
|
||||||
ID = ID[0];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ID = url;
|
|
||||||
}
|
|
||||||
return ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isImage(text) {
|
|
||||||
return text.indexOf("http") >= 0 && (text.indexOf('.jpg') || text.indexOf('.gif'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this needs to be reviewed. best to return as a css background-image instead
|
|
||||||
function displayImage(text) {
|
|
||||||
|
|
||||||
let reg = new RegExp(/\b(https?:\/\/\S+(?:png|jpe?g|gif)\S*)\b/);
|
|
||||||
let imageUrl = reg.exec(text);
|
|
||||||
if (!imageUrl) return (<span></span>);
|
|
||||||
return (<img src={imageUrl[0]} alt="" style={{maxWidth: '90%'}} />)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO use regex for code parsing / detection. Add new line detection for shift+enter
|
|
||||||
const MessageRender = ({ message }) => {
|
|
||||||
const emojis = [];
|
|
||||||
let match;
|
|
||||||
const regex1 = RegExp(/:[\-a-zA-Z_+0-9]+:/g);
|
|
||||||
while ((match = regex1.exec(message)) !== null) {
|
|
||||||
emojis.push(<Emoji emoji={match[0]} size={16} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = message.split(regex1);
|
|
||||||
parts.forEach((part, i) => {
|
|
||||||
parts[i] = <span className="match" key={i}>{part}{emojis[i]}</span>;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (message[2] === "`" && SyntaxLookup[message.slice(0,2)]
|
|
||||||
? <SyntaxHighlighter language={SyntaxLookup[message.slice(0,2)]} style={atomDark}>{message.slice(3)}</SyntaxHighlighter>
|
|
||||||
: <Linkify><span style={{ wordWrap: 'break-word', whiteSpace: 'pre-line' }}>{parts}</span></Linkify>)
|
|
||||||
};
|
|
||||||
|
|
||||||
class ChatBox extends PureComponent {
|
|
||||||
|
|
||||||
state = {
|
|
||||||
imgUrl: null
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { message } = this.props;
|
|
||||||
if (ipfsMatcher.test(message)) this.getImageFromIpfs();
|
|
||||||
}
|
|
||||||
|
|
||||||
getImageFromIpfs = async () => {
|
|
||||||
const { ipfs, message } = this.props;
|
|
||||||
const files = await getFile(ipfs, message);
|
|
||||||
const { content } = files[0];
|
|
||||||
const arrayBufferView = new Uint8Array(content);
|
|
||||||
const blob = new Blob([ arrayBufferView ], { type: "image/jpeg" });
|
|
||||||
const imgUrl = URL.createObjectURL(blob);
|
|
||||||
this.setState({ imgUrl });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { username, message, pubkey } = this.props;
|
|
||||||
const { imgUrl } = this.state;
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<ListItem>
|
|
||||||
<Avatar>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<Avatar>
|
|
||||||
{pubkey && <Jazzicon diameter={40} seed={jsNumberForAddress(pubkey)}/>}
|
|
||||||
</Avatar>
|
|
||||||
</ListItemAvatar>
|
|
||||||
</Avatar>
|
|
||||||
<ListItemText primary={`${username}`} secondary={<MessageRender message={message}/>}/>
|
|
||||||
</ListItem>
|
|
||||||
{hasYoutubeLink(message) &&
|
|
||||||
<ListItem>
|
|
||||||
<YouTube
|
|
||||||
videoId={getYoutubeId(message)}
|
|
||||||
opts={{ height: '390', width: '640', playerVars: { autoplay: 0 } }}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
}
|
|
||||||
{isSpotifyLink(message) &&
|
|
||||||
<ListItem>
|
|
||||||
<SpotifyPlayer
|
|
||||||
uri={message}
|
|
||||||
size={{ 'width': 300, 'height': 300 }}
|
|
||||||
view='list'
|
|
||||||
theme='black'
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
}
|
|
||||||
{!!imgUrl && <img src={imgUrl} alt='ipfs' style={{maxWidth: '90%'}} />}
|
|
||||||
{isImage(message) && displayImage(message)}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChatBox;
|
|
|
@ -13,7 +13,7 @@ import AddCircle from '@material-ui/icons/AddCircle';
|
||||||
|
|
||||||
import 'emoji-mart/css/emoji-mart.css';
|
import 'emoji-mart/css/emoji-mart.css';
|
||||||
|
|
||||||
import ChatBox from './ChatBox';
|
import Message from './Message';
|
||||||
import ChatHeader from './ChatHeader';
|
import ChatHeader from './ChatHeader';
|
||||||
import Userlist from './Userlist';
|
import Userlist from './Userlist';
|
||||||
import { uploadFileAndSend } from '../utils/ipfs';
|
import { uploadFileAndSend } from '../utils/ipfs';
|
||||||
|
@ -144,7 +144,7 @@ class ChatRoom extends Component {
|
||||||
<AutoScrollList style={{ height: messagesHeight, overflow: 'scroll' }}>
|
<AutoScrollList style={{ height: messagesHeight, overflow: 'scroll' }}>
|
||||||
{messages[currentChannel] && messages[currentChannel].map((message) => (
|
{messages[currentChannel] && messages[currentChannel].map((message) => (
|
||||||
<Fragment key={message.data.payload}>
|
<Fragment key={message.data.payload}>
|
||||||
<ChatBox {...message} ipfs={ipfs}/>
|
<Message {...message} ipfs={ipfs}/>
|
||||||
<li>
|
<li>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
|
|
||||||
|
import UserAvatar from './UserAvatar';
|
||||||
|
import MessageRenderer from './messages/MessageRenderer';
|
||||||
|
import YoutubeMessage from './messages/youtube';
|
||||||
|
import SpotifyMessage from './messages/spotify';
|
||||||
|
import ImageMessage from './messages/image';
|
||||||
|
import IpfsImageMessage from './messages/ipfsImage';
|
||||||
|
|
||||||
|
const Message = ({message, pubkey, username, ipfs}) => (
|
||||||
|
<Fragment>
|
||||||
|
<ListItem>
|
||||||
|
<UserAvatar pubkey={pubkey} />
|
||||||
|
<ListItemText primary={`${username}`} secondary={<MessageRenderer message={message}/>}/>
|
||||||
|
</ListItem>
|
||||||
|
<YoutubeMessage message={message} />
|
||||||
|
<SpotifyMessage message={message} />
|
||||||
|
<ImageMessage message={message} />
|
||||||
|
<IpfsImageMessage message={message} ipfs={ipfs} />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Message;
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||||
|
import Avatar from '@material-ui/core/Avatar';
|
||||||
|
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon'
|
||||||
|
|
||||||
|
const UserAvatar = ({pubkey}) => (
|
||||||
|
<Avatar>
|
||||||
|
<ListItemAvatar>
|
||||||
|
{pubkey && <Jazzicon diameter={40} seed={jsNumberForAddress(pubkey)}/>}
|
||||||
|
</ListItemAvatar>
|
||||||
|
</Avatar>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default UserAvatar;
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Emoji } from 'emoji-mart';
|
||||||
|
import SyntaxLookup from '../../utils/syntaxLookup';
|
||||||
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
|
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||||
|
import Linkify from 'react-linkify';
|
||||||
|
|
||||||
|
const isCode = (message) => {
|
||||||
|
return message[2] === "`" && SyntaxLookup[message.slice(0,2)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use regex for code parsing / detection. Add new line detection for shift+enter
|
||||||
|
const MessageRenderer = ({ message }) => {
|
||||||
|
if (isCode(message)) {
|
||||||
|
return (<SyntaxHighlighter language={SyntaxLookup[message.slice(0,2)]} style={atomDark}>{message.slice(3)}</SyntaxHighlighter>)
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis = [];
|
||||||
|
let match;
|
||||||
|
const regex1 = RegExp(/:[\-a-zA-Z_+0-9]+:/g);
|
||||||
|
while ((match = regex1.exec(message)) !== null) {
|
||||||
|
emojis.push(<Emoji emoji={match[0]} size={16} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = message.split(regex1);
|
||||||
|
parts.forEach((part, i) => {
|
||||||
|
parts[i] = <span className="match" key={i}>{part}{emojis[i]}</span>;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (<Linkify><span style={{ wordWrap: 'break-word', whiteSpace: 'pre-line' }}>{parts}</span></Linkify>)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default MessageRenderer;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
|
||||||
|
function isImage(text) {
|
||||||
|
return text.indexOf("http") >= 0 && (text.indexOf('.jpg') || text.indexOf('.gif'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this needs to be reviewed. best to return as a css background-image instead
|
||||||
|
function displayImage(text) {
|
||||||
|
let reg = new RegExp(/\b(https?:\/\/\S+(?:png|jpe?g|gif)\S*)\b/);
|
||||||
|
let imageUrl = reg.exec(text);
|
||||||
|
if (!imageUrl) return (<span></span>);
|
||||||
|
return (<img src={imageUrl[0]} alt="" style={{maxWidth: '90%'}} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageMessage = ({message}) => (
|
||||||
|
<Fragment>
|
||||||
|
{isImage(message) && displayImage(message)}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ImageMessage;
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React, { Fragment, PureComponent } from 'react';
|
||||||
|
import { Matcher } from '@areknawo/rex'
|
||||||
|
import { getFile } from '../../utils/ipfs';
|
||||||
|
|
||||||
|
const ipfsMatcher = new Matcher().begin().find('/ipfs/');
|
||||||
|
|
||||||
|
class IpfsImageMessage extends PureComponent {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
imgUrl: null
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { message } = this.props;
|
||||||
|
if (ipfsMatcher.test(message)) this.getImageFromIpfs();
|
||||||
|
}
|
||||||
|
|
||||||
|
getImageFromIpfs = async () => {
|
||||||
|
const { ipfs, message } = this.props;
|
||||||
|
const files = await getFile(ipfs, message);
|
||||||
|
const { content } = files[0];
|
||||||
|
const arrayBufferView = new Uint8Array(content);
|
||||||
|
const blob = new Blob([ arrayBufferView ], { type: "image/jpeg" });
|
||||||
|
const imgUrl = URL.createObjectURL(blob);
|
||||||
|
this.setState({ imgUrl });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { imgUrl } = this.state;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{!!imgUrl && <img src={imgUrl} alt='ipfs' style={{maxWidth: '90%'}} />}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default IpfsImageMessage;
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
import SpotifyPlayer from 'react-spotify-player';
|
||||||
|
|
||||||
|
// TODO: not exactly bulletproof right now, needs proper regex
|
||||||
|
function isSpotifyLink(text) {
|
||||||
|
return text.indexOf('spotify:') >= 0 ;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpotifyMessage = ({message}) => (
|
||||||
|
<Fragment>
|
||||||
|
{isSpotifyLink(message) &&
|
||||||
|
<ListItem>
|
||||||
|
<SpotifyPlayer
|
||||||
|
uri={message}
|
||||||
|
size={{ 'width': 300, 'height': 300 }}
|
||||||
|
view='list'
|
||||||
|
theme='black'
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SpotifyMessage;
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import YouTube from 'react-youtube';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
|
||||||
|
// TODO: not exactly bulletproof right now, needs proper regex
|
||||||
|
function hasYoutubeLink(text) {
|
||||||
|
return text.indexOf('http://www.youtube.com') >= 0 || text.indexOf('https://www.youtube.com') >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://gist.github.com/takien/4077195#
|
||||||
|
function getYoutubeId(url) {
|
||||||
|
let ID = '';
|
||||||
|
url = url.replace(/(>|<)/gi,'').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
|
||||||
|
if (url[2] !== undefined) {
|
||||||
|
ID = url[2].split(/[^0-9a-z_\-]/i);
|
||||||
|
ID = ID[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ID = url;
|
||||||
|
}
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
const YoutubeMessage = ({message}) => (
|
||||||
|
<Fragment>
|
||||||
|
{hasYoutubeLink(message) &&
|
||||||
|
<ListItem>
|
||||||
|
<YouTube
|
||||||
|
videoId={getYoutubeId(message)}
|
||||||
|
opts={{ height: '390', width: '640', playerVars: { autoplay: 0 } }}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default YoutubeMessage;
|
Loading…
Reference in New Issue