├── .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 | 
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 |
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 |
--------------------------------------------------------------------------------