├── .gitattributes ├── .gitignore ├── package.json ├── README.md ├── public └── css │ ├── reset.css │ └── style.css ├── index.js └── index.html /.gitattributes: -------------------------------------------------------------------------------- 1 | *.* linguist-language=JavaScript 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_STORE 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatter", 3 | "private": true, 4 | "description": "A chatting app using socket.io", 5 | "author": "Sohel Amin", 6 | "license": "MIT", 7 | "dependencies": { 8 | "express": "^4.15.2", 9 | "lodash": "^4.17.4", 10 | "socket.io": "^1.7.3" 11 | }, 12 | "scripts": { 13 | "start": "node index.js" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chatter 2 | A chatting application using socket.io, node.js, express.js & vue.js 3 | 4 | ### Requirements 5 | Express >= 4.0 6 | Socket.io >= 1.0 7 | 8 | ## Usage 9 | 10 | 1. Clone and navigate to this project directory 11 | 12 | 2. Install the dependencies 13 | ```bash 14 | npm install 15 | ``` 16 | 17 | 3. Run the node/express http server 18 | ```bash 19 | node index.js 20 | ``` 21 | 4. Open the url `http://localhost:3000` in multiple browsers or clients for seeing the result 22 | 23 | ## Screencast 24 | ![chatter](https://cloud.githubusercontent.com/assets/1708683/25631136/4d3fac9a-2f91-11e7-9015-e9cb0d9b6af3.png) 25 | 26 | ## Author 27 | 28 | [Sohel Amin](http://www.sohelamin.com) 29 | -------------------------------------------------------------------------------- /public/css/reset.css: -------------------------------------------------------------------------------- 1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0} 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var app = express(); 4 | var http = require('http').Server(app); 5 | var io = require('socket.io')(http); 6 | var _ = require('lodash'); 7 | var port = process.env.PORT || 3000; 8 | 9 | app.use(express.static(path.join(__dirname, 'public'))); 10 | 11 | app.get('/', function(req, res){ 12 | res.sendFile(__dirname + '/index.html'); 13 | }); 14 | 15 | var users = []; 16 | 17 | io.on('connection', function(socket) { 18 | socket.on('online user', function(user) { 19 | user.socket_id = socket.id; 20 | 21 | var foundUser = _.find(users, {id: user.id}); 22 | 23 | if (foundUser) { 24 | foundUser.socket_id = socket.id; 25 | } else { 26 | users.push(user); 27 | } 28 | 29 | io.emit('online users list', users); 30 | }); 31 | 32 | socket.on('chat message', function(msg, toUserID, from) { 33 | var toUser = _.find(users, {id: toUserID}); 34 | 35 | socket.to(toUser.socket_id).emit('chat message', msg, from); 36 | }); 37 | 38 | socket.on('disconnect', function() { 39 | users = _.reject(users, {socket_id: socket.id}); 40 | }); 41 | }); 42 | 43 | 44 | http.listen(port, function(){ 45 | console.log('listening on *:' + port); 46 | }); 47 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chatter - Direct Messaging / Chating App 6 | 7 | 8 | 13 | 14 | 15 |
16 |
17 |
18 |
19 | 20 |
21 |
    22 |
  • 23 | 24 | {{ user.name }} 25 | {{ '4:00 PM' }} 26 | {{ '...' }} 27 |
  • 28 |
29 |
30 |
31 |
To: {{ toUser.name }}
32 |
33 |
34 | {{ msg.text }} 35 |
36 |
37 | 38 |
39 | 40 | 41 | 42 | 43 |
44 |
45 |
46 |
47 | 48 | 49 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600); 2 | * { 3 | -moz-box-sizing: border-box; 4 | -webkit-box-sizing: border-box; 5 | box-sizing: border-box; 6 | } 7 | 8 | body { 9 | background-color: #f8f8f8; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | text-rendering: optimizeLegibility; 13 | font-family: 'Source Sans Pro', sans-serif; 14 | font-weight: 400; 15 | background-color: rgba(71, 75, 199, 0.84); 16 | /* background-image: url("https://www.bing.com/az/hprichbg/rb/LacetsMontvernier_ROW9287513938_1920x1080.jpg"); 17 | background-size: cover; 18 | background-repeat: none;*/ 19 | } 20 | 21 | .wrapper { 22 | position: relative; 23 | left: 50%; 24 | width: 1000px; 25 | height: 800px; 26 | -moz-transform: translate(-50%, 0); 27 | -ms-transform: translate(-50%, 0); 28 | -webkit-transform: translate(-50%, 0); 29 | transform: translate(-50%, 0); 30 | } 31 | 32 | .container { 33 | position: relative; 34 | top: 50%; 35 | left: 50%; 36 | width: 80%; 37 | height: 75%; 38 | background-color: #fff; 39 | -moz-transform: translate(-50%, -50%); 40 | -ms-transform: translate(-50%, -50%); 41 | -webkit-transform: translate(-50%, -50%); 42 | transform: translate(-50%, -50%); 43 | } 44 | .container .left { 45 | float: left; 46 | width: 37.6%; 47 | height: 100%; 48 | border: 1px solid #e6e6e6; 49 | background-color: #fff; 50 | } 51 | .container .left .top { 52 | position: relative; 53 | width: 100%; 54 | height: 96px; 55 | padding: 29px; 56 | } 57 | .container .left .top:after { 58 | position: absolute; 59 | bottom: 0; 60 | left: 50%; 61 | display: block; 62 | width: 80%; 63 | height: 1px; 64 | content: ''; 65 | background-color: #e6e6e6; 66 | -moz-transform: translate(-50%, 0); 67 | -ms-transform: translate(-50%, 0); 68 | -webkit-transform: translate(-50%, 0); 69 | transform: translate(-50%, 0); 70 | } 71 | .container .left input { 72 | float: left; 73 | width: 188px; 74 | height: 42px; 75 | padding: 0 15px; 76 | border: 1px solid #e6e6e6; 77 | background-color: #eceff1; 78 | -moz-border-radius: 21px; 79 | -webkit-border-radius: 21px; 80 | border-radius: 21px; 81 | font-family: 'Source Sans Pro', sans-serif; 82 | font-weight: 400; 83 | } 84 | .container .left input:focus { 85 | outline: none; 86 | } 87 | .container .left a.search { 88 | display: block; 89 | float: left; 90 | width: 42px; 91 | height: 42px; 92 | margin-left: 10px; 93 | border: 1px solid #e6e6e6; 94 | background-color: #00b0ff; 95 | background-image: url("http://s11.postimg.org/dpuahewmn/name_type.png"); 96 | background-repeat: no-repeat; 97 | background-position: top 12px left 14px; 98 | -moz-border-radius: 50%; 99 | -webkit-border-radius: 50%; 100 | border-radius: 50%; 101 | } 102 | .container .left .people { 103 | margin-left: -1px; 104 | border-right: 1px solid #e6e6e6; 105 | border-left: 1px solid #e6e6e6; 106 | width: -moz-calc(100% + 2px); 107 | width: -webkit-calc(100% + 2px); 108 | width: -o-calc(100% + 2px); 109 | width: calc(100% + 2px); 110 | } 111 | .container .left .people .person { 112 | position: relative; 113 | width: 100%; 114 | padding: 12px 10% 16px; 115 | cursor: pointer; 116 | background-color: #fff; 117 | } 118 | .container .left .people .person:after { 119 | position: absolute; 120 | bottom: 0; 121 | left: 50%; 122 | display: block; 123 | width: 80%; 124 | height: 1px; 125 | content: ''; 126 | background-color: #e6e6e6; 127 | -moz-transform: translate(-50%, 0); 128 | -ms-transform: translate(-50%, 0); 129 | -webkit-transform: translate(-50%, 0); 130 | transform: translate(-50%, 0); 131 | } 132 | .container .left .people .person img { 133 | float: left; 134 | width: 40px; 135 | height: 40px; 136 | margin-right: 12px; 137 | -moz-border-radius: 50%; 138 | -webkit-border-radius: 50%; 139 | border-radius: 50%; 140 | } 141 | .container .left .people .person .name { 142 | font-size: 14px; 143 | line-height: 22px; 144 | color: #1a1a1a; 145 | font-family: 'Source Sans Pro', sans-serif; 146 | font-weight: 600; 147 | } 148 | .container .left .people .person .time { 149 | font-size: 14px; 150 | position: absolute; 151 | top: 16px; 152 | right: 10%; 153 | padding: 0 0 5px 5px; 154 | color: #999; 155 | background-color: #fff; 156 | } 157 | .container .left .people .person .preview { 158 | font-size: 14px; 159 | display: inline-block; 160 | overflow: hidden !important; 161 | width: 70%; 162 | white-space: nowrap; 163 | text-overflow: ellipsis; 164 | color: #999; 165 | } 166 | .container .left .people .person.active, .container .left .people .person:hover { 167 | margin-top: -1px; 168 | margin-left: -1px; 169 | padding-top: 13px; 170 | border: 0; 171 | background-color: #00b0ff; 172 | width: -moz-calc(100% + 2px); 173 | width: -webkit-calc(100% + 2px); 174 | width: -o-calc(100% + 2px); 175 | width: calc(100% + 2px); 176 | padding-left: -moz-calc(10% + 1px); 177 | padding-left: -webkit-calc(10% + 1px); 178 | padding-left: -o-calc(10% + 1px); 179 | padding-left: calc(10% + 1px); 180 | } 181 | .container .left .people .person.active span, .container .left .people .person:hover span { 182 | color: #fff; 183 | background: transparent; 184 | } 185 | .container .left .people .person.active:after, .container .left .people .person:hover:after { 186 | display: none; 187 | } 188 | .container .right { 189 | position: relative; 190 | float: left; 191 | width: 62.4%; 192 | height: 100%; 193 | } 194 | .container .right .top { 195 | width: 100%; 196 | height: 47px; 197 | padding: 15px 29px; 198 | background-color: #eceff1; 199 | } 200 | .container .right .top span { 201 | font-size: 15px; 202 | color: #999; 203 | } 204 | .container .right .top span .name { 205 | color: #1a1a1a; 206 | font-family: 'Source Sans Pro', sans-serif; 207 | font-weight: 600; 208 | } 209 | .container .right .chat { 210 | position: relative; 211 | display: none; 212 | overflow: hidden; 213 | padding: 0 35px 92px; 214 | border-width: 1px 1px 1px 0; 215 | border-style: solid; 216 | border-color: #e6e6e6; 217 | height: -moz-calc(100% - 48px); 218 | height: -webkit-calc(100% - 48px); 219 | height: -o-calc(100% - 48px); 220 | height: calc(100% - 48px); 221 | -webkit-justify-content: flex-end; 222 | justify-content: flex-end; 223 | -webkit-flex-direction: column; 224 | flex-direction: column; 225 | } 226 | .container .right .chat.active-chat { 227 | display: block; 228 | display: -webkit-flex; 229 | display: flex; 230 | } 231 | .container .right .chat.active-chat .bubble { 232 | -moz-transition-timing-function: cubic-bezier(0.4, -0.04, 1, 1); 233 | -o-transition-timing-function: cubic-bezier(0.4, -0.04, 1, 1); 234 | -webkit-transition-timing-function: cubic-bezier(0.4, -0.04, 1, 1); 235 | transition-timing-function: cubic-bezier(0.4, -0.04, 1, 1); 236 | } 237 | .container .right .chat.active-chat .bubble:nth-of-type(1) { 238 | -moz-animation-duration: 0.15s; 239 | -webkit-animation-duration: 0.15s; 240 | animation-duration: 0.15s; 241 | } 242 | .container .right .chat.active-chat .bubble:nth-of-type(2) { 243 | -moz-animation-duration: 0.3s; 244 | -webkit-animation-duration: 0.3s; 245 | animation-duration: 0.3s; 246 | } 247 | .container .right .chat.active-chat .bubble:nth-of-type(3) { 248 | -moz-animation-duration: 0.45s; 249 | -webkit-animation-duration: 0.45s; 250 | animation-duration: 0.45s; 251 | } 252 | .container .right .chat.active-chat .bubble:nth-of-type(4) { 253 | -moz-animation-duration: 0.6s; 254 | -webkit-animation-duration: 0.6s; 255 | animation-duration: 0.6s; 256 | } 257 | .container .right .chat.active-chat .bubble:nth-of-type(5) { 258 | -moz-animation-duration: 0.75s; 259 | -webkit-animation-duration: 0.75s; 260 | animation-duration: 0.75s; 261 | } 262 | .container .right .chat.active-chat .bubble:nth-of-type(6) { 263 | -moz-animation-duration: 0.9s; 264 | -webkit-animation-duration: 0.9s; 265 | animation-duration: 0.9s; 266 | } 267 | .container .right .chat.active-chat .bubble:nth-of-type(7) { 268 | -moz-animation-duration: 1.05s; 269 | -webkit-animation-duration: 1.05s; 270 | animation-duration: 1.05s; 271 | } 272 | .container .right .chat.active-chat .bubble:nth-of-type(8) { 273 | -moz-animation-duration: 1.2s; 274 | -webkit-animation-duration: 1.2s; 275 | animation-duration: 1.2s; 276 | } 277 | .container .right .chat.active-chat .bubble:nth-of-type(9) { 278 | -moz-animation-duration: 1.35s; 279 | -webkit-animation-duration: 1.35s; 280 | animation-duration: 1.35s; 281 | } 282 | .container .right .chat.active-chat .bubble:nth-of-type(10) { 283 | -moz-animation-duration: 1.5s; 284 | -webkit-animation-duration: 1.5s; 285 | animation-duration: 1.5s; 286 | } 287 | .container .right .write { 288 | position: absolute; 289 | bottom: 29px; 290 | left: 30px; 291 | height: 42px; 292 | padding-left: 8px; 293 | border: 1px solid #e6e6e6; 294 | background-color: #eceff1; 295 | width: -moz-calc(100% - 58px); 296 | width: -webkit-calc(100% - 58px); 297 | width: -o-calc(100% - 58px); 298 | width: calc(100% - 58px); 299 | -moz-border-radius: 5px; 300 | -webkit-border-radius: 5px; 301 | border-radius: 5px; 302 | } 303 | .container .right .write input { 304 | font-size: 16px; 305 | float: left; 306 | width: 347px; 307 | height: 40px; 308 | padding: 0 10px; 309 | color: #1a1a1a; 310 | border: 0; 311 | outline: none; 312 | background-color: #eceff1; 313 | font-family: 'Source Sans Pro', sans-serif; 314 | font-weight: 400; 315 | } 316 | .container .right .write .write-link.attach:before { 317 | display: inline-block; 318 | float: left; 319 | width: 20px; 320 | height: 42px; 321 | content: ''; 322 | background-image: url("http://s1.postimg.org/s5gfy283f/attachemnt.png"); 323 | background-repeat: no-repeat; 324 | background-position: center; 325 | } 326 | .container .right .write .write-link.smiley:before { 327 | display: inline-block; 328 | float: left; 329 | width: 20px; 330 | height: 42px; 331 | content: ''; 332 | background-image: url("http://s14.postimg.org/q2ug83h7h/smiley.png"); 333 | background-repeat: no-repeat; 334 | background-position: center; 335 | } 336 | .container .right .write .write-link.send:before { 337 | display: inline-block; 338 | float: left; 339 | width: 20px; 340 | height: 42px; 341 | margin-left: 11px; 342 | content: ''; 343 | background-image: url("http://s30.postimg.org/nz9dho0pp/send.png"); 344 | background-repeat: no-repeat; 345 | background-position: center; 346 | } 347 | .container .right .bubble { 348 | font-size: 16px; 349 | position: relative; 350 | display: inline-block; 351 | clear: both; 352 | margin-bottom: 8px; 353 | padding: 13px 14px; 354 | vertical-align: top; 355 | -moz-border-radius: 5px; 356 | -webkit-border-radius: 5px; 357 | border-radius: 5px; 358 | } 359 | .container .right .bubble:before { 360 | position: absolute; 361 | top: 19px; 362 | display: block; 363 | width: 8px; 364 | height: 6px; 365 | content: '\00a0'; 366 | -moz-transform: rotate(29deg) skew(-35deg); 367 | -ms-transform: rotate(29deg) skew(-35deg); 368 | -webkit-transform: rotate(29deg) skew(-35deg); 369 | transform: rotate(29deg) skew(-35deg); 370 | } 371 | .container .right .bubble.you { 372 | float: left; 373 | color: #fff; 374 | background-color: #00b0ff; 375 | -webkit-align-self: flex-start; 376 | align-self: flex-start; 377 | -moz-animation-name: slideFromLeft; 378 | -webkit-animation-name: slideFromLeft; 379 | animation-name: slideFromLeft; 380 | } 381 | .container .right .bubble.you:before { 382 | left: -3px; 383 | background-color: #00b0ff; 384 | } 385 | .container .right .bubble.me { 386 | float: right; 387 | color: #1a1a1a; 388 | background-color: #eceff1; 389 | -webkit-align-self: flex-end; 390 | align-self: flex-end; 391 | -moz-animation-name: slideFromRight; 392 | -webkit-animation-name: slideFromRight; 393 | animation-name: slideFromRight; 394 | } 395 | .container .right .bubble.me:before { 396 | right: -3px; 397 | background-color: #eceff1; 398 | } 399 | .container .right .conversation-start { 400 | position: relative; 401 | width: 100%; 402 | margin-bottom: 27px; 403 | text-align: center; 404 | } 405 | .container .right .conversation-start span { 406 | font-size: 14px; 407 | display: inline-block; 408 | color: #999; 409 | } 410 | .container .right .conversation-start span:before, .container .right .conversation-start span:after { 411 | position: absolute; 412 | top: 10px; 413 | display: inline-block; 414 | width: 30%; 415 | height: 1px; 416 | content: ''; 417 | background-color: #e6e6e6; 418 | } 419 | .container .right .conversation-start span:before { 420 | left: 0; 421 | } 422 | .container .right .conversation-start span:after { 423 | right: 0; 424 | } 425 | 426 | @keyframes slideFromLeft { 427 | 0% { 428 | margin-left: -200px; 429 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 430 | opacity: 0; 431 | } 432 | 100% { 433 | margin-left: 0; 434 | filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); 435 | opacity: 1; 436 | } 437 | } 438 | @-webkit-keyframes slideFromLeft { 439 | 0% { 440 | margin-left: -200px; 441 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 442 | opacity: 0; 443 | } 444 | 100% { 445 | margin-left: 0; 446 | filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); 447 | opacity: 1; 448 | } 449 | } 450 | @keyframes slideFromRight { 451 | 0% { 452 | margin-right: -200px; 453 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 454 | opacity: 0; 455 | } 456 | 100% { 457 | margin-right: 0; 458 | filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); 459 | opacity: 1; 460 | } 461 | } 462 | @-webkit-keyframes slideFromRight { 463 | 0% { 464 | margin-right: -200px; 465 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); 466 | opacity: 0; 467 | } 468 | 100% { 469 | margin-right: 0; 470 | filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false); 471 | opacity: 1; 472 | } 473 | } 474 | .credits { 475 | color: white; 476 | font-size: 11px; 477 | position: absolute; 478 | bottom: 10px; 479 | right: 15px; 480 | } 481 | .credits a { 482 | color: white; 483 | text-decoration: none; 484 | } 485 | --------------------------------------------------------------------------------