From c034ac826741ae278cbd286270344784c7e9b114 Mon Sep 17 00:00:00 2001 From: lilia Date: Sun, 10 Aug 2014 23:34:29 -0700 Subject: [PATCH] Refactor components for the main content section Each conversation views now manages its own separate elements rather than all binding to a shared #conversation element, and similarly for message composition ui. Also includes the beginnings of group creation UI (not working yet), featuring bootstrap-tagsinput field for entering group recipients --- index.html | 64 +++++++++++-- js/models/messages.js | 21 +---- js/models/threads.js | 22 ++++- js/popup.js | 1 + js/views/conversation_list_item_view.js | 7 +- js/views/conversation_view.js | 13 ++- js/views/message_list_view.js | 6 +- js/views/new_message_button.js | 120 ++++++++++++++++++++++++ stylesheets/bootstrap-tagsinput.css | 6 ++ 9 files changed, 216 insertions(+), 44 deletions(-) create mode 100644 js/views/new_message_button.js diff --git a/index.html b/index.html index 5371dbd6..184bab48 100644 --- a/index.html +++ b/index.html @@ -12,10 +12,14 @@ + + -
+
@@ -32,20 +36,19 @@
-
-
    -
+ + + + @@ -110,6 +153,7 @@ + diff --git a/js/models/messages.js b/js/models/messages.js index 74c26151..9c5e7d02 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -48,26 +48,9 @@ var Whisper = Whisper || {}; if (decrypted.message.timestamp > thread.get('timestamp')) { thread.set('timestamp', decrypted.message.timestamp); } - thread.set('unreadCount', thread.get('unreadCount') + 1); - thread.set('active', true); - thread.save(); - return m; - }, - - addOutgoingMessage: function(message, thread) { - var m = thread.messages().add({ - threadId: thread.id, - body: message, - type: 'outgoing', - timestamp: new Date().getTime() - }); - m.save(); - - thread.set('timestamp', new Date().getTime()); - thread.set('unreadCount', 0); - thread.set('active', true); - thread.save(); + thread.save({unreadCount: thread.get('unreadCount') + 1, active: true}); return m; } + }))(); })() diff --git a/js/models/threads.js b/js/models/threads.js index 16f2eacb..819c4f83 100644 --- a/js/models/threads.js +++ b/js/models/threads.js @@ -21,7 +21,15 @@ var Whisper = Whisper || {}; }, sendMessage: function(message) { - var m = Whisper.Messages.addOutgoingMessage(message, this); + this.messages().add({ type: 'outgoing', + body: message, + threadId: this.id, + timestamp: new Date().getTime() }).save(); + + this.save({ timestamp: new Date().getTime(), + unreadCount: 0, + active: true}); + if (this.get('type') == 'private') { var promise = textsecure.messaging.sendMessageToNumber(this.get('id'), message, []); } @@ -57,6 +65,18 @@ var Whisper = Whisper || {}; return thread; }, + createGroup: function(recipients, name) { + var group = textsecure.storage.groups.createNewGroup(numbers); + var attributes = {}; + attributes = { + id : group.id, + name : name, + numbers : group.numbers, + type : 'group', + }; + return this.findOrCreate(attributes); + }, + findOrCreateForRecipient: function(recipient) { var attributes = {}; attributes = { diff --git a/js/popup.js b/js/popup.js index f19d10a8..ec4b3445 100644 --- a/js/popup.js +++ b/js/popup.js @@ -17,6 +17,7 @@ new Whisper.ConversationListView({el: $('#contacts')}); +new Whisper.Header({el: $('#header')}); Whisper.Threads.fetch({reset: true}); textsecure.registerOnLoadFunction(function() { diff --git a/js/views/conversation_list_item_view.js b/js/views/conversation_list_item_view.js index 54f0fa49..663a9fab 100644 --- a/js/views/conversation_list_item_view.js +++ b/js/views/conversation_list_item_view.js @@ -21,12 +21,9 @@ var Whisper = Whisper || {}; }, open: function(e) { - $('#conversation').trigger('close'); // detach any existing conversation views + $('.conversation').trigger('close'); // detach any existing conversation views if (!this.view) { - this.view = new Whisper.ConversationView({ - el: $('#conversation'), - model: this.model - }); + this.view = new Whisper.ConversationView({ model: this.model }); } else { this.view.delegateEvents(); } diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 733899ab..d4ac408c 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -4,19 +4,24 @@ var Whisper = Whisper || {}; 'use strict'; Whisper.ConversationView = Backbone.View.extend({ + className: 'conversation', initialize: function() { this.listenTo(this.model, 'destroy', this.stopListening); // auto update + this.template = $('#conversation').html(); + Mustache.parse(this.template); + this.$el.html(Mustache.render(this.template)); this.view = new Whisper.MessageListView({collection: this.model.messages()}); + this.$el.find('.discussion').append(this.view.el); }, events: { - 'submit #send': 'sendMessage', - 'close': 'undelegateEvents' + 'submit .send': 'sendMessage', + 'close': 'remove' }, sendMessage: function(e) { e.preventDefault(); - var input = this.$el.find('#send input'); + var input = this.$el.find('.send input'); if (input.val().length > 0) { this.model.sendMessage(input.val()); input.val(""); @@ -24,7 +29,7 @@ var Whisper = Whisper || {}; }, render: function() { - this.view.render(); + this.$el.show().insertAfter($('#gutter')); return this; } }); diff --git a/js/views/message_list_view.js b/js/views/message_list_view.js index 1325c1fc..bc4aedfd 100644 --- a/js/views/message_list_view.js +++ b/js/views/message_list_view.js @@ -6,10 +6,6 @@ var Whisper = Whisper || {}; Whisper.MessageListView = Whisper.ListView.extend({ tagName: 'ul', className: 'discussion', - itemView: Whisper.MessageView, - - render: function() { - $('#discussion').html('').append(this.el); - } + itemView: Whisper.MessageView }); })(); diff --git a/js/views/new_message_button.js b/js/views/new_message_button.js new file mode 100644 index 00000000..ed163883 --- /dev/null +++ b/js/views/new_message_button.js @@ -0,0 +1,120 @@ +var Whisper = Whisper || {}; + +(function () { + 'use strict'; + + Whisper.GroupRecipientsInputView = Backbone.View.extend({ + initialize: function() { + this.$el.tagsinput({ tagClass: this.tagClass }); + }, + + tagClass: function(item) { + try { + if (textsecure.utils.verifyNumber(item)) { + return; + } + } catch(ex) {} + return 'error'; + } + }); + + Whisper.NewGroupView = Backbone.View.extend({ + initialize: function() { + this.template = $('#new-group-form').html(); + Mustache.parse(this.template); + this.render(); + new Whisper.GroupRecipientsInputView({el: this.$el.find('input.numbers')}).$el.appendTo(this.$el); + }, + events: { + 'submit #send': 'send' + }, + + send: function(e) { + var numbers = this.$el.find('input.numbers').val().split(','); + var name = this.$el.find('input.name').val(); + var thread = Whisper.Threads.createGroup(numbers, name); + thread.sendMessage(input.val()); + // close this, select the new thread + }, + + render: function() { + this.$el.prepend($(Mustache.render(this.template))); + return this; + } + }); + + Whisper.MessageRecipientInputView = Backbone.View.extend({ + events: { + 'change': 'verifyNumber', + 'focus' : 'removeError' + }, + + removeError: function() { + this.$el.removeClass('error'); + }, + + verifyNumber: function(item) { + try { + if (textsecure.utils.verifyNumber(this.$el.val())) { + this.removeError(); + return; + } + } catch(ex) { console.log(ex); } + this.$el.addClass('error'); + } + }); + + Whisper.NewConversationView = Backbone.View.extend({ + initialize: function() { + this.template = $('#new-message-form').html(); + Mustache.parse(this.template); + this.input = this.$el.find('input.number'); + new Whisper.MessageRecipientInputView({el: this.input}); + }, + + events: { + 'submit #send': 'send', + 'close': 'undelegateEvents' + }, + + send: function(e) { + e.preventDefault(); + var number = this.input.val(); + if (textsecure.utils.verifyNumber(number)) { + var thread = Whisper.Threads.findOrCreateForRecipient(number); + var send_input = this.$el.find('#send input'); + thread.sendMessage(send_input.val()); + send_input.val(''); + this.$el.find('form.message').remove(); + this.$el.trigger('close'); + new Whisper.ConversationView({model: thread}); + } + }, + + render: function() { + this.$el.html(Mustache.render(this.template)); + return this; + } + }); + + Whisper.Header = Backbone.View.extend({ + events: { + 'click #new-message': 'new_message', + 'click #new-group': 'new_group' + }, + + new_message: function(e) { + e.preventDefault(); + $('.conversation').hide().trigger('close'); // detach any existing conversation views + this.view = new Whisper.NewConversationView().render().$el.insertAfter($('#gutter')); + //todo: less new + }, + + new_group: function(e) { + e.preventDefault(); + $('.conversation').trigger('close'); // detach any existing conversation views + new Whisper.NewGroupView({ el: $('.conversation') }); + } + }); + +})(); diff --git a/stylesheets/bootstrap-tagsinput.css b/stylesheets/bootstrap-tagsinput.css index 55f7c09d..05f19a1f 100644 --- a/stylesheets/bootstrap-tagsinput.css +++ b/stylesheets/bootstrap-tagsinput.css @@ -29,6 +29,12 @@ .bootstrap-tagsinput .tag { margin-right: 2px; color: white; + background-color: #00badd; + padding: 2px 5px; + border-radius: 2px; +} +.bootstrap-tagsinput .tag.error { + background-color: #ccc; } .bootstrap-tagsinput .tag [data-role="remove"] { margin-left: 8px;