diff --git a/package.json b/package.json index 05061c0..f52b31e 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "eslint-plugin-import": "^2.2.0", "eslint-plugin-jest": "^20.0.0", "eslint-plugin-jsx-a11y": "^2.2.3", - "eslint-plugin-react": "^7.0.0", + "eslint-plugin-react": "^6.10.3", "identity-obj-proxy": "^3.0.0", "jest": "^20.0.0", "preact-jsx-chai": "^2.2.1", diff --git a/src/components/App/index.js b/src/components/App/index.js index be6aa40..2cda5ff 100644 --- a/src/components/App/index.js +++ b/src/components/App/index.js @@ -15,6 +15,7 @@ import './styles.css'; const MESSENGER = 'messenger'; const FLOAT = 'float'; const SIDEPANEL = 'side'; +const TYPING_TIMEOUT_DELAY = 1000; class App extends Component { state = { @@ -27,9 +28,14 @@ class App extends Component { componentDidMount () { this.socket = createSocket(this); + this.typingTimeout = null; } - // --- UI / Event Methods + componentWillUnmount () { + window.clearTimeout(this.state.typingTimeout); + } + + // --- UI / Event Handlers toggleChat = bool => { this.setState({ chatOpen: bool }); @@ -39,7 +45,29 @@ class App extends Component { this.setState({ textBox: e.target.value }); }; - // --- Socket Methods + // --- Socket Methods + + handleKeyDown = e => { + const payload = JSON.stringify( + formatMessageForServer(null, this.state.session.client.id, this.state.session.id) + ); + + // user is still typing + window.clearTimeout(this.typingTimeout); + + // sending message + if (e.key === 'Enter') { + this.socket.emit('client:idle', payload); + return + } + + this.socket.emit('client:typing', payload); + + this.typingTimeout = window.setTimeout(() => { + // user is no longer typing + this.socket.emit('client:idle', payload); + }, TYPING_TIMEOUT_DELAY); + }; /** * @description On connecting to the socket server save a session object into the state @@ -58,7 +86,7 @@ class App extends Component { handleReconnecting = attempts => { // eslint-disable-next-line - const attemptLimit = this.socket.io._reconnectionAttempts; + const attemptLimit = this.socket.io._reconnectionAttempts; if (attempts < attemptLimit) { this.setState({ network: 'reconnecting', @@ -126,14 +154,15 @@ class App extends Component { renderClosedChat = () => ; renderOpenChat = () => - (); + />; renderChat = () => (this.state.chatOpen ? this.renderOpenChat() : this.renderClosedChat()); diff --git a/src/components/Chat/index.js b/src/components/Chat/index.js index dbc418a..28f3191 100644 --- a/src/components/Chat/index.js +++ b/src/components/Chat/index.js @@ -19,6 +19,7 @@ class Chat extends Component { propTypes = { toggleChat: PropTypes.func, handleInput: PropTypes.func, + handleKeyDown: PropTypes.func, sendMessage: PropTypes.func, network: PropTypes.string, theme: PropTypes.string, @@ -48,7 +49,7 @@ class Chat extends Component { this.props.messages.map(msg => ); render () { - const { toggleChat, textBox, handleInput, sendMessage, theme } = this.props; + const { toggleChat, textBox, handleInput, handleKeyDown, sendMessage, theme } = this.props; return (
@@ -60,7 +61,12 @@ class Chat extends Component { {this.renderMessages()} - +
); } diff --git a/src/components/Input/index.js b/src/components/Input/index.js index f07bb4a..6552318 100644 --- a/src/components/Input/index.js +++ b/src/components/Input/index.js @@ -6,13 +6,14 @@ import { applyTheme } from '../ThemeProvider'; import './styles.css'; const Input = props => { - const { sendMessage, theme, textBox, handleInput } = props; + const { sendMessage, theme, textBox, handleInput, handleKeyDown } = props; return (
handleInput(e)} + onKeyDown={e => handleKeyDown(e)} name="messages" value={textBox} /> @@ -22,6 +23,7 @@ const Input = props => { Input.propTypes = { handleInput: PropTypes.func, + handleKeyDown: PropTypes.func, sendMessage: PropTypes.func, theme: PropTypes.string, diff --git a/src/components/Message/index.js b/src/components/Message/index.js index d042c6e..0ef483e 100644 --- a/src/components/Message/index.js +++ b/src/components/Message/index.js @@ -8,9 +8,9 @@ import './styles.css'; const Message = props => { // our message's content const content = props.content.map((msg, i) => - (
  • +
  • {msg} -
  • ) + ); // our default message is a client message diff --git a/webpack.config.js b/webpack.config.js index d564326..1e2f7c8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -24,7 +24,7 @@ if (!development) { // Minify source on production only plugins.push(new webpack.optimize.UglifyJsPlugin()); - // Strip out babel-helper invariant checks + // Strip out babel-helper invariant checks plugins.push(new ReplacePlugin([ { // This is actually the property name https://github.com/kimhou/replace-bundle-webpack-plugin/issues/1 @@ -34,7 +34,7 @@ if (!development) { ])); } -module.exports = { +module.exports = { context: PATHS.SRC, entry: PATHS.SRC + '/index.js', output: { @@ -44,7 +44,7 @@ module.exports = { }, module: { rules: [ - { + { test: /\.jsx?$/, include: [ PATHS.SRC ], exclude: [ PATHS.MODULES ], @@ -55,7 +55,7 @@ module.exports = { 'transform-decorators-legacy', [ 'transform-react-jsx', { 'pragma': 'h' } ], ], - }, + }, }, { test: /\.css$/, @@ -84,7 +84,7 @@ module.exports = { // TODO: What is cheap-module-eval-source-map? devtool: development ? 'source-map' : false, devServer: { - port: process.env.PORT || 8080, + port: process.env.PORT || 3000, host: process.env.HOST || 'localhost', publicPath: '/', contentBase: './example',