/chat/public/app.js
JavaScript | 586 lines | 571 code | 10 blank | 5 comment | 3 complexity | e9811b402d06f5b3571eabd53b89b9c3 MD5 | raw file
- $(function() {
- // Objeto global
- window.irc = window.irc || {};
- // socket.io
- var socket = io.connect();
- // modelos
-
- var Message = Backbone.Model.extend({
- defaults: {
-
- 'type': 'message'
- },
- initialize: function() {
- if (this.get('raw')) {
- this.set({text: this.parse( irc.util.escapeHTML(this.get('raw')) )});
- }
- },
- parse: function(text) {
- return this._linkify(text);
- },
- // Definir texto de saída para mensagens de status
- setText: function() {
- var text = '';
- switch (this.get('type')) {
- case 'join':
- text = this.get('nick') + ' entrou no canal';
- break;
- case 'part':
- text = this.get('nick') + ' saiu do canal';
- break;
- case 'nick':
- text = this.get('oldNick') + ' agora é ' + this.get('newNick');
- break;
- }
- this.set({text: text});
- },
- // Encontrar e linkar URLs
- _linkify: function(text) {
-
- var re = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi;
- var parsed = text.replace(re, function(url) {
- // transformar em link
- var href = url;
- if (url.indexOf('http') !== 0) {
- href = 'http://' + url;
- }
- return '<a href="' + href + '" target="_blank">' + url + '</a>';
- });
- return parsed;
- }
- });
- var Stream = Backbone.Collection.extend({
- model: Message
- });
- var Person = Backbone.Model.extend({
- defaults: {
- opStatus: ''
- }
- });
- var Participants = Backbone.Collection.extend({
- model: Person,
- getByNick: function(nick) {
- return this.detect(function(person) {
- return person.get('nick') == nick;
- });
- }
- });
- var Frame = Backbone.Model.extend({
-
- defaults: {
- 'type': 'channel',
- 'active': true
- },
- initialize: function() {
- this.stream = new Stream;
- this.participants = new Participants;
- },
- part: function() {
- console.log('Leaving ' + this.get('name'));
- this.destroy();
- }
- });
- var FrameList = Backbone.Collection.extend({
- model: Frame,
- getByName: function(name) {
- return this.detect(function(frame) {
- return frame.get('name') == name;
- });
- },
- getActive: function() {
- return this.detect(function(frame) {
- return frame.get('active') == true;
- });
- },
- setActive: function(frame) {
- this.each(function(frm) {
- frm.set({active: false});
- });
- frame.set({active: true});
- },
- getChannels: function() {
- return this.filter(function(frame) {
- return frame.get('type') == 'channel';
- });
- }
-
- });
-
- window.frames = new FrameList;
- // VIEWS
- // =====
- var MessageView = Backbone.View.extend({
- tmpl: $('#message-tmpl').html(),
- initialize: function() {
- this.render();
- },
- render: function() {
- var context = {
- sender: this.model.get('sender'),
- text: this.model.get('text')
- };
- var html = Mustache.to_html(this.tmpl, context);
- $(this.el).addClass(this.model.get('type'))
- .html(html);
- return this;
- }
- });
- // Nick na sidebar
- var NickListView = Backbone.View.extend({
- el: $('.nicks'),
- initialize: function() {
- _.bindAll(this);
- },
-
- tmpl: function(opStatus, nick) {
- return '<div>' + opStatus + ' ' + nick + '</div>'
- },
- switchChannel: function(ch) {
- ch.participants.bind('add', this.addOne, this);
- ch.participants.bind('change', this.changeNick, this);
- },
- addOne: function(p) {
- var text = this.tmpl(p.get('opStatus'), p.get('nick'));
- $(this.el).append(text);
- },
- addAll: function(participants) {
- var nicks = [];
- participants.each(function(p) {
- var text = this.tmpl(p.get('opStatus'), p.get('nick'));
- nicks.push(text);
- }, this);
- $(this.el).html(nicks.join('\n'));
- },
- changeNick: function() {
- console.log('Change of nick seen');
- console.log(arguments);
- }
-
- });
- var nickList = new NickListView;
- var FrameView = Backbone.View.extend({
- el: $('#frame'),
- // Para controlar a posição de rolagem
- position: {},
- initialize: function() {
- _.bindAll(this);
- },
- addMessage: function(message, single) {
- // Faça isso apenas em adições de mensagem única
- if (single) {
- var position = $('#messages').scrollTop();
- var atBottom = $('#messages')[0].scrollHeight - position
- == $('#messages').innerHeight();
- }
- var view = new MessageView({model: message});
- $('#messages').append(view.el);
- // Desloque-se para baixo na nova mensagem se já estiver no fundo
- if (atBottom) {
- $('#messages').scrollTop(position + 100);
- }
- },
- updateTopic: function(channel) {
- this.$('#topic').text(channel.get('topic')).show();
- $('#messages').css('top', $('#topic').outerHeight(true));
- },
- // Mudar o foco para um quadro diferente
- focus: function(frame) {
- // Salvar a posição de rolagem para o frame
- if (this.focused) {
- this.position[this.focused.get('name')] = this.$('#output').scrollTop();
- }
- this.focused = frame;
- frames.setActive(this.focused);
- $('#messages').empty();
- frame.stream.each(function(message) {
- this.addMessage(message, false);
- }, this);
- nickList.addAll(frame.participants);
- if (frame.get('type') == 'channel') {
- this.$('#sidebar').show();
- frame.get('topic') && this.updateTopic(frame);
- $('.wrapper').css('margin-right', 205);
- $('#messages').css('top', $('#topic').outerHeight(true));
- } else {
- this.$('#sidebar').hide();
- this.$('#topic').hide();
- $('.wrapper').css('margin-right', 0);
- $('#messages').css('top', 0);
- }
- $(this.el).removeClass().addClass(frame.get('type'));
- this.$('#output #messsages').scrollTop(this.position[frame.get('name')] || 0);
- // Somente o frame selecionado deve enviar mensagens
- frames.each(function(frm) {
- frm.stream.unbind('add');
- frm.participants.unbind();
- frm.unbind();
- });
- frame.bind('change:topic', this.updateTopic, this);
- frame.stream.bind('add', this.addMessage, this);
- nickList.switchChannel(frame);
- },
- updateNicks: function(model, nicks) {
- console.log('Nicks rendered');
- }
- });
- var FrameTabView = Backbone.View.extend({
- tagName: 'li',
- tmpl: $('#tab-tmpl').html(),
- initialize: function() {
- this.model.bind('destroy', this.close, this);
- this.render();
- },
- events: {
- 'click': 'setActive',
- 'click .close-frame': 'close'
- },
- // Comando PART
- part: function() {
- if (this.model.get('type') === 'channel') {
- socket.emit('part', this.model.get('name'));
- } else {
-
- this.model.destroy();
- }
- },
- // Fechar o frame
- close: function() {
- // Concentre-se no próximo quadro se este tiver o foco
- if ($(this.el).hasClass('active')) {
- // Ir para o quadro anterior, a menos que seja o status
- if ($(this.el).prev().text().trim() !== 'status') {
- $(this.el).prev().click();
- } else {
- $(this.el).next().click();
- }
- }
- $(this.el).remove();
- },
- // Definir como guia ativa; Foco janela no frame
- setActive: function() {
- console.log('View setting active status');
- $(this.el).addClass('active')
- .siblings().removeClass('active');
- irc.frameWindow.focus(this.model);
- },
- render: function() {
- console.log(this.model);
- var self = this;
- var context = {
- text: this.model.get('name'),
- type: this.model.get('type'),
- isStatus: function() {
- return self.model.get('type') == 'status';
- }
- };
- var html = Mustache.to_html(this.tmpl, context);
- $(this.el).html(html);
- return this;
- }
- });
- var AppView = Backbone.View.extend({
- el: $('#content'),
- testFrames: $('#sidebar .frames'),
- frameList: $('header .frames'),
- initialize: function() {
- frames.bind('add', this.addTab, this);
- this.input = this.$('#prime-input');
- this.render();
- },
- events: {
- 'keypress #prime-input': 'sendInput',
- },
- addTab: function(frame) {
- var tab = new FrameTabView({model: frame});
- this.frameList.append(tab.el);
- tab.setActive();
- },
- joinChannel: function(name) {
- socket.emit('join', name);
- },
- // Mapeia os comandos IRC comuns para o padrão (RFC 1459)
- parse: function(text) {
- var command = text.split(' ')[0];
- console.log(command);
- var revised = '';
- switch (command) {
- case 'msg':
- revised = 'privmsg';
- break;
- default:
- revised = command;
- break;
- }
- return irc.util.swapCommand(command, revised, text);
- },
- sendInput: function(e) {
- if (e.keyCode != 13) return;
- var frame = irc.frameWindow.focused,
- input = this.input.val();
- if (input.indexOf('/') === 0) {
- var parsed = this.parse(input.substr(1));
- socket.emit('command', parsed);
- var msgParts = parsed.split(' ');
- if (msgParts[0].toLowerCase() === 'privmsg') {
- pm = frames.getByName(msgParts[1]) || new Frame({type: 'pm', name: msgParts[1]});
- pm.stream.add({sender: irc.me.get('nick'), raw: msgParts[2]})
- frames.add(pm);
- }
- } else {
- socket.emit('say', {
- target: frame.get('name'),
- message: input
- });
- frame.stream.add({sender: irc.me.get('nick'), raw: input});
- }
- this.input.val('');
- },
- render: function() {
- // Alocação dinâmica de altura
- this.el.show();
- $(window).resize(function() {
- sizeContent($('#frame #output'));
- sizeContent($('#frame #sidebar'));
- sizeContent($('#sidebar .nicks', '.stats'));
- });
- }
- });
- var ConnectView = Backbone.View.extend({
- el: $('#connect'),
- events: {
- 'click .btn': 'connect',
- 'keypress': 'connectOnEnter'
- },
- initialize: function() {
- _.bindAll(this);
- this.render();
- },
-
- render: function() {
- this.el.modal({backdrop: true, show: true});
- $('#connect-nick').focus();
- },
- connectOnEnter: function(e) {
- if (e.keyCode != 13) return;
- this.connect();
- },
- connect: function(e) {
- e && e.preventDefault();
-
- var channelInput = $('#connect-channels').val(),
- channels = channelInput ? channelInput.split(' ') : [];
- var connectInfo = {
- nick: $('#connect-nick').val(),
- server: $('#connect-server').val(),
- channels: channels
- };
- socket.emit('connect', connectInfo);
- $('#connect').modal('hide');
- irc.me = new Person({nick: connectInfo.nick});
- irc.frameWindow = new FrameView;
- irc.app = new AppView;
- // Cria status "frame"
- frames.add({name: 'status', type: 'status'});
- sizeContent($('#frame #output'));
- sizeContent($('#frame #sidebar'));
- sizeContent($('#sidebar .nicks', '.stats'));
- }
-
- });
- var connect = new ConnectView;
- // UTILS
- // =====
- function humanizeError(message) {
- var text = '';
- switch (message.command) {
- case 'err_unknowncommand':
- text = 'Isto não é um comando IRC conhecido.';
- break;
- }
- return text;
- }
- // Definir janela de saída para altura total, menos outros elementos
- function sizeContent(sel, additional) {
- var newHeight = $('html').height() - $('header').outerHeight(true)
- - $('#prime-input').outerHeight(true)
- - (sel.outerHeight(true) - sel.height()) - 10;
- // 10 = #content padding
- if (additional) {
- newHeight -= $(additional).outerHeight(true);
- }
- sel.height(newHeight);
- }
- // SOCKET EVENTS
- // =============
- socket.on('message', function(msg) {
-
- if (msg.to.indexOf('#') !== 0 &&
- msg.to.indexOf('&') !== 0 &&
- msg.to !== 'status') return;
- frame = frames.getByName(msg.to);
- if (frame) {
- frame.stream.add({sender: msg.from, raw: msg.text});
- }
- });
- socket.on('pm', function(msg) {
- pm = frames.getByName(msg.nick) || new Frame({type: 'pm', name: msg.nick});
- pm.stream.add({sender: msg.nick, raw: msg.text})
- frames.add(pm);
- })
- // Mensagem do dia
- socket.on('motd', function(data) {
- data.motd.split('\n').forEach(function(line) {
- frames.getByName('status').stream.add({sender: '', raw: line});
- });
- });
- // Entrando em um canal
- socket.on('join', function(data) {
- console.log('Join event received for ' + data.channel + ' - ' + data.nick);
- if (data.nick == irc.me.get('nick')) {
- frames.add({name: data.channel});
- } else {
- channel = frames.getByName(data.channel);
- channel.participants.add({nick: data.nick});
- var joinMessage = new Message({type: 'join', nick: data.nick});
- joinMessage.setText();
- channel.stream.add(joinMessage);
- }
- });
- // Evento Part channel
- socket.on('part', function(data) {
- console.log('Part event received for ' + data.channel + ' - ' + data.nick);
- if (data.nick == irc.me.get('nick')) {
- frames.getByName(data.channel).part();
- } else {
- channel = frames.getByName(data.channel);
- channel.participants.getByNick(data.nick).destroy();
- var partMessage = new Message({type: 'part', nick: data.nick});
- partMessage.setText();
- channel.stream.add(partMessage);
- }
- });
- // Definir evento topic
- socket.on('topic', function(data) {
- var channel = frames.getByName(data.channel);
- channel.set({topic: data.topic});
-
- });
- // Evento troca de nick
- socket.on('nick', function(data) {
-
- console.log('Nick change', data);
- if (data.oldNick == irc.me.get('nick')) {
- irc.me.set({nick: data.newNick});
- }
- // Definir novo nome em todos os canais
- data.channels.forEach(function(ch) {
- var channel = frames.getByName(ch);
- // Alterar nick na lista de usuários
- channel.participants.getByNick(data.oldNick).set({nick: data.newNick});
- // Enviar mensagem de alteração de nick para o fluxo de canal
- var nickMessage = new Message({
- type: 'nick',
- oldNick: data.oldNick,
- newNick: data.newNick
- });
- nickMessage.setText();
- channel.stream.add(nickMessage);
- });
- });
- socket.on('names', function(data) {
- var frame = frames.getByName(data.channel);
- console.log(data);
- for (var nick in data.nicks) {
- frame.participants.add({nick: nick, opStatus: data.nicks[nick]});
- }
- });
- socket.on('error', function(data) {
- console.log(data.message);
- frame = frames.getActive();
- error = humanizeError(data.message);
- frame.stream.add({type: 'error', raw: error});
- });
- });