New layout/design

Two column layout and style tweaks. Templatized conversation views.
Generalized list view.
This commit is contained in:
lilia 2014-07-22 08:55:26 -10:00
parent 6d5e32bca8
commit def32f42d4
9 changed files with 203 additions and 91 deletions

View file

@ -3,14 +3,19 @@
position: relative;
min-height: 64px;
margin: auto;
padding: 1em;
border-radius: 10px;
border: 2px solid #acdbf5;
padding: 0.5em;
background-color: #7fd0ed;
color: #fff;
margin-bottom: 0.5em;
-webkit-animation-duration: 1s;
-webkit-animation-name: convoopen;
clear: both;
}
li.conversation {
border-bottom: 1px solid #ccc;
}
.conversation .name {
padding: 1em 0 1em 3em;
}
.conversation.closed {
@ -54,9 +59,7 @@
float: right;
}
.conversation .image {
position: absolute;
top: 8px;
left: 10px;
float: left;
display: inline-block;
background-color: #fff;
border: 2px solid #acdbf5;

View file

@ -19,11 +19,75 @@
body {
margin: 0;
min-width: 400px;
min-height: 500px;
font-family: sans-serif;
color: #333;
}
/* layout 2 col full height */
html {
overflow: auto;
height: 100%;
}
body {
margin:0;
padding:0;
border:0; /* This removes the border around the viewport in old versions of IE */
width:100%;
height: 100%;
min-width:400px; /* Minimum width of layout - remove line if not required */
/* The min-width property does not work in old versions of Internet Explorer */
}
/* column container */
.colmask {
position:relative; /* This fixes the IE7 overflow hidden bug */
clear:both;
float:left;
width:100%; /* width of whole page */
height: 100%;
overflow:hidden; /* This chops off any overhanging divs */
}
/* common column settings */
.colleft {
float:left;
width:100%;
height: 100%;
position:relative;
border-right: 2px solid #acdbf5;
}
.col1,
.col2 {
float:left;
position:relative;
padding:0 0 1em 0;
overflow:hidden;
}
/* 2 Column (left menu) settings */
.leftmenu .colleft {
right:72%; /* right column width */
background:#f4f4f4; /* left column background colour */
}
.leftmenu .col1 {
width:72%; /* right column content width */
left:100%; /* 100% plus left column left padding */
}
.leftmenu .col2 {
width:28%; /* left column content width (column width minus left and right padding) */
}
/* end layout */
/*
#sidebar {
float: left;
width: 30%;
padding: 1em;
}
#main {
margin-left: 35%;
padding: 1em;
border-left: 2px solid #acdbf5;
}
*/
.container {
max-width: 960px;
@ -33,6 +97,7 @@ body {
header {
padding: 5px 0;
background-color: #7fd0ed;
}
label {
@ -40,6 +105,7 @@ label {
margin-right: 1em;
}
#compose-create,
#compose-cancel {
float: right;
}
@ -48,11 +114,12 @@ label {
padding: 0.3em 1em;
}
#popup_send_numbers {
margin-bottom: 0;
#send_numbers {
max-width: 70%;
margin-bottom: 1em;
}
#popup_send_numbers:focus + .contacts,
#send_numbers:focus + .contacts,
.contacts:hover {
display: block;
z-index: 10;

View file

@ -20,8 +20,9 @@ textsecure.registerOnLoadFunction(function() {
extension.navigator.tabs.create("options.html");
} else {
new Whisper.ConversationListView();
new Whisper.ConversationListView({el: $('#contacts')});
new Whisper.ConversationComposeView({el: $('body')});
Whisper.Threads.fetch({reset: true});
$('.my-number').text(textsecure.storage.getUnencrypted("number_id").split(".")[0]);
textsecure.storage.putUnencrypted("unreadCount", 0);
extension.navigator.setBadgeText("");

View file

@ -0,0 +1,29 @@
var Whisper = Whisper || {};
(function () {
'use strict';
Whisper.ConversationListView = Whisper.ListView.extend({
tagName: 'ul',
id: 'contacts',
itemView: Whisper.ConversationView,
collection: Whisper.Threads,
events: {
'select .conversation': 'select',
'deselect': 'deselect'
},
select: function(e) {
var target = $(e.target).closest('.conversation');
target.siblings().addClass('closed');
target.addClass('selected').trigger('open');
return false;
},
deselect: function() {
this.$el.find('.selected').removeClass('selected').trigger('close');
this.$el.find('.conversation').show();
}
});
})();

View file

@ -1,46 +0,0 @@
var Whisper = Whisper || {};
(function () {
'use strict';
Whisper.ConversationListView = Backbone.View.extend({
tagName: 'ul',
id: 'conversations',
initialize: function() {
this.views = {};
this.threads = Whisper.Threads;
this.listenTo(this.threads, 'change:completed', this.render); // auto update
this.listenTo(this.threads, 'add', this.addThread);
this.listenTo(this.threads, 'reset', this.addAll);
this.listenTo(this.threads, 'all', this.render);
this.listenTo(Whisper.Messages, 'add', this.addMessage);
// Suppresses 'add' events with {reset: true} and prevents the app view
// from being re-rendered for every model. Only renders when the 'reset'
// event is triggered at the end of the fetch.
//this.messages.threads({reset: true});
Whisper.Threads.fetch({reset: true});
Whisper.Messages.fetch();
this.$el.appendTo($('#inbox'));
},
addThread: function(thread) {
this.views[thread.id] = new Whisper.ConversationView({model: thread});
this.$el.prepend(this.views[thread.id].render().el);
},
addAll: function() {
this.$el.html('');
_.each(this.threads.where({'active': true}), this.addThread, this);
},
addMessage: function(message) {
var thread = message.thread();
if (!_.has(this.views, thread.id)) {
this.addThread(thread);
}
thread.trigger('message', message);
}
});
})();

View file

@ -34,10 +34,13 @@ var Whisper = Whisper || {};
className: 'conversation',
events: {
'click': 'toggle',
'click': 'open',
'submit form': 'sendMessage'
},
initialize: function() {
this.template = $('#contact').html();
Mustache.parse(this.template);
this.listenTo(this.model, 'change', this.render); // auto update
this.listenTo(this.model, 'message', this.addMessage); // auto update
this.listenTo(this.model, 'destroy', this.remove); // auto update
@ -50,15 +53,7 @@ var Whisper = Whisper || {};
this.$name = $('<span class="name">');
this.$header = $('<div class="header">').append(this.$image, this.$name);
this.$button = $('<button class="btn">').append($('<span>').text('Send'));
this.$input = $('<input type="text">').attr('autocomplete','off');
this.$form = $("<form class=''>").append(this.$input);
this.$messages = $('<ul class="messages">');
this.$collapsable = $('<div class="collapsable">').hide();
this.$collapsable.append(this.$messages, this.$form);
this.$el.append(this.$destroy, this.$header, this.$collapsable);
this.$el.append(this.$header, this.$collapsable);
},
sendMessage: function(e) {
@ -74,16 +69,15 @@ var Whisper = Whisper || {};
close: function() {
if (!this.$el.hasClass('closed')) {
this.$el.addClass('closed').find('.collapsable').slideUp(600);
this.$el.addClass('closed');
}
},
open: function(e) {
if (this.$el.hasClass('closed')) {
this.$el.removeClass('closed');
this.$el.find('.collapsable').slideDown(600);
}
this.$el.find('input').focus();
this.$el.siblings().addClass('closed');
this.$el.removeClass('closed');
var v = new Whisper.MessageListView({collection: this.model.messages()});
v.render();
},
toggle: function() {
@ -105,9 +99,14 @@ var Whisper = Whisper || {};
},
render: function() {
this.$name.text(this.model.get('name'));
this.$image.css('background-image: ' + this.model.get('image') + ';');
this.$el.html(
Mustache.render(this.template, {
name: this.model.get('name')
})
);
return this;
}
},
});
})();

36
js/views/list_view.js Normal file
View file

@ -0,0 +1,36 @@
var Whisper = Whisper || {};
(function () {
'use strict';
/*
* Generic list view that watches a given collection, wraps its members in
* a given child view and adds the child view elements to its own element.
*/
Whisper.ListView = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.listenTo(this.collection, 'change', this.render); // auto update
this.listenTo(this.collection, 'add', this.addOne);
this.listenTo(this.collection, 'reset', this.addAll);
this.listenTo(this.collection, 'all', this.render);
this.collection.fetch({reset: true});
},
addOne: function(model) {
if (this.itemView) {
var view = new this.itemView({model: model});
this.$el.append(view.render().el);
}
},
addAll: function() {
this.$el.html('');
this.collection.each(this.addOne, this);
},
last: function() {
this.collection.at(this.collection.length - 1);
}
});
})();

View file

@ -0,0 +1,15 @@
var Whisper = Whisper || {};
(function () {
'use strict';
Whisper.MessageListView = Whisper.ListView.extend({
tagName: 'ul',
className: 'messages',
itemView: Whisper.MessageView,
render: function() {
$('#main').html('').append(this.el);
}
});
})();

View file

@ -30,22 +30,28 @@
<span class='help' id='new-chat-help'>new message</span>
</div>
</header>
<div class='container'>
<div id="listener"></div>
<div id="log"></div>
<div id="inbox">
<form id="send" style="display:none;">
<div class='closed conversation'>
<div class='header'>
<span class='image'></span>
<input id="send_numbers" type='text' placeholder="+xxxxxxxxxx">
<input type=submit id="compose-create" class='btn btn-square' value='>'>
<button id="compose-cancel" class='btn btn-square'>&times;</button>
</div>
<div class='colmask leftmenu'>
<div class='colleft'>
<div id='main' class='col1'>
<div class='container'>
<div id="listener"></div>
<div id="log"></div>
</div>
</form>
</div>
<div id='sidebar' class="col2">
<form id="send" class='clearfix'>
<input type=submit id="compose-create" class='btn btn-square' value='>'>
<input id="send_numbers" type='text' placeholder="+xxxxxxxxxx">
</form>
<ul id="contacts"></ul>
</div>
</div>
</div>
<script type="text/x-tmpl-mustache" id="contact">
<div class='image'></div>
<div class='name'>{{name}}</div>
<div class='snippet'>{{lastMessage}}</div>
</script>
<script type="text/x-tmpl-mustache" id="message">
<div class="bubble">
<span class="message-text">
@ -84,8 +90,10 @@
<script type="text/javascript" src="js/chromium.js"></script>
<script type="text/javascript" src="js/views/notifications.js"></script>
<script type="text/javascript" src="js/views/message_view.js"></script>
<script type="text/javascript" src="js/views/list_view.js"></script>
<script type="text/javascript" src="js/views/message_list_view.js"></script>
<script type="text/javascript" src="js/views/conversations/show.js"></script>
<script type="text/javascript" src="js/views/conversations/index.js"></script>
<script type="text/javascript" src="js/views/conversation_list_view.js"></script>
<script type="text/javascript" src="js/views/conversations/new.js"></script>
<script type="text/javascript" src="js/popup.js"></script>
</body>