WIP pill view for selected recipients

pull/749/head
lilia 10 years ago
parent c84ccfc735
commit 746e6530b9

@ -35,8 +35,10 @@
</div> </div>
</div> </div>
</script> </script>
<script type='text/x-tmpl-mustache' id='contact_pill'>
<span>{{ name }}</span><span class='remove'>x</span>
</script>
<script type='text/x-tmpl-mustache' id='contact'> <script type='text/x-tmpl-mustache' id='contact'>
<input type='checkbox' class='checkbox'></span>
<span class='avatar'></span> <span class='avatar'></span>
<div class='contact-details'> <div class='contact-details'>
<h3 class='contact-name'> <h3 class='contact-name'>
@ -52,21 +54,29 @@
</div> </div>
</script> </script>
<script type='text/x-tmpl-mustache' id='new-conversation'> <script type='text/x-tmpl-mustache' id='new-conversation'>
<input type='text' class='new-message' placeholder="Name or phone number" /> <div class='new-group-update-form clearfix'>
<div class='new-group-update-form'>
<div>
<input type='text' name='name' class='name' placeholder='Group Name' value="{{ name }}">
</div>
<div class='group-avatar'> <div class='group-avatar'>
<div><input type='file' name='avatar' class='file-input'></div> <div class='paperclip'></div>
<input type='file' name='avatar' class='file-input'>
</div> </div>
<button class='create-group'>Create group</button> <input type='text' name='name' class='name' placeholder='Group Name' value="{{ name }}">
</div>
<div class='recipients-container'>
<span class='recipients'></span>
<input type='text' class='new-message' placeholder="Name or phone number" />
</div>
<div class='buttons'>
<button class='create'>Next</button>
</div> </div>
<div class='results'> <div class='results'>
<div class='new-contact'></div> <div class='new-contact'></div>
<div class='contacts'></div> <div class='contacts'></div>
</div> </div>
</script> </script>
<script type='text/x-tmpl-mustache' id='attachment-preview'>
<img src="{{ source }}" class="preview" />
<div class="close">x</div>
</script>
<script type="text/javascript" src="js/components.js"></script> <script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="js/libtextsecure.js"></script> <script type="text/javascript" src="js/libtextsecure.js"></script>

@ -25,8 +25,7 @@ var Whisper = Whisper || {};
className: 'contact', className: 'contact',
events: { events: {
'click': 'open', 'click': 'open'
'click .checkbox': 'checkbox'
}, },
initialize: function() { initialize: function() {
this.template = $('#contact').html(); this.template = $('#contact').html();
@ -41,14 +40,6 @@ var Whisper = Whisper || {};
this.$el.trigger('open', {modelId: this.model.id}); this.$el.trigger('open', {modelId: this.model.id});
}, },
checkbox: function(e) {
e.stopPropagation();
this.$el.trigger('checkbox', {
modelId: this.model.id,
checked: e.target.checked
});
},
render: function() { render: function() {
this.$el.html( this.$el.html(
Mustache.render(this.template, { Mustache.render(this.template, {

@ -25,6 +25,7 @@ var Whisper = Whisper || {};
this.$input = this.$el.find('input[type=file]'); this.$input = this.$el.find('input[type=file]');
this.modal = new Whisper.ModalView({el: $('#file-modal')}); this.modal = new Whisper.ModalView({el: $('#file-modal')});
this.thumb = new Whisper.AttachmentPreviewView(); this.thumb = new Whisper.AttachmentPreviewView();
this.$el.addClass('file-input');
}, },
events: { events: {

@ -38,7 +38,7 @@
this.$el.addClass('loading'); this.$el.addClass('loading');
this.conversations.fetchActive({reset: true}).then(function() { this.conversations.fetchActive({reset: true}).then(function() {
this.$el.removeClass('loading'); this.$el.removeClass('loading');
window.conversations = this.conversations; window.conversations = this.conversations; // debug
}.bind(this)); }.bind(this));
extension.on('message', function() { extension.on('message', function() {
@ -51,20 +51,12 @@
'click .back button': 'hideCompose', 'click .back button': 'hideCompose',
'click .fab': 'showCompose', 'click .fab': 'showCompose',
'open #contacts': 'openConversation', 'open #contacts': 'openConversation',
'open .contacts': 'openConversation', 'open .new-conversation .contacts': 'openConversation'
'open .new-group-update-form': 'openConversation',
'open .new-contact': 'createConversation',
}, },
openConversation: function(e, data) { openConversation: function(e, data) {
bg.openConversation(data.modelId); bg.openConversation(data.modelId);
this.hideCompose(); this.hideCompose();
}, },
createConversation: function(e, data) {
this.newConversationView.new_contact.model.save().then(function() {
bg.openConversation(data.modelId);
});
this.hideCompose();
},
showCompose: function() { showCompose: function() {
this.$fab.hide(); this.$fab.hide();
this.$contacts.hide(); this.$contacts.hide();

@ -18,7 +18,7 @@ var Whisper = Whisper || {};
(function () { (function () {
'use strict'; 'use strict';
var typeahead = Backbone.TypeaheadCollection.extend({ var ContactsTypeahead = Backbone.TypeaheadCollection.extend({
typeaheadAttributes: [ typeaheadAttributes: [
'name', 'name',
'e164_number', 'e164_number',
@ -30,72 +30,160 @@ var Whisper = Whisper || {};
model: Whisper.Conversation model: Whisper.Conversation
}); });
Whisper.ContactPillView = Backbone.View.extend({
tagName: 'span',
className: 'recipient',
events: {
'click .remove': 'removeModel'
},
initialize: function() {
this.template = $('#contact_pill').html();
Mustache.parse(this.template);
var error = this.model.validate(this.model.attributes);
if (error) {
this.$el.addClass('error');
}
},
removeModel: function() {
this.$el.trigger('remove', {modelId: this.model.id});
this.remove();
},
render: function() {
this.$el.html(
Mustache.render(this.template, { name: this.model.getTitle() })
);
return this;
}
});
Whisper.RecipientListView = Whisper.ListView.extend({
itemView: Whisper.ContactPillView
});
Whisper.NewConversationView = Backbone.View.extend({ Whisper.NewConversationView = Backbone.View.extend({
className: 'new-conversation', className: 'new-conversation',
initialize: function() { initialize: function() {
this.template = $('#new-conversation').html(); this.template = $('#new-conversation').html();
Mustache.parse(this.template); Mustache.parse(this.template);
this.$el.html($(Mustache.render(this.template))); this.$el.html($(Mustache.render(this.template)));
this.$input = this.$el.find('input.new-message');
this.$group_update = this.$el.find('.new-group-update-form'); this.$group_update = this.$el.find('.new-group-update-form');
this.$buttons = this.$el.find('.buttons');
this.$input = this.$el.find('input.new-message');
// Collection of contacts to match user input against
this.typeahead = new ContactsTypeahead();
this.typeahead.fetch({ conditions: { type: 'private' } });
this.typeahead_collection = new typeahead(); // View to display the matched contacts from typeahead
this.typeahead_view = new Whisper.ConversationListView({ this.typeahead_view = new Whisper.ConversationListView({
collection : new Whisper.ConversationCollection({ collection : new Whisper.ConversationCollection([], {
comparator: function(m) { return m.getTitle(); } comparator: function(m) { return m.getTitle(); }
}), })
className: 'typeahead'
});
this.typeahead_view.$el.appendTo(this.$el.find('.contacts'));
this.typeahead_collection.fetch({
conditions: { type: 'private' }
}); });
this.$el.find('.contacts').append(this.typeahead_view.el);
// View to display a new contact
this.new_contact = new Whisper.ConversationListItemView({ this.new_contact = new Whisper.ConversationListItemView({
model: new Whisper.Conversation({ model: new Whisper.Conversation({
active_at: null, active_at: null,
type: 'private' type: 'private'
}) })
}).render(); }).render();
this.$el.find('.new-contact').append(this.new_contact.el);
this.newGroupUpdateView = new Whisper.NewGroupUpdateView({ // Group avatar file input
model: new Whisper.Conversation({ type: 'group' }), this.avatarInput = new Whisper.FileInputView({
el: this.$group_update el: this.$el.find('.group-avatar')
});
// Collection of recipients selected for the new message
this.recipients = new Whisper.ConversationCollection([], {
comparator: false
});
// View to display the selected recipients
new Whisper.RecipientListView({
collection: this.recipients,
el: this.$el.find('.recipients')
}); });
this.group_members = new Whisper.ConversationCollection();
this.$el.find('.new-contact').append(this.new_contact.el);
}, },
events: { events: {
'change input.new-message': 'filterContacts', 'change input.new-message': 'filterContacts',
'keyup input.new-message': 'filterContacts', 'keyup input.new-message': 'filterContacts',
'checkbox .contact': 'updateGroup', 'open .new-contact': 'addNewRecipient',
'click .create-group': 'createGroup' 'open .contacts': 'addRecipient',
'remove .recipient': 'removeRecipient',
'click .create': 'create'
}, },
updateGroup: function(e, data) { addNewRecipient: function(e, data) {
this.$input.focus(); this.new_contact.model.newContact = true; // hack
if (data.checked) { this.recipients.add(this.new_contact.model);
this.group_members.add({id: data.modelId}); this.new_contact.model = new Whisper.Conversation({
active_at: null,
type: 'private'
});
this.resetTypeahead();
this.updateControls();
},
addRecipient: function(e, data) {
this.recipients.add(this.typeahead.remove(data.modelId));
this.filterContacts();
this.updateControls();
},
removeRecipient: function(e, data) {
var model = this.recipients.remove(data.modelId);
if (!model.newContact) { // hack
this.typeahead.add(model);
}
this.filterContacts();
this.updateControls();
},
updateControls: function() {
if (this.recipients.length > 0) {
this.$buttons.slideDown();
} else { } else {
this.group_members.remove({id: data.modelId}); this.$buttons.slideUp();
} }
this.group_members if (this.recipients.length > 1) {
if (this.group_members.length) { this.$group_update.slideDown();
this.$group_update.show(); } else {
this.$group_update.slideUp();
}
this.$input.focus();
},
create: function() {
if (this.recipients.length > 1) {
this.createGroup();
} else { } else {
this.$group_update.hide(); this.createConversation();
} }
}, },
createConversation: function() {
var conversation = new Whisper.Conversation({
id: this.recipients.at(0).id,
type: 'private'
});
conversation.fetch().fail(function() {
if (conversation.save()) {
this.$el.trigger('open', { modelId: conversation.id });
}
});
},
createGroup: function() { createGroup: function() {
return this.newGroupUpdateView.avatarInput.getFiles().then(function(avatarFiles) { return this.avatarInput.getFiles().then(function(avatarFiles) {
var attributes = { var attributes = {
type: 'group', type: 'group',
name: this.$el.find('.new-group-update-form .name').val(), name: this.$el.find('.new-group-update-form .name').val(),
avatar: avatarFiles[0], avatar: avatarFiles[0],
members: this.group_members.pluck('id') members: this.recipients.pluck('id')
}; };
return textsecure.messaging.createGroup( return textsecure.messaging.createGroup(
attributes.members, attributes.name, attributes.avatar attributes.members, attributes.name, attributes.avatar
@ -109,11 +197,18 @@ var Whisper = Whisper || {};
}.bind(this)); }.bind(this));
}, },
reset: function() { resetTypeahead: function() {
this.new_contact.$el.hide(); this.new_contact.$el.hide();
this.$input.val('').focus(); this.$input.val('').focus();
this.typeahead_view.collection.reset(this.typeahead_collection.models); this.typeahead_view.collection.reset(this.typeahead.models);
this.group_members.reset([]); },
reset: function() {
this.$buttons.hide();
this.$group_update.hide();
this.typeahead.add(this.recipients.models);
this.recipients.reset([]);
this.resetTypeahead();
}, },
filterContacts: function() { filterContacts: function() {
@ -121,15 +216,15 @@ var Whisper = Whisper || {};
if (query.length) { if (query.length) {
if (this.maybeNumber(query)) { if (this.maybeNumber(query)) {
this.new_contact.model.set('id', query); this.new_contact.model.set('id', query);
this.new_contact.$el.show(); this.new_contact.render().$el.show();
} else { } else {
this.new_contact.$el.hide(); this.new_contact.$el.hide();
} }
this.typeahead_view.collection.reset( this.typeahead_view.collection.reset(
this.typeahead_collection.typeahead(query) this.typeahead.typeahead(query)
); );
} else { } else {
this.reset(); this.resetTypeahead();
} }
}, },

@ -180,54 +180,8 @@ button {
} }
.attachments { .attachments {
position: relative;
float: left; float: left;
height: 100%; height: 100%;
width: 36px;
margin-right: 10px;
.paperclip {
width: 100%;
height: 100%;
background: url('/images/paperclip.png') no-repeat;
background-size: 90%;
background-position: center 6px;
}
input[type=file] {
display: none;
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
top: 0;
left: 0;
cursor: pointer;
z-index: 1;
}
img.preview {
max-width: 100%;
}
.close {
font-family: sans-serif;
color: white;
position: absolute;
top: -10px;
left: 20px;
text-align: center;
cursor: default;
border-radius: 50%;
width: 20px;
height: 20px;
padding: 0px;
background: #666;
color: #fff;
text-align: center;
}
} }
.send-btn { .send-btn {

@ -10,6 +10,15 @@ body {
font-size: 14px; font-size: 14px;
} }
.clearfix:before,
.clearfix:after {
display: table;
content: " ";
}
.clearfix:after {
clear: both;
}
#header { #header {
position: fixed; position: fixed;
top: 0; top: 0;
@ -51,3 +60,51 @@ body {
} }
} }
} }
.file-input {
position: relative;
width: 36px;
margin-right: 10px;
.paperclip {
width: 100%;
height: 100%;
background: url('/images/paperclip.png') no-repeat;
background-size: 90%;
background-position: center 6px;
}
input[type=file] {
display: none;
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
top: 0;
left: 0;
cursor: pointer;
z-index: 1;
}
img.preview {
max-width: 100%;
}
.close {
font-family: sans-serif;
color: white;
position: absolute;
top: -10px;
left: 20px;
text-align: center;
cursor: default;
border-radius: 50%;
width: 20px;
height: 20px;
padding: 0px;
background: #666;
color: #fff;
text-align: center;
}
}

@ -10,13 +10,17 @@
// TODO: spinner // TODO: spinner
} }
.contact .checkbox { .contact {
display: none; .number, .checkbox {
display: none;
}
} }
input.new-message { input.new-message {
box-sizing: border-box; border: none;
width: 100%; padding: 0;
margin: 0;
outline: 0;
} }
.back { .back {
@ -39,8 +43,50 @@ input.new-message {
} }
} }
.new-conversation .new-group-update-form { .new-conversation {
display: none; .new-group-update-form {
display: none;
button.create-group {
float: right;
}
.group-avatar {
float: left;
height: 36px;
}
}
.buttons {
display: none;
}
}
.new-conversation {
.recipients-container {
background-color: white;
padding: 2px;
border-bottom: 1px solid #f2f2f2;
line-height: 24px;
}
.recipient {
display: inline-block;
margin: 0 2px 2px 0;
padding: 0 5px;
border-radius: 10px;
background-color: $blue;
color: white;
&.error {
background-color: #f00;
}
.remove {
margin-left: 5px;
padding: 0 2px;
}
}
} }
.fab { .fab {
@ -70,23 +116,32 @@ input.new-message {
font-size: smaller; font-size: smaller;
} }
.new-contact, .new-conversation {
.typeahead {
.last-message, .last-timestamp { .last-message, .last-timestamp {
display: none; display: none;
} }
.contact .checkbox { .contact {
display: inline-block; .checkbox, .number {
display: inline-block;
}
.number {
color: $grey;
font-size: small;
}
} }
} }
.new-contact .contact-details::before { .new-contact {
content: 'Create new contact'; .contact-name { display: none; }
display: block; .contact-details::before {
font-style: italic; content: 'Create new contact';
opacity: 0.7; display: block;
padding-right: 8px; font-style: italic;
opacity: 0.7;
padding-right: 8px;
}
} }
.index { .index {

@ -15,6 +15,14 @@ body {
font-family: Roboto, "Helvetica Neue", Arial, Helvetica, sans-serif; font-family: Roboto, "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 14px; } font-size: 14px; }
.clearfix:before,
.clearfix:after {
display: table;
content: " "; }
.clearfix:after {
clear: both; }
#header { #header {
position: fixed; position: fixed;
top: 0; top: 0;
@ -50,18 +58,58 @@ body {
white-space: nowrap; white-space: nowrap;
padding: 5px 15px 5px 10px; } padding: 5px 15px 5px 10px; }
.file-input {
position: relative;
width: 36px;
margin-right: 10px; }
.file-input .paperclip {
width: 100%;
height: 100%;
background: url("/images/paperclip.png") no-repeat;
background-size: 90%;
background-position: center 6px; }
.file-input input[type=file] {
display: none;
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
top: 0;
left: 0;
cursor: pointer;
z-index: 1; }
.file-input img.preview {
max-width: 100%; }
.file-input .close {
font-family: sans-serif;
color: white;
position: absolute;
top: -10px;
left: 20px;
text-align: center;
cursor: default;
border-radius: 50%;
width: 20px;
height: 20px;
padding: 0px;
background: #666;
color: #fff;
text-align: center; }
.gutter { .gutter {
margin-top: 36px; } margin-top: 36px; }
#contacts { #contacts {
overflow-y: scroll; } overflow-y: scroll; }
.contact .checkbox { .contact .number, .contact .checkbox {
display: none; } display: none; }
input.new-message { input.new-message {
box-sizing: border-box; border: none;
width: 100%; } padding: 0;
margin: 0;
outline: 0; }
.back { .back {
display: none; display: none;
@ -81,6 +129,31 @@ input.new-message {
.new-conversation .new-group-update-form { .new-conversation .new-group-update-form {
display: none; } display: none; }
.new-conversation .new-group-update-form button.create-group {
float: right; }
.new-conversation .new-group-update-form .group-avatar {
float: left;
height: 36px; }
.new-conversation .buttons {
display: none; }
.new-conversation .recipients-container {
background-color: white;
padding: 2px;
border-bottom: 1px solid #f2f2f2;
line-height: 24px; }
.new-conversation .recipient {
display: inline-block;
margin: 0 2px 2px 0;
padding: 0 5px;
border-radius: 10px;
background-color: #2a92e7;
color: white; }
.new-conversation .recipient.error {
background-color: #f00; }
.new-conversation .recipient .remove {
margin-left: 5px;
padding: 0 2px; }
.fab { .fab {
z-index: 1; z-index: 1;
@ -105,14 +178,16 @@ input.new-message {
.last-timestamp { .last-timestamp {
font-size: smaller; } font-size: smaller; }
.new-contact .last-message, .new-contact .last-timestamp, .new-conversation .last-message, .new-conversation .last-timestamp {
.typeahead .last-message,
.typeahead .last-timestamp {
display: none; } display: none; }
.new-contact .contact .checkbox, .new-conversation .contact .checkbox, .new-conversation .contact .number {
.typeahead .contact .checkbox {
display: inline-block; } display: inline-block; }
.new-conversation .contact .number {
color: #616161;
font-size: small; }
.new-contact .contact-name {
display: none; }
.new-contact .contact-details::before { .new-contact .contact-details::before {
content: 'Create new contact'; content: 'Create new contact';
display: block; display: block;
@ -297,44 +372,8 @@ button {
font-size: 24px; font-size: 24px;
background: transparent; } background: transparent; }
.bottom-bar .attachments { .bottom-bar .attachments {
position: relative;
float: left; float: left;
height: 100%; height: 100%; }
width: 36px;
margin-right: 10px; }
.bottom-bar .attachments .paperclip {
width: 100%;
height: 100%;
background: url("/images/paperclip.png") no-repeat;
background-size: 90%;
background-position: center 6px; }
.bottom-bar .attachments input[type=file] {
display: none;
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
top: 0;
left: 0;
cursor: pointer;
z-index: 1; }
.bottom-bar .attachments img.preview {
max-width: 100%; }
.bottom-bar .attachments .close {
font-family: sans-serif;
color: white;
position: absolute;
top: -10px;
left: 20px;
text-align: center;
cursor: default;
border-radius: 50%;
width: 20px;
height: 20px;
padding: 0px;
background: #666;
color: #fff;
text-align: center; }
.bottom-bar .send-btn { .bottom-bar .send-btn {
float: right; float: right;
height: 100%; height: 100%;

Loading…
Cancel
Save