diff --git a/app.js b/app.js index e40aa191cd..5ea5275cf4 100644 --- a/app.js +++ b/app.js @@ -42,7 +42,20 @@ var passportConf = require('./config/passport'); * Create Express server. */ +var socket = require('socket.io'); +var express = require('express'); +var http = require('http'); + var app = express(); +var server = http.createServer(app); + +server.listen(2999, function() { + console.log('server started on %d', 2999); +}); + +var io = socket.listen(server); + +//console.log('Express server started on port %s', server.address().port); /** * Connect to MongoDB. @@ -205,4 +218,71 @@ app.listen(app.get('port'), function() { console.log('Express server listening on port %d in %s mode', app.get('port'), app.get('env')); }); -module.exports = app; \ No newline at end of file +module.exports = app; + +/** + * Chat + */ + +var usernames = {}; +var numUsers = 0; + +io.on('connection', function (socket) { + var addedUser = false; + + // when the client emits 'new message', this listens and executes + socket.on('new message', function (data) { + // we tell the client to execute 'new message' + socket.broadcast.emit('new message', { + username: socket.username, + message: data + }); + }); + + // when the client emits 'add user', this listens and executes + socket.on('add user', function (username) { + // we store the username in the socket session for this client + socket.username = username; + // add the client's username to the global list + usernames[username] = username; + ++numUsers; + addedUser = true; + socket.emit('login', { + numUsers: numUsers + }); + // echo globally (all clients) that a person has connected + socket.broadcast.emit('user joined', { + username: socket.username, + numUsers: numUsers + }); + }); + + // when the client emits 'typing', we broadcast it to others + socket.on('typing', function () { + socket.broadcast.emit('typing', { + username: socket.username + }); + }); + + // when the client emits 'stop typing', we broadcast it to others + socket.on('stop typing', function () { + socket.broadcast.emit('stop typing', { + username: socket.username + }); + }); + + // when the user disconnects.. perform this + socket.on('disconnect', function () { + // remove the username from global usernames list + if (addedUser) { + delete usernames[socket.username]; + --numUsers; + + // echo globally that this client has left + socket.broadcast.emit('user left', { + username: socket.username, + numUsers: numUsers + }); + } + }); +}); \ No newline at end of file diff --git a/config/passport.js b/config/passport.js index 685d915050..69d1b99a93 100644 --- a/config/passport.js +++ b/config/passport.js @@ -64,6 +64,7 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok user.profile.name = user.profile.name || profile.displayName; user.profile.location = user.profile.location || profile._json.location; user.profile.picture = user.profile.picture || profile._json.profile_image_url_https; + user.profile.username = profile.displayName; user.save(function(err) { req.flash('info', { msg: 'Twitter account has been linked.' }); done(err, user); diff --git a/controllers/curriculum.js b/controllers/curriculum.js index 3c44bfbc1e..4955e5f06e 100644 --- a/controllers/curriculum.js +++ b/controllers/curriculum.js @@ -6,7 +6,5 @@ exports.index = function(req, res) { res.render('curriculum/curriculum', { title: 'Curriculum', - test: 'hi', - courses: Course.all }); }; \ No newline at end of file diff --git a/models/User.js b/models/User.js index 95b9a69bd2..66c32ffbb6 100644 --- a/models/User.js +++ b/models/User.js @@ -15,7 +15,8 @@ var userSchema = new mongoose.Schema({ gender: { type: String, default: '' }, location: { type: String, default: '' }, website: { type: String, default: '' }, - picture: { type: String, default: '' } + picture: { type: String, default: '' }, + username: { type: String, default: '' } }, resetPasswordToken: String, diff --git a/package.json b/package.json index e21b1f08e2..566d3c59e3 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "passport-oauth": "^1.0.0", "passport-twitter": "^1.0.2", "request": "^2.44.0", + "socket.io": "^1.1.0", "stripe": "^2.8.0", "tumblr.js": "^0.0.4", "twilio": "^1.7.0", diff --git a/public/css/firechat-default.css b/public/css/firechat-default.css deleted file mode 100644 index abdf597dde..0000000000 --- a/public/css/firechat-default.css +++ /dev/null @@ -1,944 +0,0 @@ -@charset "UTF-8"; -/* Boilerplate: Reset -============================================================ */ -#firechat div, -#firechat span, -#firechat applet, -#firechat object, -#firechat iframe, -#firechat h1, -#firechat h2, -#firechat h3, -#firechat h4, -#firechat h5, -#firechat h6, -#firechat p, -#firechat blockquote, -#firechat pre, -#firechat a, -#firechat abbr, -#firechat acronym, -#firechat address, -#firechat big, -#firechat cite, -#firechat code, -#firechat del, -#firechat dfn, -#firechat em, -#firechat img, -#firechat ins, -#firechat kbd, -#firechat q, -#firechat s, -#firechat samp, -#firechat small, -#firechat strike, -#firechat strong, -#firechat sub, -#firechat sup, -#firechat tt, -#firechat var, -#firechat b, -#firechat u, -#firechat i, -#firechat center, -#firechat dl, -#firechat dt, -#firechat dd, -#firechat ol, -#firechat ul, -#firechat li, -#firechat fieldset, -#firechat form, -#firechat label, -#firechat legend, -#firechat table, -#firechat caption, -#firechat tbody, -#firechat tfoot, -#firechat thead, -#firechat tr, -#firechat th, -#firechat td, -#firechat article, -#firechat aside, -#firechat canvas, -#firechat details, -#firechat embed, -#firechat figure, -#firechat figcaption, -#firechat footer, -#firechat header, -#firechat hgroup, -#firechat menu, -#firechat nav, -#firechat output, -#firechat ruby, -#firechat section, -#firechat summary, -#firechat time, -#firechat mark, -#firechat audio, -#firechat video { - border: 0; - font-size: 12px; - font-family: arial, helvetica, sans-serif; - vertical-align: baseline; - margin: 0; - padding: 0; -} -#firechat article, -#firechat aside, -#firechat details, -#firechat figcaption, -#firechat figure, -#firechat footer, -#firechat header, -#firechat hgroup, -#firechat menu, -#firechat nav, -#firechat section { - display: block; -} -#firechat body { - line-height: 1; -} -#firechat ol, -#firechat ul { - list-style: none; -} -#firechat blockquote, -#firechat q { - quotes: none; -} -#firechat blockquote:before, -#firechat blockquote:after, -#firechat q:before, -#firechat q:after { - content: none; -} -#firechat table { - border-collapse: collapse; - border-spacing: 0; -} -/* Boilerplate: Mixins -============================================================ */ -.clearfix { - *zoom: 1; -} -.clearfix:before, -.clearfix:after { - display: table; - content: ""; - line-height: 0; -} -.clearfix:after { - clear: both; -} -/* Boilerplate: Responsive Layout -============================================================ */ -#firechat { - color: #333; - text-align: left; -} -#firechat .center { - float: none !important; - margin-left: auto !important; - margin-right: auto !important; -} -#firechat .left { - float: left !important; -} -#firechat .right { - float: right !important; -} -#firechat .alignleft { - text-align: left !important; -} -#firechat .alignright { - text-align: right !important; -} -#firechat .aligncenter { - text-align: center !important; -} -#firechat .hidden { - display: none !important; -} -#firechat .row { - clear: both; -} -#firechat .fifth, -#firechat .fivesixth, -#firechat .fourfifth, -#firechat .half, -#firechat .ninetenth, -#firechat .quarter, -#firechat .sevententh, -#firechat .sixth, -#firechat .tenth, -#firechat .third, -#firechat .threefifth, -#firechat .threequarter, -#firechat .threetenth, -#firechat .twofifth, -#firechat .twothird, -#firechat .full { - margin-left: 2.127659574468085%; - float: left; - min-height: 1px; -} -#firechat .fifth:first-child, -#firechat .fivesixth:first-child, -#firechat .fourfifth:first-child, -#firechat .half:first-child, -#firechat .ninetenth:first-child, -#firechat .quarter:first-child, -#firechat .sevententh:first-child, -#firechat .sixth:first-child, -#firechat .tenth:first-child, -#firechat .third:first-child, -#firechat .threefifth:first-child, -#firechat .threequarter:first-child, -#firechat .threetenth:first-child, -#firechat .twofifth:first-child, -#firechat .twothird:first-child, -#firechat .full:first-child { - margin-left: 0; -} -#firechat .tenth { - width: 8.08510638297872%; -} -#firechat .sixth { - width: 14.893617021276595%; -} -#firechat .fifth { - width: 18.297872340425535%; -} -#firechat .quarter { - width: 23.404255319148938%; -} -#firechat .threetenth { - width: 26.3829787235%; -} -#firechat .third { - width: 31.914893617021278%; -} -#firechat .twofifth { - width: 38.72340425531915%; -} -#firechat .half { - width: 48.93617021276596%; -} -#firechat .sevententh { - width: 58.7234042555%; -} -#firechat .threefifth { - width: 59.14893617021278%; -} -#firechat .twothird { - width: 65.95744680851064%; -} -#firechat .threequarter { - width: 74.46808510638297%; -} -#firechat .ninetenth { - width: 74.8936170215%; -} -#firechat .fourfifth { - width: 79.57446808510639%; -} -#firechat .fivesixth { - width: 82.9787234042553%; -} -#firechat .full { - width: 100%; -} -#firechat .clipped { - overflow: hidden; -} -#firechat strong { - font-weight: bold; -} -#firechat em { - font-style: italic; -} -#firechat label { - display: block; -} -#firechat a { - color: #005580; -} -#firechat a:visited, -#firechat a:hover, -#firechat a:active { - color: #005580; -} -#firechat p { - margin: 10px 0; -} -#firechat h1, -#firechat h2, -#firechat h3, -#firechat h4, -#firechat h5, -#firechat h6 { - margin: 10px 0; - font-family: inherit; - font-weight: bold; - line-height: 20px; - color: inherit; -} -#firechat h1, -#firechat h2, -#firechat h3 { - line-height: 40px; -} -#firechat h1 { - font-size: 38.5px; -} -#firechat h2 { - font-size: 31.5px; -} -#firechat h3 { - font-size: 24.5px; -} -#firechat h4 { - font-size: 17.5px; -} -#firechat h5 { - font-size: 14px; -} -#firechat h6 { - font-size: 11.9px; -} -#firechat small { - font-size: 90%; -} -/* Component: Tabs -============================================================ */ -#firechat .nav { - list-style: none; -} -#firechat .nav > li > a { - display: block; - background-color: #eeeeee; - text-decoration: none; - overflow: hidden; - white-space: nowrap; -} -#firechat .nav > li > a:hover, -#firechat .nav > li > a:focus { - background-color: #ffffff; -} -#firechat .nav-tabs { - border-bottom: 1px solid #ddd; - clear: both; -} -#firechat .nav-tabs > li { - float: left; - margin-bottom: -1px; - max-width: 45%; -} -#firechat .nav-tabs > li > a { - -webkit-border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 0; - -webkit-border-bottom-left-radius: 0; - -webkit-border-top-left-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 0; - -moz-border-radius-bottomleft: 0; - -moz-border-radius-topleft: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - border-top-left-radius: 4px; - padding: 4px 8px; - margin-right: 2px; - line-height: 20px; - border: 1px solid transparent; - border-color: #cccccc; -} -#firechat .nav-tabs > .active > a, -#firechat .nav-tabs > .active > a:hover, -#firechat .nav-tabs > .active > a:focus { - border-bottom-color: transparent; - background-color: #ffffff; - cursor: default; -} -#firechat .tab-content { - overflow: auto; -} -#firechat .tab-content > .tab-pane { - display: none; -} -#firechat .tab-content > .active { - display: block; - background-color: #ffffff; -} -/* Component: dropdowns -============================================================ */ -#firechat .caret { - display: inline-block; - width: 0; - height: 0; - vertical-align: top; - border-top: 4px solid #000000; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - content: ""; - margin-top: 8px; - margin-left: 2px; -} -#firechat .firechat-dropdown { - position: relative; -} -#firechat .firechat-dropdown-toggle { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - text-decoration: none; -} -#firechat .firechat-dropdown-toggle:focus, -#firechat .firechat-dropdown-toggle:active { - outline: none; - text-decoration: none; -} -#firechat .firechat-dropdown-toggle.btn { - padding: 4px 0 0; - height: 22px; -} -#firechat .firechat-dropdown-menu { - *zoom: 1; - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-right-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 0; - -moz-border-radius-topright: 0; - -moz-border-radius-bottomright: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; - border-top-left-radius: 0; - z-index: 1000; - display: none; - float: left; - position: absolute; - top: 100%; - left: 0; - width: 100%; - background-color: #ffffff; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; - border: 1px solid #ccc; - min-width: 98%; - padding: 0; - margin: -1px 0 0; -} -#firechat .firechat-dropdown-menu:before, -#firechat .firechat-dropdown-menu:after { - display: table; - content: ""; - line-height: 0; -} -#firechat .firechat-dropdown-menu:after { - clear: both; -} -#firechat .firechat-dropdown-menu ul { - background-color: #ffffff; - list-style: none; - overflow-y: scroll; - max-height: 300px; -} -#firechat .firechat-dropdown-menu ul > li > a { - display: block; - padding: 1px 1px 1px 3px; - clear: both; - font-weight: normal; - line-height: 20px; - color: #333333; - white-space: nowrap; -} -#firechat .firechat-dropdown-menu ul > li > a.highlight { - background-color: #d9edf7; -} -#firechat .firechat-dropdown-menu ul > li > a:hover, -#firechat .firechat-dropdown-menu ul > li > a:focus, -#firechat .firechat-dropdown-menu ul > .active > a, -#firechat .firechat-dropdown-menu ul > .active > a:hover, -#firechat .firechat-dropdown-menu ul > .active > a:focus { - text-decoration: none; - color: #000000; - background-color: #d9edf7; - outline: 0; -} -#firechat .firechat-dropdown-menu ul > .disabled > a, -#firechat .firechat-dropdown-menu ul > .disabled > a:hover, -#firechat .firechat-dropdown-menu ul > .disabled > a:focus { - color: #999999; - text-decoration: none; - background-color: transparent; - background-image: none; - cursor: default; -} -#firechat .firechat-dropdown-header { - position: relative; - width: 100%; - padding: 10px 0; - background-color: #eeeeee; - border-bottom: 1px solid #cccccc; -} -#firechat .firechat-dropdown-footer { - position: relative; - width: 100%; - padding: 10px 0px; - background-color: #eeeeee; - border-top: 1px solid #cccccc; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -#firechat .open { - *z-index: 1000; -} -#firechat .open > .firechat-dropdown-menu { - display: block; - border: 1px solid #cccccc; - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-right-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 0; - -moz-border-radius-topright: 0; - -moz-border-radius-bottomright: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; - border-top-left-radius: 0; -} -#firechat .open > .firechat-dropdown-toggle { - outline: none; - text-decoration: none; - -webkit-border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 0; - -webkit-border-bottom-left-radius: 0; - -webkit-border-top-left-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 0; - -moz-border-radius-bottomleft: 0; - -moz-border-radius-topleft: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - border-top-left-radius: 4px; -} -/* Component: Prompts -============================================================ */ -#firechat .prompt-wrapper { - position: absolute; - z-index: 1000; -} -#firechat .prompt { - position: absolute; - z-index: 1001; - background-color: #ffffff; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.45); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.45); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.45); -} -#firechat .prompt-header { - padding: 4px 8px; - font-weight: bold; - background-color: #eeeeee; - border: 1px solid #cccccc; - -webkit-border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 0; - -webkit-border-bottom-left-radius: 0; - -webkit-border-top-left-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 0; - -moz-border-radius-bottomleft: 0; - -moz-border-radius-topleft: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - border-top-left-radius: 4px; -} -#firechat .prompt-header a.close { - opacity: 0.6; - font-size: 13px; - margin-top: 2px; -} -#firechat .prompt-header a.close:hover { - opacity: 0.9; -} -#firechat .prompt-body { - background-color: #ffffff; - padding: 4px 8px; - border-left: 1px solid #cccccc; - border-right: 1px solid #cccccc; -} -#firechat .prompt-footer { - padding: 4px 8px; - background-color: #eeeeee; - border: 1px solid #cccccc; - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-right-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 0; - -moz-border-radius-topright: 0; - -moz-border-radius-bottomright: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; - border-top-left-radius: 0; -} -#firechat .prompt-background { - background-color: #333333; - border: 1px solid #333333; - opacity: 0.8; - z-index: 1000; - height: 100%; - width: 100%; -} -/* Component: Buttons -============================================================ */ -#firechat .btn { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - height: 24px; - display: inline-block; - *display: inline; - *zoom: 1; - padding: 2px 5px; - margin-bottom: 0; - text-align: center; - vertical-align: middle; - cursor: pointer; - color: #333333; - font-size: 12px; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - background-color: #f5f5f5; - background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); - background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); - border-color: #e6e6e6 #e6e6e6 #bfbfbf; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - *background-color: #e6e6e6; - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - border: 1px solid #cccccc; - *border: 0; - border-bottom-color: #b3b3b3; - *margin-left: .3em; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} -#firechat .btn:hover, -#firechat .btn:focus, -#firechat .btn:active, -#firechat .btn.active, -#firechat .btn.disabled, -#firechat .btn[disabled] { - color: #333333; - background-color: #e6e6e6; - *background-color: #d9d9d9; - outline: 0; -} -#firechat .btn:active, -#firechat .btn.active { - background-color: #cccccc; -} -#firechat .btn:first-child { - *margin-left: 0; -} -#firechat .btn:hover, -#firechat .btn:focus { - color: #333333; - text-decoration: none; - background-position: 0 -15px; - -webkit-transition: background-position 0.1s linear; - -moz-transition: background-position 0.1s linear; - -o-transition: background-position 0.1s linear; - transition: background-position 0.1s linear; -} -#firechat .btn.active, -#firechat .btn:active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} -#firechat .btn.disabled, -#firechat .btn[disabled] { - cursor: default; - background-image: none; - opacity: 0.65; - filter: alpha(opacity=65); -} -#firechat .btn.disabled:active, -#firechat .btn[disabled]:active { - -webkit-box-shadow: inherit; - -moz-box-shadow: inherit; - box-shadow: inherit; - background-color: #e6e6e6; -} -/* Component: Context Menu -============================================================ */ -#firechat .contextmenu { - position: fixed; - z-index: 1001; - min-width: 150px; - border: 1px solid #cccccc; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -#firechat .contextmenu ul { - background-color: #ffffff; - list-style: none; -} -#firechat .contextmenu ul > li > a { - display: block; - padding: 3px 10px; - clear: both; - font-weight: normal; - line-height: 20px; - color: #333333; - white-space: nowrap; -} -#firechat .contextmenu ul > li > a.highlight { - background-color: #d9edf7; -} -#firechat .contextmenu ul > li > a:hover, -#firechat .contextmenu ul > li > a:focus { - text-decoration: none; - color: #ffffff; - background-color: #0081c2; - outline: 0; -} -/* Custom Styles -============================================================ */ -#firechat { - padding: 0; - font-family: sans-serif; - font-size: 12px; - line-height: 18px; -} -#firechat input, -#firechat textarea { - width: 100%; - font-family: sans-serif; - font-size: 12px; - line-height: 18px; - padding: 2px 5px; - border: 1px solid #cccccc; - -webkit-border-radius: 1px; - -moz-border-radius: 1px; - border-radius: 1px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} -#firechat input:-moz-placeholder, -#firechat textarea:-moz-placeholder { - color: #aaaaaa; -} -#firechat input:-ms-input-placeholder, -#firechat textarea:-ms-input-placeholder { - color: #aaaaaa; -} -#firechat input::-webkit-input-placeholder, -#firechat textarea::-webkit-input-placeholder { - color: #aaaaaa; -} -#firechat input[disabled], -#firechat textarea[disabled] { - background-color: #eeeeee; -} -#firechat input { - height: 24px; -} -#firechat textarea { - resize: none; - height: 40px; -} -#firechat .search-wrapper { - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; - border: 1px solid #cccccc; - margin: 0 5px; - padding: 2px 5px; - background: #ffffff; -} -#firechat .search-wrapper > input[type=text] { - padding-left: 0px; - border: none; -} -#firechat .search-wrapper > input[type=text]:focus, -#firechat .search-wrapper > input[type=text]:active { - outline: 0; -} -#firechat .chat { - overflow: auto; - -ms-overflow-x: hidden; - overflow-x: hidden; - height: 290px; - position: relative; - margin-bottom: 5px; - border: 1px solid #cccccc; - border-top: none; - overflow-y: scroll; -} -#firechat .chat textarea { - overflow: auto; - vertical-align: top; -} -#firechat .message { - color: #333; - padding: 3px 5px; - border-bottom: 1px solid #ccc; -} -#firechat .message.highlighted { - background-color: #d9edf7; -} -#firechat .message .name { - font-weight: bold; - overflow-x: hidden; -} -#firechat .message.message-self { - color: #2675ab; -} -#firechat .message:nth-child(odd) { - background-color: #f9f9f9; -} -#firechat .message:nth-child(odd).highlighted { - background-color: #d9edf7; -} -#firechat .message:nth-child(odd).message-local { - background-color: #effafc; -} -#firechat .message-content { - word-wrap: break-word; - padding-right: 45px; -} -#firechat .message-content.red { - color: red; -} -#firechat .message.message-notification .message-content { - font-style: italic; -} -#firechat ul::-webkit-scrollbar { - -webkit-appearance: none; - width: 7px; -} -#firechat ul::-webkit-scrollbar-thumb { - border-radius: 4px; - -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); -} -#firechat #firechat-header { - padding: 6px 0 0 0; - height: 40px; -} -#firechat #firechat-tabs { - height: 435px; -} -#firechat #firechat-tab-list { - background-color: #ffffff; -} -#firechat #firechat-tab-content { - width: 100%; - background-color: #ffffff; -} -#firechat .tab-pane-menu { - border: 1px solid #ccc; - border-top: none; - vertical-align: middle; - padding-bottom: 5px; -} -#firechat .tab-pane-menu .firechat-dropdown { - margin: 5px 0 0 5px; -} -#firechat .tab-pane-menu > .icon { - margin: 5px 2px 0; -} -#firechat .icon { - display: inline-block; - *margin-right: .3em; - line-height: 20px; - vertical-align: middle; - background-repeat: no-repeat; - padding: 0; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAL8AAAANEAYAAACoeGM7AAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAM80lEQVR42t2cZ4xWRRfHZ3dhwbK7CJZYYiOxV+wlUQzCKmBBsWPvYjQiaiyxYUwULLErltgrNpRF4q5iV0BRLFE0diUi7LIq6Pqw74f//rzO4Zm9d+5z9c37ni//3OfemXtm5sypc58q55xzrrpauHSpcOONheedJ9x9d2Hv3sLvvxfefbfw5puFf/5Zvr+8VFUl7Ozs/rkNNxTef79w222F77wjPOoo4ccfF/O+UDvGzTVEf8wH1zU1wlJJePTRwv33Fx57rPDnn/1+wbzzG+IXvrhmPUPzY/kJEc/HzqslK1fLLy8cMUK4667CFVYQ/vqr8PXXhU884f8eL6eXXKJ2wqSd3tDY2KOHc84NGqSRnnCC7tbVCdvbhRMnasWnTdt5Z+eca2pK6797Yh6ef1641lrCPfYQfv21UNwl62qv115b2Nws/PZb4d57C3/7LftalaOQ/C63nPCII4Tsg6228scHffqp8LHHhPfe6/8eu49feEH44YfCq64S/v67cMGC7tuvu65w6FDhllsKkS/0zoQJaZzMmSO+JaEzZmgkY8b88Ydzzs2b19HhnHNffSWJ6tVLM1cq/fKLrjXiUaPUrn9/XZ9yip6rqVmyxDnnnnrKvPbAA4V6TTJxP/zgT4zdyC+9JOzXz++PgceSVSS1tUJtE+c22si/f+21Pj9Wwd54o//8ZpsJd9xR2KtX9+8vmkLzcsABPt9z5wr32af88xiOf4oaGoSHH17M+/LKg203aJDwiy+EVh5DyHzuuWcxfDn31lvOOffMM7Nn6z1SsZ2dn30m/OQT4eefC7/8UvjBB0LaVzav7D873vfeE6LQISvv3Of50P6OJetgWFp5ZeFzz5V/b1bEEd1333x80g+O4mWXCXEQMDB33CG8/XYhBhuDmMZnOn30kZ5/+23nnGtpefNN55wbP96Xl/nzhYsWCdvauEbeQP1Of6US/XRZfDz8Bx8U9uwpPPFE4eOPC/EMVl9dOG6ccORI4Q03CA87LHbA5RcCwgO47TbhokXCYcOERCKQ7GJiMLiPokeQ2QCjRwtvukkY6zHg0V10kXDAAH++EIz77hM++aRwxRWFq64q3H57v9/+/YVPPy2cOFF47rnCNE8kll/Gi4eIAWU9v/vOnz8MA4qjb18hG51+2JiLF+ebX/hA4U+b5t+/7jrhAw8IkQ88WvnPiZzi4Q0eXL6/dEJhawaGDydmk4uEQmhu1kj79tVImb8RI8TZwIG0p78ddnAubOhDhIcPIXd4nnh4OHYYzPXXF7K/ed5GBLb/NLKRFOtMfyCZAiIKns8aSVp9xP4i8nv//Ti+icjgl0iDeUsjpKDSyJYeqqs1A+3taDBpkn79Qi5YW9vf5wXq0YP+1L5U6loAUjpW4WPhQhN00EFC2SXnDj1UiAF44w2hTWnEEqkcCIWJYkShQNbDIIJZZx2h9Xhs/7EpFDwCNs6UKf584tGQsiFCGTJEuMEG/jxBzBe/H3+8cJddhBiAZ58thl/eM2uW8MgjhWyAyy8XojhRUGuuKayvF7Jhf/pJ+O67wltvFU6dGscv78fTgg45RPjII+XbYZA23bT8/Vtu8ecBDy9MpHS0EROFr1B76lQp7r32su20EwYMkGSOG4dq4I0a4fDh9G9TQN0TKR0iB8aD3G+9tRDHDoPPenKf53GYiABiPWm7f046SUgKDs8aQwxlVfiQTVXh0BChxir+kDol5WPv24im+Ahcb6iu9jVnR4euGX9oXsLj7GKYHP68eUI8ANsRA7WKE8sNkUopdvgJsW0QUBbc3oewl6GQs9LUDgr85JOF5OZHjRKyESZNEp55ppBIC7IbxgoSkQzt2OgXX1wMv9RCFBAuG4HgSREKU0vB42Jjo6gxtPvtJ8TzTM91+kQOf731hHj4KHybA0bRK8B1bpNNhAqkE8OH40CKLZ3I4a+0kq6l8JuapPAbGx991Lm/r5s8+aFDlWOdOVMuS0ODNm5bm1RWSwsSrJY2FZVGRFYYYgwt+wO5EpeJ4eaa+zxPexQ+/Wcl+iFCxeBvs43QRujW048lu09Y16KI/m3Ewu8h/VS55w/5b7AGMrbW5lyXImQh8EhtqGBDGOu525QDnm5RhOcBzZ8vfO01IYIaev/Mmf5zpALsfSiv5bYLfeqpQhSUrYHY92GYbD+shx0XKSOKlrFk1xHFTQRon7M1FNtepaOErwsvFBIqkzKKlQ8MDoTnCpFKwyCi2FZbTYhDgydO6gfaaaesnFC0xQcUUQwlBi6VpPCHDJGKmDyZGWMldY2nPmmSP5NEdlkJRYSCZr7FRbiIzTX3eZ72tiiclZjf444rzyd6hkid9+fNCNhxvfJKvn5C/YVy9nZVYw89ZCdfI6TVEuCLCI4Zhp+Ojq6FIAdLFR0PjpQOFpyFsQMidw5ZxRo7ATaEI6fMgFD8FJsxXCgee00qSiWPJNfKaYLp08u/P1YQ4ZfxWgWDAcOjZmMR8G+3nZBTPbwffhYuFJ5zjhCPKi9ZBUzkh8dpBTkk2KSKLr1UiAIJEYYhK2GQIOto4OGj8NdYQ4hck8tnvkmtQSGDHKK6On8nUNtIPHwUvp05SeSwYTI1s2YpJhk5kliO/uP4Qe6IxMnhk9JhXqyCtQ4Oz9Oe/nwzl05EtJbI5fMeTs8QscIP+zwtEuA+csz+fvjhOH5tf6SSoZCjEipeKw5MVj12PemZ9tXVvunt2dOuHE+K06oqvbG2lp2ikS1ZIknv3burO45lXn+9kKItOfzQwhO6jR0r5PiXPaUQmzNHkMllIyhMMBudmgKnjqxnwnSssooQhWpTLOR6UVwc/4ul1lYhx7rwRBF4PBEEwxLFavhkeTmmN2aMEAG3ghdrqHx149z555efx9DGY37vvFNoPcxQe1v0SyObe8cRIYf/4otCPHwUPsVge4yXWgTEumWl9nZtsPp6irbk8OVyTJ5sYyRJdGOjkitJjUP3FyzwNzIOV1ZivdinFG3J4dt5twqW9eJ5nUFKDIB1xNIIh8p6oijUa64RcnyT2SLSJHWXdtqKcdD/BRcIifBiCf44TIGe2WILYaiGhWHFoaFGxnxtvnlWDvwZY/yLF6toy+8LF2rGamrESXK8WRIwe7YcjKYmzdD06Xpi7lzFWqR4/1pYeywMT5ncL4rpiiuE9tgnIRyeozP9ZyVSBNZyskHxpFFUCGroOCf36Zf28MtzRCqxOXPaM24iJxsJQQgOCgyiqM4GJvduKZRbjOWXXH+fPkKKiUQmOnyYRBrwZeeXCIxxQ/mPSfpEkZn3KZO+7PHiH38U2mKurQHY478ooHSSaZswgWOZum5uFra2iqPOTs0g93EE4NzWAKZM4RioIoDx4/PNE4YQxWNTs6HUgL1Pe3sMNCuF9qOVG2penIPHYOHokcJ8+WVhS4sQR4NaJP3F7luI9tSALBFxovhBW6uonFSORm7mzMEAzJjhnHMNDdLI9fXgq68651xdnZLYffpY+cpIhLzkUNNySd98I7QKFPs0cGC+4dv3sCAYIE4bhXJboWvwrruExxwjJPWStyjD8/b0AyEiCt4Wh6xBpCi+225CG5kUdWogxC/HMyldshGJ7IhcWIfTTxciLxhWqKgcJxuPc/h2nTAAaYrq4IP9dni01jCEiVM3nIdWdaizUxuTDdvaSgQQ6kfPNTVhKPgOgP7j5id0Dh9DbR05Unlch54PfQeQRuyrUM7ZOoxkCq6+WkikhiOBIQ/JEx7sGWfE8QlZOYKyOi7sS+uQgdn3ra/4LT/ZSS1razEEfBgoOf0rdRUaIB4rXyCedpqQFAu5KzxdPyBJPPRlj7d1Tw89JLznHiGeb+hDFVIWIQPll9SS3ynKUUwj5RWbI6Q/inWVEgJsUxRFKdKi+T3rLKEtshf9IZytPdgIgNM75PA5dWQ9fBAFE0+cu+dDGQyAHwGMHi0P/sordT12LB4+Ct//0Cbvh1zWINr9gAePIYe4thGCbU//sYRHn/fDLBwyawhDOfe8ESbvCx3/zHuKJp5Cil8cVlVlxcjXhv5yICvh4aPwreDlJY5nUay1Cj2rINn2lD4qPX6KwcPzRQHhKeP5WeQ+ng2nSyhCcv4fKip1EuIXRQlfXIMUn7nPl9SkhKhFFM2v7QcDEIoAQoiHbxV+5V/uEgGQAuJLXRQ7X/KS0sHD///8cpcIl/P69MN3GDh2RBzUvvjLFVI2NuK1einty+CsZDMY/z0qyuOvkGwIY8+zMuE2ZYGHj8JHUWQlm/sm1ENRZ81dWrShLEVAy1/20F9EEZrTQnk9HJCNYL+YLMrD+F/jF7IbnBQQNQDOi/MFJ9fk8O26xisMQmb7OykaagD+p/RLl3JNDj+U0gn13z0xLhQ0BtEqfLtP7TXP057+YvcDCpvDInwvYInUcnIqqjLKK2/IydlnF8NHfvq3FP9/ANgTCJ9z9ZF7AAAAAElFTkSuQmCC) no-repeat top left; - opacity: 0.3; - font-size: 22px; - font-family: Arial; - font-weight: bold; - overflow: hidden; -} -#firechat .icon.plus { - margin-top: 0; - vertical-align: top; - background: transparent; -} -#firechat .icon.search { - background-position: 0 0; - width: 13px; - height: 13px; -} -#firechat .icon.close { - background-position: -120px 0; - width: 13px; - height: 13px; -} -#firechat .icon.user-chat { - background-position: -138px 0; - width: 17px; - height: 13px; -} -#firechat .icon.user-group { - background-position: -18px 0; - width: 17px; - height: 13px; -} -#firechat .icon.user-mute { - background-position: -84px 0; - width: 13px; - height: 13px; -} -#firechat .icon.user-mute.red { - background-position: -102px 0; - width: 13px; - height: 13px; -} -#firechat .icon:hover, -#firechat .btn:hover > .icon { - opacity: 0.6; -} -#firechat a > .icon { - margin: 3px 1px; -} \ No newline at end of file diff --git a/public/css/styles.less b/public/css/styles.less index 3b965a33df..9aed791383 100644 --- a/public/css/styles.less +++ b/public/css/styles.less @@ -97,4 +97,127 @@ body { margin: 0; height: 100%; position: relative; } .btn-nav { margin-top: 5px; +} + +ul { + list-style: none; + word-wrap: break-word; +} + +/* Pages */ + +.pages { + height: inherit; + margin: 0; + padding: 0; + width: inherit; +} + +.page { + height: 100%; + position: absolute; + width: 100%; +} + +/* Login Page */ + +.login.page { + background-color: #000; +} + +.login.page .form { + height: 100px; + margin-top: -100px; + position: absolute; + + text-align: center; + top: 50%; + width: 100%; +} + +.login.page .form .usernameInput { + background-color: transparent; + border: none; + border-bottom: 2px solid #fff; + outline: none; + padding-bottom: 15px; + text-align: center; + width: 400px; +} + +.login.page .title { + font-size: 200%; +} + +.login.page .usernameInput { + font-size: 200%; + letter-spacing: 3px; +} + +.login.page .title, .login.page .usernameInput { + color: #fff; + font-weight: 100; +} + +/* Chat page */ + +.chat.page { + display: none; +} + +/* Font */ + +.messages { + font-size: 150%; +} + +.inputMessage { + font-size: 100%; +} + +.log { + color: gray; + font-size: 70%; + margin: 5px; + text-align: center; +} + +/* Messages */ + +.chatArea { + height: 100%; + padding-bottom: 60px; +} + +.messages { + height: 100%; + margin: 0; + overflow-y: scroll; + padding: 10px 20px 10px 20px; +} + +.message.typing .messageBody { + color: gray; +} + +.username { + float: left; + font-weight: 700; + overflow: hidden; + padding-right: 15px; + text-align: right; +} + +/* Input */ + +.inputMessage { + border: 10px solid #000; + bottom: 0; + height: 60px; + left: 0; + outline: none; + padding-left: 10px; + position: absolute; + right: 0; + width: 100%; } \ No newline at end of file diff --git a/public/js/firechat-default.js b/public/js/firechat-default.js deleted file mode 100644 index 298ba2f269..0000000000 --- a/public/js/firechat-default.js +++ /dev/null @@ -1,1869 +0,0 @@ -(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); -this["FirechatDefaultTemplates"] = this["FirechatDefaultTemplates"] || {}; - -this["FirechatDefaultTemplates"]["templates/layout-full.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
\n
\n
\n\n\nChat Rooms\n\n\n
    \n
    \n\n\nVisitors\n\n\n
    \n
    \n
    \n\n\n
    \n
    \n
      \n
      \n
      \n
      \n
      \n
      \n
        \n
        \n
        \n
        ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/layout-popout.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
        \n
        \n
          \n
          \n
          \n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/message-context-menu.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '
          \n\n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/message.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '
          \n
          \n'; if (!disableActions) { ;__p += '\n\n'; } ;__p += '
          \n
          \n' +((__t = ( message )) == null ? '' : __t) +'\n
          \n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/prompt-alert.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
          \n
          ' +__e( message ) +'
          \n

          \n\n

          \n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/prompt-create-room.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
          \n
          Give your chat room a name:
          \n\n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/prompt-invitation.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
          \n
          ' +__e( fromUserName ) +'
          \n

          invited you to join

          \n
          ' +__e( toRoomName ) +'
          \n

          \n\n\n

          \n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/prompt-invite-private.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
          \n
          Invite ' +__e( userName ) +' to ' +__e( roomName ) +'?
          \n

          \n\n\n

          \n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/prompt-invite-reply.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '
          \n
          ' +__e( toUserName ) +'
          \n

          \n'; if (status === 'accepted') { ;__p += ' accepted your invite. '; } else { ;__p += ' declined your invite. '; } ;__p += '\n

          \n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/prompt-user-mute.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
          \n
          ' +__e( userName ) +'
          \n

          \n\n\n

          \n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/prompt.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
          \n
          \n' +__e( title ) +'\nX\n
          \n
          \n' +((__t = ( content )) == null ? '' : __t) +'\n
          \n
          \n
          ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/room-list-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '
        • \n\n' +__e( name ) +'\n\n
        • ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/room-user-list-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '
        • \n\n' +__e( name ) +''; if (!disableActions) { ;__p += '\n \n \n'; } ;__p += '\n\n
        • ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/room-user-search-list-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '
        • \n\n'; if (disableActions) { ;__p += '\n' +__e( name ) +'\n'; } else { ;__p += '\n' +__e( name ) +'\n+\n'; } ;__p += '\n\n
        • ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/tab-content.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
          \n
          \n\n\nIn Room\n\n\n
          \n
            \n
            \n
            \n\n+\nInvite\n\n
            \n
            \n
            \n\n\n
            \n
            \n
              \n
              \n
              \n
              \n
              \n\n\n
              \n
              ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/tab-menu-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '
            • \n' +__e( name ) +'\n
            • ';}return __p}; - -this["FirechatDefaultTemplates"]["templates/user-search-list-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '
            • \n\n'; if (disableActions) { ;__p += '\n' +__e( name ) +'\n'; } else { ;__p += '\n' +__e( name ) +'\n \n'; } ;__p += '\n\n
            • ';}return __p}; -(function($) { - - // Shim for Function.bind(...) - (Required by IE < 9, FF < 4, SF < 6) - if (!Function.prototype.bind) { - Function.prototype.bind = function(oThis) { - if (typeof this !== "function") { - throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); - } - - var aArgs = Array.prototype.slice.call(arguments, 1), - fToBind = this, - fNOP = function() {}, - fBound = function() { - return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, - aArgs.concat(Array.prototype.slice.call(arguments))); - }; - - fNOP.prototype = this.prototype; - fBound.prototype = new fNOP(); - return fBound; - }; - } - - // Shim for Object.keys(...) - (Required by IE < 9, FF < 4) - Object.keys = Object.keys || function(oObj) { - var result = []; - for (var name in oObj) { - if (oObj.hasOwnProperty(name)) { - result.push(name); - } - } - return result; - }; - -})(); - -// Firechat is a simple, easily-extensible data layer for multi-user, -// multi-room chat, built entirely on [Firebase](https://firebase.com). -// -// The Firechat object is the primary conduit for all underlying data events. -// It exposes a number of methods for binding event listeners, creating, -// entering, or leaving chat rooms, initiating chats, sending messages, -// and moderator actions such as warning, kicking, or suspending users. -// -// Firechat.js 1.0.0 -// https://firebase.com -// (c) 2014 Firebase -// License: MIT - -// Setup -// -------------- -(function(Firebase) { - - // Establish a reference to the `window` object, and save the previous value - // of the `Firechat` variable. - var root = this, - previousFirechat = root.Firechat; - - function Firechat(firebaseRef, options) { - - // Instantiate a new connection to Firebase. - this._firebase = firebaseRef; - - // User-specific instance variables. - this._user = null; - this._userId = null; - this._userName = null; - this._isModerator = false; - - // A unique id generated for each session. - this._sessionId = null; - - // A mapping of event IDs to an array of callbacks. - this._events = {}; - - // A mapping of room IDs to a boolean indicating presence. - this._rooms = {}; - - // A mapping of operations to re-queue on disconnect. - this._presenceBits = {}; - - // Commonly-used Firebase references. - this._userRef = null; - this._messageRef = this._firebase.child('room-messages'); - this._roomRef = this._firebase.child('room-metadata'); - this._privateRoomRef = this._firebase.child('room-private-metadata'); - this._moderatorsRef = this._firebase.child('moderators'); - this._suspensionsRef = this._firebase.child('suspensions'); - this._usersOnlineRef = this._firebase.child('user-names-online'); - - // Setup and establish default options. - this._options = options || {}; - - // The number of historical messages to load per room. - this._options.numMaxMessages = this._options.numMaxMessages || 50; - } - - // Run Firechat in *noConflict* mode, returning the `Firechat` variable to - // its previous owner, and returning a reference to the Firechat object. - Firechat.noConflict = function noConflict() { - root.Firechat = previousFirechat; - return Firechat; - }; - - // Export the Firechat object as a global. - root.Firechat = Firechat; - - // Firechat Internal Methods - // -------------- - Firechat.prototype = { - - // Load the initial metadata for the user's account and set initial state. - _loadUserMetadata: function(onComplete) { - var self = this; - - // Update the user record with a default name on user's first visit. - this._userRef.transaction(function(current) { - if (!current || !current.id || !current.name) { - return { - id: self._userId, - name: self._userName - }; - } - }, function(error, committed, snapshot) { - self._user = snapshot.val(); - self._moderatorsRef.child(self._userId).once('value', function(snapshot) { - self._isModerator = !!snapshot.val(); - root.setTimeout(onComplete, 0); - }); - }); - }, - - // Initialize Firebase listeners and callbacks for the supported bindings. - _setupDataEvents: function() { - // Monitor connection state so we can requeue disconnect operations if need be. - this._firebase.root().child('.info/connected').on('value', function(snapshot) { - if (snapshot.val() === true) { - // We're connected (or reconnected)! Set up our presence state. - for (var i = 0; i < this._presenceBits; i++) { - var op = this._presenceBits[i], - ref = this._firebase.root().child(op.ref); - - ref.onDisconnect().set(op.offlineValue); - ref.set(op.onlineValue); - } - } - }, this); - - // Generate a unique session id for the visit. - var sessionRef = this._userRef.child('sessions').push(); - this._sessionId = sessionRef.name(); - this._queuePresenceOperation(sessionRef, true, null); - - // Register our username in the public user listing. - var usernameRef = this._usersOnlineRef.child(this._userName.toLowerCase()); - var usernameSessionRef = usernameRef.child(this._sessionId); - this._queuePresenceOperation(usernameSessionRef, { - id: this._userId, - name: this._userName - }, null); - - // Listen for state changes for the given user. - this._userRef.on('value', this._onUpdateUser, this); - - // Listen for chat invitations from other users. - this._userRef.child('invites').on('child_added', this._onFirechatInvite, this); - - // Listen for messages from moderators and adminstrators. - this._userRef.child('notifications').on('child_added', this._onNotification, this); - }, - - // Append the new callback to our list of event handlers. - _addEventCallback: function(eventId, callback) { - this._events[eventId] = this._events[eventId] || []; - this._events[eventId].push(callback); - }, - - // Retrieve the list of event handlers for a given event id. - _getEventCallbacks: function(eventId) { - if (this._events.hasOwnProperty(eventId)) { - return this._events[eventId]; - } - return []; - }, - - // Invoke each of the event handlers for a given event id with specified data. - _invokeEventCallbacks: function(eventId) { - var args = [], - callbacks = this._getEventCallbacks(eventId); - - Array.prototype.push.apply(args, arguments); - args = args.slice(1); - - for (var i = 0; i < callbacks.length; i += 1) { - callbacks[i].apply(null, args); - } - }, - - // Keep track of on-disconnect events so they can be requeued if we disconnect the reconnect. - _queuePresenceOperation: function(ref, onlineValue, offlineValue) { - ref.onDisconnect().set(offlineValue); - ref.set(onlineValue); - this._presenceBits[ref.toString()] = { - ref: ref, - onlineValue: onlineValue, - offlineValue: offlineValue - }; - }, - - // Remove an on-disconnect event from firing upon future disconnect and reconnect. - _removePresenceOperation: function(path, value) { - var ref = new Firebase(path); - ref.onDisconnect().cancel(); - ref.set(value); - delete this._presenceBits[path]; - }, - - // Event to monitor current user state. - _onUpdateUser: function(snapshot) { - this._user = snapshot.val(); - this._invokeEventCallbacks('user-update', this._user); - }, - - // Event to monitor current auth + user state. - _onAuthRequired: function() { - this._invokeEventCallbacks('auth-required'); - }, - - // Events to monitor room entry / exit and messages additional / removal. - _onEnterRoom: function(room) { - this._invokeEventCallbacks('room-enter', room); - }, - _onNewMessage: function(roomId, snapshot) { - var message = snapshot.val(); - message.id = snapshot.name(); - this._invokeEventCallbacks('message-add', roomId, message); - }, - _onRemoveMessage: function(roomId, snapshot) { - var messageId = snapshot.name(); - this._invokeEventCallbacks('message-remove', roomId, messageId); - }, - _onLeaveRoom: function(roomId) { - this._invokeEventCallbacks('room-exit', roomId); - }, - - // Event to listen for notifications from administrators and moderators. - _onNotification: function(snapshot) { - var notification = snapshot.val(); - if (!notification.read) { - if (notification.notificationType !== 'suspension' || notification.data.suspendedUntil < new Date().getTime()) { - snapshot.ref().child('read').set(true); - } - this._invokeEventCallbacks('notification', notification); - } - }, - - // Events to monitor chat invitations and invitation replies. - _onFirechatInvite: function(snapshot) { - var self = this, - invite = snapshot.val(); - - // Skip invites we've already responded to. - if (invite.status) { - return; - } - - invite.id = invite.id || snapshot.name(); - self.getRoom(invite.roomId, function(room) { - invite.toRoomName = room.name; - self._invokeEventCallbacks('room-invite', invite); - }); - }, - _onFirechatInviteResponse: function(snapshot) { - var self = this, - invite = snapshot.val(); - - invite.id = invite.id || snapshot.name(); - this._invokeEventCallbacks('room-invite-response', invite); - } - }; - - // Firechat External Methods - // -------------- - - // Initialize the library and setup data listeners. - Firechat.prototype.setUser = function(userId, userName, callback) { - var self = this; - - self._firebase.root().child('.info/authenticated').on('value', function(snapshot) { - var authenticated = snapshot.val(); - if (authenticated) { - self._firebase.root().child('.info/authenticated').off(); - - self._userId = userId.toString(); - self._userName = userName.toString(); - self._userRef = self._firebase.child('users').child(self._userId); - self._loadUserMetadata(function() { - root.setTimeout(function() { - callback(self._user); - self._setupDataEvents(); - }, 0); - }); - } else { - self.warn('Firechat requires an authenticated Firebase reference. Pass an authenticated reference before loading.'); - } - }); - }; - - // Resumes the previous session by automatically entering rooms. - Firechat.prototype.resumeSession = function() { - this._userRef.child('rooms').once('value', function(snapshot) { - var rooms = snapshot.val(); - for (var roomId in rooms) { - this.enterRoom(rooms[roomId].id); - } - }, /* onError */ function(){}, /* context */ this); - }; - - // Callback registration. Supports each of the following events: - Firechat.prototype.on = function(eventType, cb) { - this._addEventCallback(eventType, cb); - }; - - // Create and automatically enter a new chat room. - Firechat.prototype.createRoom = function(roomName, roomType, callback) { - var self = this, - newRoomRef = this._roomRef.push(); - - var newRoom = { - id: newRoomRef.name(), - name: roomName, - type: roomType || 'public', - createdByUserId: this._userId, - createdAt: Firebase.ServerValue.TIMESTAMP - }; - - if (roomType === 'private') { - newRoom.authorizedUsers = {}; - newRoom.authorizedUsers[this._userId] = true; - } - - newRoomRef.set(newRoom, function(error) { - if (!error) { - self.enterRoom(newRoomRef.name()); - } - if (callback) { - callback(newRoomRef.name()); - } - }); - }; - - // Enter a chat room. - Firechat.prototype.enterRoom = function(roomId) { - var self = this; - self.getRoom(roomId, function(room) { - var roomName = room.name; - - if (!roomId || !roomName) return; - - // Skip if we're already in this room. - if (self._rooms[roomId]) { - return; - } - - self._rooms[roomId] = true; - - if (self._user) { - // Save entering this room to resume the session again later. - self._userRef.child('rooms').child(roomId).set({ - id: roomId, - name: roomName, - active: true - }); - - // Set presence bit for the room and queue it for removal on disconnect. - var presenceRef = self._firebase.child('room-users').child(roomId).child(self._userId).child(self._sessionId); - self._queuePresenceOperation(presenceRef, { - id: self._userId, - name: self._userName - }, null); - } - - // Invoke our callbacks before we start listening for new messages. - self._onEnterRoom({ id: roomId, name: roomName }); - - // Setup message listeners - self._roomRef.child(roomId).once('value', function(snapshot) { - self._messageRef.child(roomId).limit(self._options.numMaxMessages).on('child_added', function(snapshot) { - self._onNewMessage(roomId, snapshot); - }, /* onCancel */ function() { - // Turns out we don't have permission to access these messages. - self.leaveRoom(roomId); - }, /* context */ self); - - self._messageRef.child(roomId).limit(self._options.numMaxMessages).on('child_removed', function(snapshot) { - self._onRemoveMessage(roomId, snapshot); - }, /* onCancel */ function(){}, /* context */ self); - }, /* onFailure */ function(){}, self); - }); - }; - - // Leave a chat room. - Firechat.prototype.leaveRoom = function(roomId) { - var self = this, - userRoomRef = self._firebase.child('room-users').child(roomId); - - // Remove listener for new messages to this room. - self._messageRef.child(roomId).off(); - - if (self._user) { - var presenceRef = userRoomRef.child(self._userId).child(self._sessionId); - - // Remove presence bit for the room and cancel on-disconnect removal. - self._removePresenceOperation(presenceRef.toString(), null); - - // Remove session bit for the room. - self._userRef.child('rooms').child(roomId).remove(); - } - - delete self._rooms[roomId]; - - // Invoke event callbacks for the room-exit event. - self._onLeaveRoom(roomId); - }; - - Firechat.prototype.sendMessage = function(roomId, messageContent, messageType, cb) { - var self = this, - message = { - userId: self._userId, - name: self._userName, - timestamp: Firebase.ServerValue.TIMESTAMP, - message: messageContent, - type: messageType || 'default' - }, - newMessageRef; - - if (!self._user) { - self._onAuthRequired(); - if (cb) { - cb(new Error('Not authenticated or user not set!')); - } - return; - } - - newMessageRef = self._messageRef.child(roomId).push(); - newMessageRef.setWithPriority(message, Firebase.ServerValue.TIMESTAMP, cb); - }; - - Firechat.prototype.deleteMessage = function(roomId, messageId, cb) { - var self = this; - - self._messageRef.child(roomId).child(messageId).remove(cb); - }; - - // Mute or unmute a given user by id. This list will be stored internally and - // all messages from the muted clients will be filtered client-side after - // receipt of each new message. - Firechat.prototype.toggleUserMute = function(userId, cb) { - var self = this; - - if (!self._user) { - self._onAuthRequired(); - if (cb) { - cb(new Error('Not authenticated or user not set!')); - } - return; - } - - self._userRef.child('muted').child(userId).transaction(function(isMuted) { - return (isMuted) ? null : true; - }, cb); - }; - - // Send a moderator notification to a specific user. - Firechat.prototype.sendSuperuserNotification = function(userId, notificationType, data, cb) { - var self = this, - userNotificationsRef = self._firebase.child('users').child(userId).child('notifications'); - - userNotificationsRef.push({ - fromUserId: self._userId, - timestamp: Firebase.ServerValue.TIMESTAMP, - notificationType: notificationType, - data: data || {} - }, cb); - }; - - // Warn a user for violating the terms of service or being abusive. - Firechat.prototype.warnUser = function(userId) { - var self = this; - - self.sendSuperuserNotification(userId, 'warning'); - }; - - // Suspend a user by putting the user into read-only mode for a period. - Firechat.prototype.suspendUser = function(userId, timeLengthSeconds, cb) { - var self = this, - suspendedUntil = new Date().getTime() + 1000*timeLengthSeconds; - - self._suspensionsRef.child(userId).set(suspendedUntil, function(error) { - if (error && cb) { - return cb(error); - } else { - self.sendSuperuserNotification(userId, 'suspension', { - suspendedUntil: suspendedUntil - }); - return cb(null); - } - }); - }; - - // Invite a user to a specific chat room. - Firechat.prototype.inviteUser = function(userId, roomId) { - var self = this, - sendInvite = function() { - var inviteRef = self._firebase.child('users').child(userId).child('invites').push(); - inviteRef.set({ - id: inviteRef.name(), - fromUserId: self._userId, - fromUserName: self._userName, - roomId: roomId - }); - - // Handle listen unauth / failure in case we're kicked. - inviteRef.on('value', self._onFirechatInviteResponse, function(){}, self); - }; - - if (!self._user) { - self._onAuthRequired(); - return; - } - - self.getRoom(roomId, function(room) { - if (room.type === 'private') { - var authorizedUserRef = self._roomRef.child(roomId).child('authorizedUsers'); - authorizedUserRef.child(userId).set(true, function(error) { - if (!error) { - sendInvite(); - } - }); - } else { - sendInvite(); - } - }); - }; - - Firechat.prototype.acceptInvite = function(inviteId, cb) { - var self = this; - - self._userRef.child('invites').child(inviteId).once('value', function(snapshot) { - var invite = snapshot.val(); - if (invite === null && cb) { - return cb(new Error('acceptInvite(' + inviteId + '): invalid invite id')); - } else { - self.enterRoom(invite.roomId); - self._userRef.child('invites').child(inviteId).update({ - 'status': 'accepted', - 'toUserName': self._userName - }, cb); - } - }, self); - }; - - Firechat.prototype.declineInvite = function(inviteId, cb) { - var self = this, - updates = { - 'status': 'declined', - 'toUserName': self._userName - }; - - self._userRef.child('invites').child(inviteId).update(updates, cb); - }; - - Firechat.prototype.getRoomList = function(cb) { - var self = this; - - self._roomRef.once('value', function(snapshot) { - cb(snapshot.val()); - }); - }; - - Firechat.prototype.getUsersByRoom = function() { - var self = this, - roomId = arguments[0], - query = self._firebase.child('room-users').child(roomId), - cb = arguments[arguments.length - 1], - limit = null; - - if (arguments.length > 2) { - limit = arguments[1]; - } - - query = (limit) ? query.limit(limit) : query; - - query.once('value', function(snapshot) { - var usernames = snapshot.val() || {}, - usernamesUnique = {}; - - for (var username in usernames) { - for (var session in usernames[username]) { - // Skip all other sessions for this user as we only need one. - usernamesUnique[username] = usernames[username][session]; - break; - } - } - - root.setTimeout(function() { - cb(usernamesUnique); - }, 0); - }); - }; - - Firechat.prototype.getUsersByPrefix = function(prefix, startAt, endAt, limit, cb) { - var self = this, - query = this._usersOnlineRef, - prefixLower = prefix.toLowerCase(); - - if (startAt) { - query = query.startAt(null, startAt); - } else if (endAt) { - query = query.endAt(null, endAt); - } else { - query = (prefixLower) ? query.startAt(null, prefixLower) : query.startAt(); - } - - query = (limit) ? query.limit(limit) : query; - - query.once('value', function(snapshot) { - var usernames = snapshot.val() || {}, - usernamesFiltered = {}; - - for (var userNameKey in usernames) { - var sessions = usernames[userNameKey], - userName, userId, usernameClean; - - // Grab the user data from the first registered active session. - for (var sessionId in sessions) { - userName = sessions[sessionId].name; - userId = sessions[sessionId].id; - - // Skip all other sessions for this user as we only need one. - break; - } - - // Filter out any usernames that don't match our prefix and break. - if ((prefix.length > 0) && (userName.toLowerCase().indexOf(prefixLower) !== 0)) - continue; - - usernamesFiltered[userName] = { - name: userName, - id: userId - }; - } - - root.setTimeout(function() { - cb(usernamesFiltered); - }, 0); - }); - }; - - // Miscellaneous helper methods. - Firechat.prototype.getRoom = function(roomId, callback) { - this._roomRef.child(roomId).once('value', function(snapshot) { - callback(snapshot.val()); - }); - }; - - Firechat.prototype.userIsModerator = function() { - return this._isModerator; - }; - - Firechat.prototype.warn = function(msg) { - if (console) { - msg = 'Firechat Warning: ' + msg; - if (typeof console.warn === 'function') { - console.warn(msg); - } else if (typeof console.log === 'function') { - console.log(msg); - } - } - }; -})(Firebase); -(function($) { - - - if (!$ || (parseInt($().jquery.replace(/\./g, ""), 10) < 170)) { - throw new Error("jQuery 1.7 or later required!"); - } - - var root = this, - previousFirechatUI = root.FirechatUI; - - root.FirechatUI = FirechatUI; - - if (!self.FirechatDefaultTemplates) { - throw new Error("Unable to find chat templates!"); - } - - function FirechatUI(firebaseRef, el, options) { - var self = this; - - if (!firebaseRef) { - throw new Error('FirechatUI: Missing required argument `firebaseRef`'); - } - - if (!el) { - throw new Error('FirechatUI: Missing required argument `el`'); - } - - options = options || {}; - this._options = options; - - this._el = el; - this._user = null; - this._chat = new Firechat(firebaseRef, options); - - // A list of rooms to enter once we've made room for them (once we've hit the max room limit). - this._roomQueue = []; - - // Define some constants regarding maximum lengths, client-enforced. - this.maxLengthUsername = 15; - this.maxLengthUsernameDisplay = 15; - this.maxLengthRoomName = 24; - this.maxLengthMessage = 120; - this.maxUserSearchResults = 100; - - // Define some useful regexes. - this.urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim; - this.pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim; - - this._renderLayout(); - - // Grab shortcuts to commonly used jQuery elements. - this.$wrapper = $('#firechat'); - this.$roomList = $('#firechat-room-list'); - this.$tabList = $('#firechat-tab-list'); - this.$tabContent = $('#firechat-tab-content'); - this.$messages = {}; - - // Rate limit messages from a given user with some defaults. - this.$rateLimit = { - limitCount: 10, // max number of events - limitInterval: 10000, // max interval for above count in milliseconds - limitWaitTime: 30000, // wait time if a user hits the wait limit - history: {} - }; - - // Setup UI bindings for chat controls. - this._bindUIEvents(); - - // Setup bindings to internal methods - this._bindDataEvents(); - } - - // Run FirechatUI in *noConflict* mode, returning the `FirechatUI` variable to - // its previous owner, and returning a reference to the FirechatUI object. - FirechatUI.noConflict = function noConflict() { - root.FirechatUI = previousFirechatUI; - return FirechatUI; - }; - - FirechatUI.prototype = { - - _bindUIEvents: function() { - // Chat-specific custom interactions and functionality. - this._bindForHeightChange(); - this._bindForTabControls(); - this._bindForRoomList(); - this._bindForUserRoomList(); - this._bindForUserSearch(); - this._bindForUserMuting(); - this._bindForChatInvites(); - this._bindForRoomListing(); - - // Generic, non-chat-specific interactive elements. - this._setupTabs(); - this._setupDropdowns(); - this._bindTextInputFieldLimits(); - }, - - _bindDataEvents: function() { - this._chat.on('user-update', this._onUpdateUser.bind(this)); - - // Bind events for new messages, enter / leaving rooms, and user metadata. - this._chat.on('room-enter', this._onEnterRoom.bind(this)); - this._chat.on('room-exit', this._onLeaveRoom.bind(this)); - this._chat.on('message-add', this._onNewMessage.bind(this)); - this._chat.on('message-remove', this._onRemoveMessage.bind(this)); - - // Bind events related to chat invitations. - this._chat.on('room-invite', this._onChatInvite.bind(this)); - this._chat.on('room-invite-response', this._onChatInviteResponse.bind(this)); - - // Binds events related to admin or moderator notifications. - this._chat.on('notification', this._onNotification.bind(this)); - }, - - _renderLayout: function() { - var template = FirechatDefaultTemplates["templates/layout-full.html"]; - $(this._el).html(template({ - maxLengthUsername: this.maxLengthUsername - })); - }, - - _onUpdateUser: function(user) { - // Update our current user state and render latest user name. - this._user = user; - - // Update our interface to reflect which users are muted or not. - var mutedUsers = this._user.muted || {}; - $('[data-event="firechat-user-mute-toggle"]').each(function(i, el) { - var userId = $(this).closest('[data-user-id]').data('user-id'); - $(this).toggleClass('red', !!mutedUsers[userId]); - }); - - // Ensure that all messages from muted users are removed. - for (var userId in mutedUsers) { - $('.message[data-user-id="' + userId + '"]').fadeOut(); - } - }, - - _onEnterRoom: function(room) { - this.attachTab(room.id, room.name); - }, - _onLeaveRoom: function(roomId) { - this.removeTab(roomId); - - // Auto-enter rooms in the queue - if ((this._roomQueue.length > 0)) { - this._chat.enterRoom(this._roomQueue.shift(roomId)); - } - }, - _onNewMessage: function(roomId, message) { - var userId = message.userId; - if (!this._user || !this._user.muted || !this._user.muted[userId]) { - this.showMessage(roomId, message); - } - }, - _onRemoveMessage: function(roomId, messageId) { - this.removeMessage(roomId, messageId); - }, - - // Events related to chat invitations. - _onChatInvite: function(invitation) { - var self = this; - var template = FirechatDefaultTemplates["templates/prompt-invitation.html"]; - var $prompt = this.prompt('Invite', template(invitation)); - $prompt.find('a.close').click(function() { - $prompt.remove(); - self._chat.declineInvite(invitation.id); - return false; - }); - - $prompt.find('[data-toggle=accept]').click(function() { - $prompt.remove(); - self._chat.acceptInvite(invitation.id); - return false; - }); - - $prompt.find('[data-toggle=decline]').click(function() { - $prompt.remove(); - self._chat.declineInvite(invitation.id); - return false; - }); - }, - _onChatInviteResponse: function(invitation) { - if (!invitation.status) return; - - var self = this, - template = FirechatDefaultTemplates["templates/prompt-invite-reply.html"], - $prompt; - - if (invitation.status && invitation.status === 'accepted') { - $prompt = this.prompt('Accepted', template(invitation)); - this._chat.getRoom(invitation.roomId, function(room) { - self.attachTab(invitation.roomId, room.name); - }); - } else { - $prompt = this.prompt('Declined', template(invitation)); - } - - $prompt.find('a.close').click(function() { - $prompt.remove(); - return false; - }); - }, - - // Events related to admin or moderator notifications. - _onNotification: function(notification) { - if (notification.notificationType === 'warning') { - this.renderAlertPrompt('Warning', 'You are being warned for inappropriate messaging. Further violation may result in temporary or permanent ban of service.'); - } else if (notification.notificationType === 'suspension') { - var suspendedUntil = notification.data.suspendedUntil, - secondsLeft = Math.round((suspendedUntil - new Date().getTime()) / 1000), - timeLeft = ''; - - if (secondsLeft > 0) { - if (secondsLeft > 2*3600) { - var hours = Math.floor(secondsLeft / 3600); - timeLeft = hours + ' hours, '; - secondsLeft -= 3600*hours; - } - timeLeft += Math.floor(secondsLeft / 60) + ' minutes'; - this.renderAlertPrompt('Suspended', 'A moderator has suspended you for violating site rules. You cannot send messages for another ' + timeLeft + '.'); - } - } - } - }; - - /** - * Initialize an authenticated session with a user id and name. - * This method assumes that the underlying Firebase reference has - * already been authenticated. - */ - FirechatUI.prototype.setUser = function(userId, userName) { - var self = this; - - // Initialize data events - self._chat.setUser(userId, userName, function(user) { - self._user = user; - - if (self._chat.userIsModerator()) { - self._bindSuperuserUIEvents(); - } - - self._chat.resumeSession(); - }); - }; - - /** - * Exposes internal chat bindings via this external interface. - */ - FirechatUI.prototype.on = function(eventType, cb) { - var self = this; - - this._chat.on(eventType, cb); - }; - - /** - * Binds a custom context menu to messages for superusers to warn or ban - * users for violating terms of service. - */ - FirechatUI.prototype._bindSuperuserUIEvents = function() { - var self = this, - parseMessageVars = function(event) { - var $this = $(this), - messageId = $this.closest('[data-message-id]').data('message-id'), - userId = $('[data-message-id="' + messageId + '"]').closest('[data-user-id]').data('user-id'), - roomId = $('[data-message-id="' + messageId + '"]').closest('[data-room-id]').data('room-id'); - - return { messageId: messageId, userId: userId, roomId: roomId }; - }, - clearMessageContextMenus = function() { - // Remove any context menus currently showing. - $('[data-toggle="firechat-contextmenu"]').each(function() { - $(this).remove(); - }); - - // Remove any messages currently highlighted. - $('#firechat .message.highlighted').each(function() { - $(this).removeClass('highlighted'); - }); - }, - showMessageContextMenu = function(event) { - var $this = $(this), - $message = $this.closest('[data-message-id]'), - template = FirechatDefaultTemplates["templates/message-context-menu.html"], - messageVars = parseMessageVars.call(this, event), - $template; - - event.preventDefault(); - - // Clear existing menus. - clearMessageContextMenus(); - - // Highlight the relevant message. - $this.addClass('highlighted'); - - self._chat.getRoom(messageVars.roomId, function(room) { - // Show the context menu. - $template = $(template({ - id: $message.data('message-id') - })); - $template.css({ - left: event.clientX, - top: event.clientY - }).appendTo(self.$wrapper); - }); - }; - - // Handle dismissal of message context menus (any non-right-click click event). - $(document).bind('click', { self: this }, function(event) { - if (!event.button || event.button != 2) { - clearMessageContextMenus(); - } - }); - - // Handle display of message context menus (via right-click on a message). - $(document).delegate('[data-class="firechat-message"]', 'contextmenu', showMessageContextMenu); - - // Handle click of the 'Warn User' contextmenu item. - $(document).delegate('[data-event="firechat-user-warn"]', 'click', function(event) { - var messageVars = parseMessageVars.call(this, event); - self._chat.warnUser(messageVars.userId); - }); - - // Handle click of the 'Suspend User (1 Hour)' contextmenu item. - $(document).delegate('[data-event="firechat-user-suspend-hour"]', 'click', function(event) { - var messageVars = parseMessageVars.call(this, event); - self._chat.suspendUser(messageVars.userId, /* 1 Hour = 3600s */ 60*60); - }); - - // Handle click of the 'Suspend User (1 Day)' contextmenu item. - $(document).delegate('[data-event="firechat-user-suspend-day"]', 'click', function(event) { - var messageVars = parseMessageVars.call(this, event); - self._chat.suspendUser(messageVars.userId, /* 1 Day = 86400s */ 24*60*60); - }); - - // Handle click of the 'Delete Message' contextmenu item. - $(document).delegate('[data-event="firechat-message-delete"]', 'click', function(event) { - var messageVars = parseMessageVars.call(this, event); - self._chat.deleteMessage(messageVars.roomId, messageVars.messageId); - }); - }; - - /** - * Binds to height changes in the surrounding div. - */ - FirechatUI.prototype._bindForHeightChange = function() { - var self = this, - $el = $(this._el), - lastHeight = null; - - setInterval(function() { - var height = $el.height(); - if (height != lastHeight) { - lastHeight = height; - $('.chat').each(function(i, el) { - - }); - } - }, 500); - }; - - /** - * Binds custom inner-tab events. - */ - FirechatUI.prototype._bindForTabControls = function() { - var self = this; - - // Handle click of tab close button. - $(document).delegate('[data-event="firechat-close-tab"]', 'click', function(event) { - var roomId = $(this).closest('[data-room-id]').data('room-id'); - self._chat.leaveRoom(roomId); - return false; - }); - }; - - /** - * Binds room list dropdown to populate room list on-demand. - */ - FirechatUI.prototype._bindForRoomList = function() { - var self = this; - - $('#firechat-btn-rooms').bind('click', function() { - if ($(this).parent().hasClass('open')) { - return; - } - - var $this = $(this), - template = FirechatDefaultTemplates["templates/room-list-item.html"], - selectRoomListItem = function() { - var parent = $(this).parent(), - roomId = parent.data('room-id'), - roomName = parent.data('room-name'); - - if (self.$messages[roomId]) { - self.focusTab(roomId); - } else { - self._chat.enterRoom(roomId, roomName); - } - return false; - }; - - self._chat.getRoomList(function(rooms) { - self.$roomList.empty(); - for (var roomId in rooms) { - var room = rooms[roomId]; - if (room.type != "public") continue; - room.isRoomOpen = !!self.$messages[room.id]; - var $roomItem = $(template(room)); - $roomItem.children('a').bind('click', selectRoomListItem); - self.$roomList.append($roomItem.toggle(true)); - } - }); - }); - }; - - /** - * Binds user list dropdown per room to populate user list on-demand. - */ - FirechatUI.prototype._bindForUserRoomList = function() { - var self = this; - - // Upon click of the dropdown, autofocus the input field and trigger list population. - $(document).delegate('[data-event="firechat-user-room-list-btn"]', 'click', function(event) { - event.stopPropagation(); - - var $this = $(this), - roomId = $this.closest('[data-room-id]').data('room-id'), - template = FirechatDefaultTemplates["templates/room-user-list-item.html"], - targetId = $this.data('target'), - $target = $('#' + targetId); - - $target.empty(); - self._chat.getUsersByRoom(roomId, function(users) { - for (var username in users) { - user = users[username]; - user.disableActions = (!self._user || user.id === self._user.id); - user.nameTrimmed = self.trimWithEllipsis(user.name, self.maxLengthUsernameDisplay); - user.isMuted = (self._user && self._user.muted && self._user.muted[user.id]); - $target.append($(template(user))); - } - self.sortListLexicographically('#' + targetId); - }); - }); - }; - - /** - * Binds user search buttons, dropdowns, and input fields for searching all - * active users currently in chat. - */ - FirechatUI.prototype._bindForUserSearch = function() { - var self = this, - handleUserSearchSubmit = function(event) { - var $this = $(this), - targetId = $this.data('target'), - controlsId = $this.data('controls'), - templateId = $this.data('template'), - prefix = $this.val() || $this.data('prefix') || '', - startAt = $this.data('startAt') || null, - endAt = $this.data('endAt') || null; - - event.preventDefault(); - - userSearch(targetId, templateId, controlsId, prefix, startAt, endAt); - }, - userSearch = function(targetId, templateId, controlsId, prefix, startAt, endAt) { - var $target = $('#' + targetId), - $controls = $('#' + controlsId), - template = FirechatDefaultTemplates[templateId]; - - // Query results, filtered by prefix, using the defined startAt and endAt markets. - self._chat.getUsersByPrefix(prefix, startAt, endAt, self.maxUserSearchResults, function(users) { - var numResults = 0, - $prevBtn, $nextBtn, username, firstResult, lastResult; - - $target.empty(); - - for (username in users) { - var user = users[username]; - - // Disable buttons for . - user.disableActions = (!self._user || user.id === self._user.id); - - numResults += 1; - - $target.append(template(user)); - - // If we've hit our result limit, the additional value signifies we should paginate. - if (numResults === 1) { - firstResult = user.name.toLowerCase(); - } else if (numResults >= self.maxUserSearchResults) { - lastResult = user.name.toLowerCase(); - break; - } - } - - if ($controls) { - $prevBtn = $controls.find('[data-toggle="firechat-pagination-prev"]'); - $nextBtn = $controls.find('[data-toggle="firechat-pagination-next"]'); - - // Sort out configuration for the 'next' button - if (lastResult) { - $nextBtn - .data('event', 'firechat-user-search') - .data('startAt', lastResult) - .data('prefix', prefix) - .removeClass('disabled').removeAttr('disabled'); - } else { - $nextBtn - .data('event', null) - .data('startAt', null) - .data('prefix', null) - .addClass('disabled').attr('disabled', 'disabled'); - } - } - }); - }; - - $(document).delegate('[data-event="firechat-user-search"]', 'keyup', handleUserSearchSubmit); - $(document).delegate('[data-event="firechat-user-search"]', 'click', handleUserSearchSubmit); - - // Upon click of the dropdown, autofocus the input field and trigger list population. - $(document).delegate('[data-event="firechat-user-search-btn"]', 'click', function(event) { - event.stopPropagation(); - var $input = $(this).next('div.firechat-dropdown-menu').find('input'); - $input.focus(); - $input.trigger(jQuery.Event('keyup')); - }); - - // Ensure that the dropdown stays open despite clicking on the input element. - $(document).delegate('[data-event="firechat-user-search"]', 'click', function(event) { - event.stopPropagation(); - }); - }; - - /** - * Binds user mute toggles and removes all messages for a given user upon mute. - */ - FirechatUI.prototype._bindForUserMuting = function() { - var self = this; - $(document).delegate('[data-event="firechat-user-mute-toggle"]', 'click', function(event) { - var $this = $(this), - userId = $this.closest('[data-user-id]').data('user-id'), - userName = $this.closest('[data-user-name]').data('user-name'), - isMuted = $this.hasClass('red'), - template = FirechatDefaultTemplates["templates/prompt-user-mute.html"]; - - event.preventDefault(); - - // Require user confirmation for muting. - if (!isMuted) { - var $prompt = self.prompt('Mute User?', template({ - userName: userName - })); - - $prompt.find('a.close').first().click(function() { - $prompt.remove(); - return false; - }); - - $prompt.find('[data-toggle=decline]').first().click(function() { - $prompt.remove(); - return false; - }); - - $prompt.find('[data-toggle=accept]').first().click(function() { - self._chat.toggleUserMute(userId); - $prompt.remove(); - return false; - }); - } else { - self._chat.toggleUserMute(userId); - } - }); - }; - - /** - * Binds to elements with the data-event='firechat-user-(private)-invite' and - * handles invitations as well as room creation and entering. - */ - FirechatUI.prototype._bindForChatInvites = function() { - var self = this, - renderInvitePrompt = function(event) { - var $this = $(this), - userId = $this.closest('[data-user-id]').data('user-id'), - roomId = $this.closest('[data-room-id]').data('room-id'), - userName = $this.closest('[data-user-name]').data('user-name'), - template = FirechatDefaultTemplates["templates/prompt-invite-private.html"], - $prompt; - - self._chat.getRoom(roomId, function(room) { - $prompt = self.prompt('Invite', template({ - userName: userName, - roomName: room.name - })); - - $prompt.find('a.close').click(function() { - $prompt.remove(); - return false; - }); - - $prompt.find('[data-toggle=decline]').click(function() { - $prompt.remove(); - return false; - }); - - $prompt.find('[data-toggle=accept]').first().click(function() { - $prompt.remove(); - self._chat.inviteUser(userId, roomId, room.name); - return false; - }); - return false; - }); - return false; - }, - renderPrivateInvitePrompt = function(event) { - var $this = $(this), - userId = $this.closest('[data-user-id]').data('user-id'), - userName = $this.closest('[data-user-name]').data('user-name'), - template = FirechatDefaultTemplates["templates/prompt-invite-private.html"], - $prompt; - - if (userId && userName) { - $prompt = self.prompt('Private Invite', template({ - userName: userName, - roomName: 'Private Chat' - })); - - $prompt.find('a.close').click(function() { - $prompt.remove(); - return false; - }); - - $prompt.find('[data-toggle=decline]').click(function() { - $prompt.remove(); - return false; - }); - - $prompt.find('[data-toggle=accept]').first().click(function() { - $prompt.remove(); - var roomName = 'Private Chat'; - self._chat.createRoom(roomName, 'private', function(roomId) { - self._chat.inviteUser(userId, roomId, roomName); - }); - return false; - }); - } - return false; - }; - - $(document).delegate('[data-event="firechat-user-chat"]', 'click', renderPrivateInvitePrompt); - $(document).delegate('[data-event="firechat-user-invite"]', 'click', renderInvitePrompt); - }; - - /** - * Binds to room dropdown button, menu items, and create room button. - */ - FirechatUI.prototype._bindForRoomListing = function() { - var self = this, - $createRoomPromptButton = $('#firechat-btn-create-room-prompt'), - $createRoomButton = $('#firechat-btn-create-room'), - renderRoomList = function(event) { - var type = $(this).data('room-type'); - - self.sortListLexicographically('#firechat-room-list'); - }; - - // Handle click of the create new room prompt-button. - $createRoomPromptButton.bind('click', function(event) { - self.promptCreateRoom(); - return false; - }); - - // Handle click of the create new room button. - $createRoomButton.bind('click', function(event) { - var roomName = $('#firechat-input-room-name').val(); - $('#firechat-prompt-create-room').remove(); - self._chat.createRoom(roomName); - return false; - }); - }; - - /** - * A stripped-down version of bootstrap-tab.js. - * - * Original bootstrap-tab.js Copyright 2012 Twitter, Inc.,licensed under the Apache v2.0 - */ - FirechatUI.prototype._setupTabs = function() { - var self = this, - show = function($el) { - var $this = $el, - $ul = $this.closest('ul:not(.firechat-dropdown-menu)'), - selector = $this.attr('data-target'), - previous = $ul.find('.active:last a')[0], - $target, - e; - - if (!selector) { - selector = $this.attr('href'); - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); - } - - if ($this.parent('li').hasClass('active')) return; - - e = $.Event('show', { relatedTarget: previous }); - - $this.trigger(e); - - if (e.isDefaultPrevented()) return; - - $target = $(selector); - - activate($this.parent('li'), $ul); - activate($target, $target.parent(), function () { - $this.trigger({ - type: 'shown', - relatedTarget: previous - }); - }); - }, - activate = function (element, container, callback) { - var $active = container.find('> .active'), - transition = callback && $.support.transition && $active.hasClass('fade'); - - function next() { - $active - .removeClass('active') - .find('> .firechat-dropdown-menu > .active') - .removeClass('active'); - - element.addClass('active'); - - if (transition) { - element.addClass('in'); - } else { - element.removeClass('fade'); - } - - if (element.parent('.firechat-dropdown-menu')) { - element.closest('li.firechat-dropdown').addClass('active'); - } - - if (callback) { - callback(); - } - } - - if (transition) { - $active.one($.support.transition.end, next); - } else { - next(); - } - - $active.removeClass('in'); - }; - - $(document).delegate('[data-toggle="firechat-tab"]', 'click', function(event) { - event.preventDefault(); - show($(this)); - }); - }; - - /** - * A stripped-down version of bootstrap-dropdown.js. - * - * Original bootstrap-dropdown.js Copyright 2012 Twitter, Inc., licensed under the Apache v2.0 - */ - FirechatUI.prototype._setupDropdowns = function() { - var self = this, - toggle = '[data-toggle=firechat-dropdown]', - toggleDropdown = function(event) { - var $this = $(this), - $parent = getParent($this), - isActive = $parent.hasClass('open'); - - if ($this.is('.disabled, :disabled')) return; - - clearMenus(); - - if (!isActive) { - $parent.toggleClass('open'); - } - - $this.focus(); - - return false; - }, - clearMenus = function() { - $('[data-toggle=firechat-dropdown]').each(function() { - getParent($(this)).removeClass('open'); - }); - }, - getParent = function($this) { - var selector = $this.attr('data-target'), - $parent; - - if (!selector) { - selector = $this.attr('href'); - selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); - } - - $parent = selector && $(selector); - - if (!$parent || !$parent.length) $parent = $this.parent(); - - return $parent; - }; - - $(document) - .bind('click', clearMenus) - .delegate('.firechat-dropdown-menu', 'click', function(event) { event.stopPropagation(); }) - .delegate('[data-toggle=firechat-dropdown]', 'click', toggleDropdown); - }; - - /** - * Binds to any text input fields with data-provide='limit' and - * data-counter='', and upon value change updates the selector - * content to reflect the number of characters remaining, as the 'maxlength' - * attribute less the current value length. - */ - FirechatUI.prototype._bindTextInputFieldLimits = function() { - $('body').delegate('input[data-provide="limit"], textarea[data-provide="limit"]', 'keyup', function(event) { - var $this = $(this), - $target = $($this.data('counter')), - limit = $this.attr('maxlength'), - count = $this.val().length; - - $target.html(Math.max(0, limit - count)); - }); - }; - - /** - * Given a title and message content, show an alert prompt to the user. - * - * @param {string} title - * @param {string} message - */ - FirechatUI.prototype.renderAlertPrompt = function(title, message) { - var template = FirechatDefaultTemplates["templates/prompt-alert.html"], - $prompt = this.prompt(title, template({ message: message })); - - $prompt.find('.close').click(function() { - $prompt.remove(); - return false; - }); - return; - }; - - /** - * Toggle input field s if we want limit / unlimit input fields. - */ - FirechatUI.prototype.toggleInputs = function(isEnabled) { - $('#firechat-tab-content textarea').each(function() { - var $this = $(this); - if (isEnabled) { - $(this).val(''); - } else { - $(this).val('You have exceeded the message limit, please wait before sending.'); - } - $this.prop('disabled', !isEnabled); - }); - $('#firechat-input-name').prop('disabled', !isEnabled); - }; - - /** - * Given a room id and name, attach the tab to the interface and setup events. - * - * @param {string} roomId - * @param {string} roomName - */ - FirechatUI.prototype.attachTab = function(roomId, roomName) { - var self = this; - - // If this tab already exists, give it focus. - if (this.$messages[roomId]) { - this.focusTab(roomId); - return; - } - - var room = { - id: roomId, - name: roomName - }; - - // Populate and render the tab content template. - var tabTemplate = FirechatDefaultTemplates["templates/tab-content.html"]; - var $tabContent = $(tabTemplate(room)); - this.$tabContent.prepend($tabContent); - var $messages = $('#firechat-messages' + roomId); - - // Keep a reference to the message listing for later use. - this.$messages[roomId] = $messages; - - // Attach on-enter event to textarea. - var $textarea = $tabContent.find('textarea').first(); - $textarea.bind('keydown', function(e) { - var message = self.trimWithEllipsis($textarea.val(), self.maxLengthMessage); - if ((e.which === 13) && (message !== '')) { - $textarea.val(''); - self._chat.sendMessage(roomId, message); - return false; - } - }); - - // Populate and render the tab menu template. - var tabListTemplate = FirechatDefaultTemplates["templates/tab-menu-item.html"]; - var $tab = $(tabListTemplate(room)); - this.$tabList.prepend($tab); - - // Attach on-shown event to move tab to front and scroll to bottom. - $tab.bind('shown', function(event) { - $messages.scrollTop($messages[0].scrollHeight); - }); - - // Dynamically update the width of each tab based upon the number open. - var tabs = this.$tabList.children('li'); - var tabWidth = Math.floor($('#firechat-tab-list').width() / tabs.length); - this.$tabList.children('li').css('width', tabWidth); - - // Update the room listing to reflect that we're now in the room. - this.$roomList.children('[data-room-id=' + roomId + ']').children('a').addClass('highlight'); - - // Sort each item in the user list alphabetically on click of the dropdown. - $('#firechat-btn-room-user-list-' + roomId).bind('click', function() { - self.sortListLexicographically('#firechat-room-user-list-' + roomId); - return false; - }); - - // Automatically select the new tab. - this.focusTab(roomId); - }; - - /** - * Given a room id, focus the given tab. - * - * @param {string} roomId - */ - FirechatUI.prototype.focusTab = function(roomId) { - if (this.$messages[roomId]) { - var $tabLink = this.$tabList.find('[data-room-id=' + roomId + ']').find('a'); - if ($tabLink.length) { - $tabLink.first().trigger('click'); - } - } - }; - - /** - * Given a room id, remove the tab and all child elements from the interface. - * - * @param {string} roomId - */ - FirechatUI.prototype.removeTab = function(roomId) { - delete this.$messages[roomId]; - - // Remove the inner tab content. - this.$tabContent.find('[data-room-id=' + roomId + ']').remove(); - - // Remove the tab from the navigation menu. - this.$tabList.find('[data-room-id=' + roomId + ']').remove(); - - // Dynamically update the width of each tab based upon the number open. - var tabs = this.$tabList.children('li'); - var tabWidth = Math.floor($('#firechat-tab-list').width() / tabs.length); - this.$tabList.children('li').css('width', tabWidth); - - // Automatically select the next tab if there is one. - this.$tabList.find('[data-toggle="firechat-tab"]').first().trigger('click'); - - // Update the room listing to reflect that we're now in the room. - this.$roomList.children('[data-room-id=' + roomId + ']').children('a').removeClass('highlight'); - }; - - /** - * Render a new message in the specified chat room. - * - * @param {string} roomId - * @param {string} message - */ - FirechatUI.prototype.showMessage = function(roomId, rawMessage) { - var self = this; - - // Setup defaults - var message = { - id : rawMessage.id, - localtime : self.formatTime(rawMessage.timestamp), - message : rawMessage.message || '', - userId : rawMessage.userId, - name : rawMessage.name, - type : rawMessage.type || 'default', - isSelfMessage : (self._user && rawMessage.userId == self._user.id), - disableActions : (!self._user || rawMessage.userId == self._user.id) - }; - - // While other data is escaped in the Underscore.js templates, escape and - // process the message content here to add additional functionality (add links). - // Also trim the message length to some client-defined maximum. - var messageConstructed = ''; - message.message = _.map(message.message.split(' '), function(token) { - if (self.urlPattern.test(token) || self.pseudoUrlPattern.test(token)) { - return self.linkify(encodeURI(token)); - } else { - return _.escape(token); - } - }).join(' '); - message.message = self.trimWithEllipsis(message.message, self.maxLengthMessage); - - // Populate and render the message template. - var template = FirechatDefaultTemplates["templates/message.html"]; - var $message = $(template(message)); - var $messages = self.$messages[roomId]; - if ($messages) { - - var scrollToBottom = false; - if ($messages.scrollTop() / ($messages[0].scrollHeight - $messages[0].offsetHeight) >= 0.95) { - // Pinned to bottom - scrollToBottom = true; - } else if ($messages[0].scrollHeight <= $messages.height()) { - // Haven't added the scrollbar yet - scrollToBottom = true; - } - - $messages.append($message); - - if (scrollToBottom) { - $messages.scrollTop($messages[0].scrollHeight); - } - } - }; - - /** - * Remove a message by id. - * - * @param {string} roomId - * @param {string} messageId - */ - FirechatUI.prototype.removeMessage = function(roomId, messageId) { - $('.message[data-message-id="' + messageId + '"]').remove(); - }; - - /** - * Given a selector for a list element, sort the items alphabetically. - * - * @param {string} selector - */ - FirechatUI.prototype.sortListLexicographically = function(selector) { - $(selector).children("li").sort(function(a, b) { - var upA = $(a).text().toUpperCase(); - var upB = $(b).text().toUpperCase(); - return (upA < upB) ? -1 : (upA > upB) ? 1 : 0; - }).appendTo(selector); - }; - - /** - * Remove leading and trailing whitespace from a string and shrink it, with - * added ellipsis, if it exceeds a specified length. - * - * @param {string} str - * @param {number} length - * @return {string} - */ - FirechatUI.prototype.trimWithEllipsis = function(str, length) { - str = str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); - return (length && str.length <= length) ? str : str.substring(0, length) + '...'; - }; - - /** - * Given a timestamp, format it in the form hh:mm am/pm. Defaults to now - * if the timestamp is undefined. - * - * @param {Number} timestamp - * @param {string} date - */ - FirechatUI.prototype.formatTime = function(timestamp) { - var date = (timestamp) ? new Date(timestamp) : new Date(), - hours = date.getHours() || 12, - minutes = '' + date.getMinutes(), - ampm = (date.getHours() >= 12) ? 'pm' : 'am'; - - hours = (hours > 12) ? hours - 12 : hours; - minutes = (minutes.length < 2) ? '0' + minutes : minutes; - return '' + hours + ':' + minutes + ampm; - }; - - /** - * Launch a prompt to allow the user to create a new room. - */ - FirechatUI.prototype.promptCreateRoom = function() { - var self = this; - var template = FirechatDefaultTemplates["templates/prompt-create-room.html"]; - - var $prompt = this.prompt('Create Public Room', template({ - maxLengthRoomName: this.maxLengthRoomName, - isModerator: self._chat.userIsModerator() - })); - $prompt.find('a.close').first().click(function() { - $prompt.remove(); - return false; - }); - - - $prompt.find('[data-toggle=submit]').first().click(function() { - var name = $prompt.find('[data-input=firechat-room-name]').first().val(); - if (name !== '') { - self._chat.createRoom(name, 'public'); - $prompt.remove(); - } - return false; - }); - - $prompt.find('[data-input=firechat-room-name]').first().focus(); - $prompt.find('[data-input=firechat-room-name]').first().bind('keydown', function(e) { - if (e.which === 13) { - var name = $prompt.find('[data-input=firechat-room-name]').first().val(); - if (name !== '') { - self._chat.createRoom(name, 'public'); - $prompt.remove(); - return false; - } - } - }); - }; - - /** - * Inner method to launch a prompt given a specific title and HTML content. - * @param {string} title - * @param {string} content - */ - FirechatUI.prototype.prompt = function(title, content) { - var template = FirechatDefaultTemplates["templates/prompt.html"], - $prompt; - - $prompt = $(template({ - title: title, - content: content - })).css({ - top: this.$wrapper.position().top + (0.333 * this.$wrapper.height()), - left: this.$wrapper.position().left + (0.125 * this.$wrapper.width()), - width: 0.75 * this.$wrapper.width() - }); - this.$wrapper.append($prompt.removeClass('hidden')); - return $prompt; - }; - - // see http://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links - FirechatUI.prototype.linkify = function(str) { - var self = this; - return str - .replace(self.urlPattern, '$&') - .replace(self.pseudoUrlPattern, '$1$2'); - }; - -})(jQuery); \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index c12a97e385..a5253f4091 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,38 +1,269 @@ $(document).ready(function() { - function init() { - var firepadRef = getExampleRef(); - var ref = new Firebase('scorching-heat-2873.firebaseIO.com'); - var codeMirror = CodeMirror(document.getElementById('firepad-container'), { - lineNumbers: true, - mode: 'javascript' - }); - var firepad = Firepad.fromCodeMirror(firepadRef, codeMirror, { - defaultText: '// JavaScript Editing with Firepad!\\nfunction go() {\n var message = "Hello, world.";\n console.log(message);\n}' - }); - } - function getExampleRef() { - var ref = new Firebase('https://firepad.firebaseio-demo.com'); - var hash = window.location.hash.replace(/#/g, ''); - if (hash) { - ref = ref.child(hash); + var FADE_TIME = 150; // ms + var TYPING_TIMER_LENGTH = 400; // ms + var COLORS = [ + '#e21400', '#91580f', '#f8a700', '#f78b00', + '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', + '#3b88eb', '#3824aa', '#a700ff', '#d300e7' + ]; + + // Initialize varibles + var $window = $(window); + var $usernameInput = $('.usernameInput'); // Input for username + var $messages = $('.messages'); // Messages area + var $inputMessage = $('.inputMessage'); // Input message input box + + var $loginPage = $('.login.page'); // The login page + var $chatPage = $('.chat.page'); // The chatroom page + + // Prompt for setting a username + var username; + var connected = false; + var typing = false; + var lastTypingTime; + var $currentInput = $usernameInput.focus(); + + var socket = io("http://localhost:2999"); + + + function addParticipantsMessage (data) { + var message = ''; + if (data.numUsers === 1) { + message += "there's 1 participant"; } else { - ref = ref.push(); // generate unique location. - window.location = window.location + '#' + ref.name(); // add it as a hash to the URL. + message += "there are " + data.numUsers + " participants"; } - if (typeof console !== 'undefined') - console.log('Firebase data: ', ref.toString()); - return ref; + log(message); } - init(); - var chatRef = new Firebase('https://scorching-heat-2873.firebaseio.com/chat'); - var auth = new FirebaseSimpleLogin(chatRef, function(err, user) { - console.log("HEY!") - if (user) { - var chat = new FirechatUI(chatRef, document.getElementById('firechat-wrapper')); - chat.setUser(user.uid, user.displayName); + + // Sets the client's username + function setUsername () { + username = cleanInput($usernameInput.val().trim()); + + // If the username is valid + if (username) { + $loginPage.fadeOut(); + $chatPage.show(); + $loginPage.off('click'); + $currentInput = $inputMessage.focus(); + + // Tell the server your username + socket.emit('add user', username); + } + } + + // Sends a chat message + function sendMessage () { + var message = $inputMessage.val(); + // Prevent markup from being injected into the message + message = cleanInput(message); + // if there is a non-empty message and a socket connection + if (message && connected) { + $inputMessage.val(''); + addChatMessage({ + username: username, + message: message + }); + // tell server to execute 'new message' and send along one parameter + socket.emit('new message', message); + } + } + + // Log a message + function log (message, options) { + var $el = $('
            • ').addClass('log').text(message); + addMessageElement($el, options); + } + + // Adds the visual chat message to the message list + function addChatMessage (data, options) { + // Don't fade the message in if there is an 'X was typing' + var $typingMessages = getTypingMessages(data); + options = options || {}; + if ($typingMessages.length !== 0) { + options.fade = false; + $typingMessages.remove(); + } + + var $usernameDiv = $('') + .text(data.username) + .css('color', getUsernameColor(data.username)); + var $messageBodyDiv = $('') + .text(data.message); + + var typingClass = data.typing ? 'typing' : ''; + var $messageDiv = $('
            • ') + .data('username', data.username) + .addClass(typingClass) + .append($usernameDiv, $messageBodyDiv); + + addMessageElement($messageDiv, options); + } + + // Adds the visual chat typing message + function addChatTyping (data) { + data.typing = true; + data.message = 'is typing'; + addChatMessage(data); + } + + // Removes the visual chat typing message + function removeChatTyping (data) { + getTypingMessages(data).fadeOut(function () { + $(this).remove(); + }); + } + + // Adds a message element to the messages and scrolls to the bottom + // el - The element to add as a message + // options.fade - If the element should fade-in (default = true) + // options.prepend - If the element should prepend + // all other messages (default = false) + function addMessageElement (el, options) { + var $el = $(el); + + // Setup default options + if (!options) { + options = {}; + } + if (typeof options.fade === 'undefined') { + options.fade = true; + } + if (typeof options.prepend === 'undefined') { + options.prepend = false; + } + + // Apply options + if (options.fade) { + $el.hide().fadeIn(FADE_TIME); + } + if (options.prepend) { + $messages.prepend($el); + } else { + $messages.append($el); + } + $messages[0].scrollTop = $messages[0].scrollHeight; + } + + // Prevents input from having injected markup + function cleanInput (input) { + return $('
              ').text(input).text(); + } + + // Updates the typing event + function updateTyping () { + if (connected) { + if (!typing) { + typing = true; + socket.emit('typing'); + } + lastTypingTime = (new Date()).getTime(); + + setTimeout(function () { + var typingTimer = (new Date()).getTime(); + var timeDiff = typingTimer - lastTypingTime; + if (timeDiff >= TYPING_TIMER_LENGTH && typing) { + socket.emit('stop typing'); + typing = false; + } + }, TYPING_TIMER_LENGTH); + } + } + + // Gets the 'X is typing' messages of a user + function getTypingMessages (data) { + return $('.typing.message').filter(function (i) { + return $(this).data('username') === data.username; + }); + } + + // Gets the color of a username through our hash function + function getUsernameColor (username) { + // Compute hash code + var hash = 7; + for (var i = 0; i < username.length; i++) { + hash = username.charCodeAt(i) + (hash << 5) - hash; + } + // Calculate color + var index = Math.abs(hash % COLORS.length); + return COLORS[index]; + } + + // Keyboard events + + $window.keydown(function (event) { + // Auto-focus the current input when a key is typed + if (!(event.ctrlKey || event.metaKey || event.altKey)) { + $currentInput.focus(); + } + // When the client hits ENTER on their keyboard + if (event.which === 13) { + if (username) { + sendMessage(); + socket.emit('stop typing'); + typing = false; + } else { + setUsername(); + } } }); + $inputMessage.on('input', function() { + updateTyping(); + }); + + // Click events + + // Focus input when clicking anywhere on login page + $loginPage.click(function () { + $currentInput.focus(); + }); + + // Focus input when clicking on the message input's border + $inputMessage.click(function () { + $inputMessage.focus(); + }); + + // Socket events + + // Whenever the server emits 'login', log the login message + socket.on('login', function (data) { + connected = true; + // Display the welcome message + var message = "Welcome to Socket.IO Chat – "; + log(message, { + prepend: true + }); + addParticipantsMessage(data); + }); + + // Whenever the server emits 'new message', update the chat body + socket.on('new message', function (data) { + addChatMessage(data); + }); + + // Whenever the server emits 'user joined', log it in the chat body + socket.on('user joined', function (data) { + log(data.username + ' joined'); + addParticipantsMessage(data); + }); + + // Whenever the server emits 'user left', log it in the chat body + socket.on('user left', function (data) { + log(data.username + ' left'); + addParticipantsMessage(data); + removeChatTyping(data); + }); + + // Whenever the server emits 'typing', show the typing message + socket.on('typing', function (data) { + addChatTyping(data); + }); + + // Whenever the server emits 'stop typing', kill the typing message + socket.on('stop typing', function (data) { + removeChatTyping(data); + }); }); diff --git a/views/curriculum/curriculum.jade b/views/curriculum/curriculum.jade index 8b9f563b49..e57456719e 100644 --- a/views/curriculum/curriculum.jade +++ b/views/curriculum/curriculum.jade @@ -2,4 +2,14 @@ extends ../layout block content .page-header - h3 Your Curriculum \ No newline at end of file + h3 Your Curriculum + .col-xs-12.col-sm-12.col-md-12 + ul.pages + li.chat.page + div.chatArea + ul.messages + input.inputMessage(placeholder='Type here...') + li.login.page + div.form + h3.title What's your nickname? + input.usernameInput(type='text', maxlength='14') diff --git a/views/layout.jade b/views/layout.jade index 1fba48c98e..8a190aa408 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -7,6 +7,7 @@ html meta(name='description', content='') meta(name='csrf-token', content=_csrf) meta(name='author', content='') + script(src='https://cdn.socket.io/socket.io-1.0.0.js') title #{title} | Free Code Camp != css('styles') body