Merge pull request #2 from status-im/emoji

Add emoji support
This commit is contained in:
Iuri Matias 2018-11-23 17:03:29 -05:00 committed by GitHub
commit a18f70fdb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 187 additions and 115 deletions

View File

@ -5,6 +5,7 @@
<title>Status</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<link rel="stylesheet" href="https://unpkg.com/emoji-mart@2.8.1/css/emoji-mart.css" />
<script>
(function() {
if (!process.env.HOT) {

View File

@ -7,6 +7,7 @@ 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/styles/prism';
@ -40,12 +41,25 @@ function getYoutubeId(url) {
return ID;
}
//TODO use regex for code parsing / detection. Add new line detection for shift+enter
const MessageRender = ({ message }) => (
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' }}>{message}</span></Linkify>
);
// 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_]+:/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 = {
@ -64,7 +78,6 @@ class ChatBox extends PureComponent {
const arrayBufferView = new Uint8Array(content);
const blob = new Blob([ arrayBufferView ], { type: "image/jpeg" });
const imgUrl = URL.createObjectURL(blob);
const image = `data:image/png;base64,${content.toString('base64')}`;
this.setState({ imgUrl });
};
@ -77,33 +90,33 @@ class ChatBox extends PureComponent {
<Avatar>
<ListItemAvatar>
<Avatar>
{pubkey && <Jazzicon diameter={40} seed={jsNumberForAddress(pubkey)} />}
{pubkey && <Jazzicon diameter={40} seed={jsNumberForAddress(pubkey)}/>}
</Avatar>
</ListItemAvatar>
</Avatar>
<ListItemText primary={`${username}`} secondary={<MessageRender message={message} />} />
<ListItemText primary={`${username}`} secondary={<MessageRender message={message}/>}/>
</ListItem>
{hasYoutubeLink(message) &&
<ListItem>
<YouTube
videoId={getYoutubeId(message)}
opts={{height: '390', width: '640', playerVars: { autoplay: 0 }}}
/>
</ListItem>
<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>
<ListItem>
<SpotifyPlayer
uri={message}
size={{ 'width': 300, 'height': 300 }}
view='list'
theme='black'
/>
</ListItem>
}
{!!imgUrl && <img src={imgUrl} alt='ipfs' style={{ width: '100%' }}/>}
</Fragment>
)
);
};
}

View File

@ -1,5 +1,5 @@
// @flow
import React, { Fragment, PureComponent, createRef } from 'react';
import React, { Fragment, Component, PureComponent, createRef } from 'react';
import { Formik } from 'formik';
import autoscroll from 'autoscroll-react';
import List from '@material-ui/core/List';
@ -10,13 +10,19 @@ import ListItemText from '@material-ui/core/ListItemText';
import Divider from '@material-ui/core/Divider';
import Grid from '@material-ui/core/Grid';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Dropzone from 'react-dropzone';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
import { Picker } from 'emoji-mart';
import ChatBox from './ChatBox';
import ChatBox, { Emoji } from './ChatBox';
import ChatHeader from './ChatHeader';
import { uploadFileAndSend } from '../utils/ipfs';
import 'emoji-mart/css/emoji-mart.css';
class WhoIsTyping extends PureComponent {
whoIsTyping() {
@ -68,96 +74,138 @@ const AutoScrollList = autoscroll(List);
const formStyle = { display: 'flex', justifyContent: 'center', alignItems: 'center', flexBasis: '10%' };
const listStyle = { overflowY: 'auto', flexBasis: '76%', position: 'absolute', top: '72px', left: 0, right: 0, bottom: '67px' };
const ChatRoomForm = createRef();
const ChatRoom = ({ messages, sendMessage, currentChannel, usersTyping, typingEvent, channelUsers, allUsers, ipfs }) => (
<Grid container style={{ height: '100vh'}}>
<Grid xs={8} item >
<Dropzone
onDrop={(a, r) => { onDrop(a,r,ipfs,sendMessage) } }
disableClick
style={{ position: 'relative', height: '100%' }}
activeStyle={{ backgroundColor: 'grey', outline: '5px dashed lightgrey', alignSelf: 'center', outlineOffset: '-10px' }}>
<Grid
container
direction="column"
justify="flex-start"
alignItems="stretch"
style={{ height: '100%' }}
>
<ChatHeader currentChannel={currentChannel}/>
<Divider />
<AutoScrollList style={listStyle}>
{messages[currentChannel] && messages[currentChannel].map((message) => (
<Fragment key={message.data.payload}>
<ChatBox {...message} ipfs={ipfs} />
<li>
<Divider />
</li>
</Fragment>
))}
</AutoScrollList>
<Formik
initialValues={{ chatInput: '' }}
onSubmit={(values, { setSubmitting, resetForm }) => {
const { chatInput } = values;
sendMessage(chatInput);
resetForm();
setSubmitting(false);
class ChatRoom extends Component {
constructor(props) {
super(props);
this.state = {
showEmojis: false
};
}
toggleEmojis(e) {
e.preventDefault();
this.setState(({showEmojis: !this.state.showEmojis}));
}
addEmoji(emoji, chatInput, setValue) {
console.log(emoji);
setValue('chatInput', `${chatInput}:${emoji.id}:`);
this.setState(({showEmojis: false}));
// <Emoji emoji=":santa::skin-tone-3:" size={16} />
}
render() {
const { messages, sendMessage, currentChannel, usersTyping, typingEvent, channelUsers, allUsers, ipfs } = this.props;
const {showEmojis} = this.state;
return (
<Grid container style={{ height: '100vh' }}>
<Grid xs={8} item>
<Dropzone
onDrop={(a, r) => {
onDrop(a, r, ipfs, sendMessage);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
}) => (
<div className="chat-input" style={{position: 'absolute', bottom: 0, left: 0, right: 0, paddingBottom: 10}}>
<form onSubmit={handleSubmit} style={formStyle} ref={ChatRoomForm}>
<TextField
id="chatInput"
multiline
style={{ width: 'auto', flexGrow: '0.95', margin: '2px 0 0 0' }}
label="Type a message..."
type="text"
name="chatInput"
margin="normal"
variant="outlined"
fullWidth
onChange={handleChange}
onKeyDown={(e) => keyDownHandler(e, typingEvent, setFieldValue, values.chatInput)}
onBlur={handleBlur}
value={values.chatInput || ''}
/>
{errors.chatInput && touched.chatInput && errors.chatInput}
</form>
<WhoIsTyping
currentChannel={currentChannel}
usersTyping={usersTyping}
users={allUsers} />
</div>
)}
</Formik>
disableClick
style={{ position: 'relative', height: '100%' }}
activeStyle={{
backgroundColor: 'grey',
outline: '5px dashed lightgrey',
alignSelf: 'center',
outlineOffset: '-10px'
}}>
<Grid
container
direction="column"
justify="flex-start"
alignItems="stretch"
style={{ height: '100%' }}
>
<ChatHeader currentChannel={currentChannel}/>
<Divider/>
<AutoScrollList style={listStyle}>
{messages[currentChannel] && messages[currentChannel].map((message) => (
<Fragment key={message.data.payload}>
<ChatBox {...message} ipfs={ipfs}/>
<li>
<Divider/>
</li>
</Fragment>
))}
</AutoScrollList>
<Formik
initialValues={{ chatInput: '' }}
onSubmit={(values, { setSubmitting, resetForm }) => {
const { chatInput } = values;
sendMessage(chatInput);
resetForm();
setSubmitting(false);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
}) => (
<div className="chat-input"
style={{ position: 'absolute', bottom: 0, left: 0, right: 0, paddingBottom: 10 }}>
<form onSubmit={handleSubmit} style={formStyle} ref={ChatRoomForm}>
<TextField
id="chatInput"
multiline
style={{ width: 'auto', flexGrow: '0.95', margin: '2px 0 0 0' }}
label="Type a message..."
type="text"
name="chatInput"
margin="normal"
variant="outlined"
fullWidth
onChange={handleChange}
onKeyDown={(e) => keyDownHandler(e, typingEvent, setFieldValue, values.chatInput)}
onBlur={handleBlur}
value={values.chatInput || ''}
/>
{showEmojis && <Picker onSelect={(emoji) => this.addEmoji(emoji, values.chatInput, setFieldValue)}
style={{ position: 'absolute', bottom: '80px', right: '20px' }}/>}
<Button onClick={(e) => this.toggleEmojis(e)}>Smile</Button>
{errors.chatInput && touched.chatInput && errors.chatInput}
</form>
<WhoIsTyping
currentChannel={currentChannel}
usersTyping={usersTyping}
users={allUsers}/>
</div>
)}
</Formik>
</Grid>
</Dropzone>
</Grid>
</Dropzone>
</Grid>
<Grid xs={4} item style={{overflow: 'auto'}}>
<List>
{Object.keys(channelUsers).map(user => (
<ListItem button key={user}>
<span className="dot" style={{"height": "10px", "width": "11px", "background-color": (allUsers[user].online ? "lightgreen" : "lightgrey"), "border-radius": "50%", "margin-right": "10px"}}/>
<ListItemAvatar>
<Avatar>
<Jazzicon diameter={40} seed={jsNumberForAddress(user)} />
</Avatar>
</ListItemAvatar>
<ListItemText primary={allUsers[user].username} />
</ListItem>
))}
</List>
</Grid>
</Grid>
);
<Grid xs={4} item style={{ overflow: 'auto' }}>
<List>
{Object.keys(channelUsers).map(user => (
<ListItem button key={user}>
<span className="dot" style={{
'height': '10px',
'width': '11px',
'background-color': (allUsers[user].online ? 'lightgreen' : 'lightgrey'),
'border-radius': '50%',
'margin-right': '10px'
}}/>
<ListItemAvatar>
<Avatar>
<Jazzicon diameter={40} seed={jsNumberForAddress(user)}/>
</Avatar>
</ListItemAvatar>
<ListItemText primary={allUsers[user].username}/>
</ListItem>
))}
</List>
</Grid>
</Grid>
)
}
}
export default ChatRoom;

5
package-lock.json generated
View File

@ -7094,6 +7094,11 @@
"minimalistic-crypto-utils": "^1.0.0"
}
},
"emoji-mart": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-2.8.1.tgz",
"integrity": "sha512-9ScBU9ObG4qVQOMvQWQwUkYVNK/eOu/TRk8o+LABtnL2BE/o9CU4OvtjcoBPNiC/7QtRhW9vOwmvUx/OjT+mwA=="
},
"emoji-regex": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",

View File

@ -264,6 +264,7 @@
"electron-debug": "^2.0.0",
"electron-log": "^2.2.17",
"electron-updater": "^3.1.6",
"emoji-mart": "^2.8.1",
"eth-keyring-controller": "^3.3.1",
"formik": "^1.3.1",
"history": "^4.7.2",

View File

@ -5144,6 +5144,10 @@ elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
emoji-mart@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-2.8.1.tgz#4ffcb807237989a953085c80a6553d21f1effc62"
emoji-regex@^6.5.1:
version "6.5.1"
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"