Restore two column layout
Establishes basic functionality for viewing conversations in two column mode, including message area and message list resizing, and maintaining scroll position. Various subviews need to be retooled but are more or less still functional, i.e., new message, message detail, key verification, etc...
This commit is contained in:
parent
00dfcbb462
commit
d6a4e6e496
10 changed files with 134 additions and 75 deletions
|
@ -16,12 +16,29 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
|
<script type='text/x-tmpl-mustache' id='two-column'>
|
||||||
|
<div class='title-bar' id='header'>
|
||||||
|
<div class='menu'>
|
||||||
|
<button class='hamburger'></button>
|
||||||
|
<ul class='menu-list'>
|
||||||
|
<li><a class='new-group'>Create Group</a></li>
|
||||||
|
<li><a class='settings'>Settings</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<span class='conversation-title'>Signal</span>
|
||||||
|
<div class='socket-status'></div>
|
||||||
|
</div>
|
||||||
|
<div class='gutter'>
|
||||||
|
<div class='conversations scrollable'></div>
|
||||||
|
<span class='fab'></span>
|
||||||
|
</div>
|
||||||
|
<div class='conversation-stack'></div>
|
||||||
|
</script>
|
||||||
<script type='text/x-tmpl-mustache' id='conversation'>
|
<script type='text/x-tmpl-mustache' id='conversation'>
|
||||||
<div class='title-bar' id='header'>
|
<div class='conversation-header'>
|
||||||
<div class='menu'>
|
<div class='conversation-menu menu'>
|
||||||
<button class='hamburger'></button>
|
<button class='hamburger'></button>
|
||||||
<ul class='menu-list'>
|
<ul class='menu-list'>
|
||||||
<li><a class='openInbox'>Open Inbox</a></li>
|
|
||||||
{{#group}}
|
{{#group}}
|
||||||
<li><a class='view-members'>Members</a></li>
|
<li><a class='view-members'>Members</a></li>
|
||||||
<li><a class='new-group-update'>Update group</a></li>
|
<li><a class='new-group-update'>Update group</a></li>
|
||||||
|
|
|
@ -117,45 +117,7 @@
|
||||||
conversation.fetchContacts();
|
conversation.fetchContacts();
|
||||||
});
|
});
|
||||||
conversation.fetchMessages();
|
conversation.fetchMessages();
|
||||||
|
return conversation;
|
||||||
var windowId = windowMap.windowIdFrom(modelId);
|
|
||||||
|
|
||||||
// prevent multiple copies of the same conversation from being opened
|
|
||||||
if (!windowId) {
|
|
||||||
// open the panel
|
|
||||||
extension.windows.open({
|
|
||||||
id: modelId,
|
|
||||||
url: 'conversation.html',
|
|
||||||
type: 'panel',
|
|
||||||
frame: 'none',
|
|
||||||
focused: true,
|
|
||||||
width: 300,
|
|
||||||
height: 440,
|
|
||||||
minWidth: 230,
|
|
||||||
minHeight: 73
|
|
||||||
}, function (windowInfo) {
|
|
||||||
windowId = windowInfo.id;
|
|
||||||
windowMap.add({ windowId: windowId, modelId: modelId });
|
|
||||||
|
|
||||||
windowInfo.onClosed.addListener(function () {
|
|
||||||
onWindowClosed(windowId);
|
|
||||||
});
|
|
||||||
|
|
||||||
// close the panel if background.html is refreshed
|
|
||||||
extension.windows.beforeUnload(function() {
|
|
||||||
// TODO: reattach after reload instead of closing.
|
|
||||||
extension.windows.remove(windowId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// focus the panel
|
|
||||||
extension.windows.focus(windowId, function (error) {
|
|
||||||
if (error) {
|
|
||||||
closeConversation(windowId); // panel isn't actually open...
|
|
||||||
openConversation(modelId); // ...and so we try again.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Inbox window controller */
|
/* Inbox window controller */
|
||||||
|
@ -170,7 +132,7 @@
|
||||||
type: 'panel',
|
type: 'panel',
|
||||||
frame: 'none',
|
frame: 'none',
|
||||||
focused: true,
|
focused: true,
|
||||||
width: 260,
|
width: 580,
|
||||||
height: 440,
|
height: 440,
|
||||||
minWidth: 230,
|
minWidth: 230,
|
||||||
minHeight: 150
|
minHeight: 150
|
||||||
|
|
|
@ -22,6 +22,9 @@
|
||||||
className: function() {
|
className: function() {
|
||||||
return [ 'conversation', this.model.get('type') ].join(' ');
|
return [ 'conversation', this.model.get('type') ].join(' ');
|
||||||
},
|
},
|
||||||
|
id: function() {
|
||||||
|
return 'conversation-' + this.model.cid;
|
||||||
|
},
|
||||||
template: $('#conversation').html(),
|
template: $('#conversation').html(),
|
||||||
render_attributes: function() {
|
render_attributes: function() {
|
||||||
return {
|
return {
|
||||||
|
@ -36,10 +39,6 @@
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
this.appWindow = options.appWindow;
|
this.appWindow = options.appWindow;
|
||||||
new Whisper.WindowControlsView({
|
|
||||||
appWindow: this.appWindow
|
|
||||||
}).$el.insertAfter(this.$('.menu'));
|
|
||||||
|
|
||||||
this.fileInput = new Whisper.FileInputView({
|
this.fileInput = new Whisper.FileInputView({
|
||||||
el: this.$('.attachments'),
|
el: this.$('.attachments'),
|
||||||
window: this.appWindow.contentWindow
|
window: this.appWindow.contentWindow
|
||||||
|
@ -85,7 +84,8 @@
|
||||||
'click .hamburger': 'toggleMenu',
|
'click .hamburger': 'toggleMenu',
|
||||||
'click .openInbox' : 'openInbox',
|
'click .openInbox' : 'openInbox',
|
||||||
'click' : 'onClick',
|
'click' : 'onClick',
|
||||||
'select .entry': 'messageDetail'
|
'select .entry': 'messageDetail',
|
||||||
|
'force-resize': 'forceUpdateMessageFieldSize'
|
||||||
},
|
},
|
||||||
|
|
||||||
viewMembers: function() {
|
viewMembers: function() {
|
||||||
|
@ -235,12 +235,15 @@
|
||||||
|
|
||||||
$bottomBar.outerHeight(this.$messageField.outerHeight() + 1);
|
$bottomBar.outerHeight(this.$messageField.outerHeight() + 1);
|
||||||
var $bottomBarNewHeight = $bottomBar.outerHeight();
|
var $bottomBarNewHeight = $bottomBar.outerHeight();
|
||||||
$discussionContainer.outerHeight(this.$el.outerHeight() - $bottomBarNewHeight - this.$('#header').outerHeight());
|
$discussionContainer.outerHeight(this.$el.outerHeight() - $bottomBarNewHeight - this.$('.conversation-header').outerHeight());
|
||||||
|
|
||||||
this.view.scrollToBottomIfNeeded();
|
this.view.scrollToBottomIfNeeded();
|
||||||
},
|
},
|
||||||
|
|
||||||
forceUpdateMessageFieldSize: function (event) {
|
forceUpdateMessageFieldSize: function (event) {
|
||||||
|
if (this.$el.css('display') === 'none') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.view.scrollToBottomIfNeeded();
|
this.view.scrollToBottomIfNeeded();
|
||||||
window.autosize.update(this.$messageField);
|
window.autosize.update(this.$messageField);
|
||||||
this.updateMessageFieldSize(event);
|
this.updateMessageFieldSize(event);
|
||||||
|
|
|
@ -56,11 +56,32 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Whisper.ConversationStack = Whisper.View.extend({
|
||||||
|
className: 'conversation-stack',
|
||||||
|
open: function(conversation) {
|
||||||
|
var $el = this.$('#conversation-' + conversation.cid);
|
||||||
|
if ($el === null || $el.length === 0) {
|
||||||
|
var view = new Whisper.ConversationView({
|
||||||
|
model: conversation,
|
||||||
|
appWindow: this.model.appWindow
|
||||||
|
});
|
||||||
|
$el = view.$el;
|
||||||
|
}
|
||||||
|
$el.prependTo(this.el);
|
||||||
|
$el.find('.message-list').trigger('reset-scroll');
|
||||||
|
$el.trigger('force-resize');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Whisper.InboxView = Whisper.View.extend({
|
Whisper.InboxView = Whisper.View.extend({
|
||||||
template: $('#inbox').html(),
|
template: $('#two-column').html(),
|
||||||
className: 'inbox',
|
className: 'inbox',
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
this.render();
|
this.render();
|
||||||
|
this.conversation_stack = new Whisper.ConversationStack({
|
||||||
|
el: this.$('.conversation-stack'),
|
||||||
|
model: { appWindow: options.appWindow }
|
||||||
|
});
|
||||||
|
|
||||||
this.newConversationView = new Whisper.NewConversationView({
|
this.newConversationView = new Whisper.NewConversationView({
|
||||||
appWindow: options.appWindow
|
appWindow: options.appWindow
|
||||||
|
@ -91,7 +112,8 @@
|
||||||
'select .contact': 'openConversation',
|
'select .contact': 'openConversation',
|
||||||
},
|
},
|
||||||
openConversation: function(e, data) {
|
openConversation: function(e, data) {
|
||||||
bg.openConversation(data.modelId);
|
var conversation = bg.openConversation(data.modelId);
|
||||||
|
this.conversation_stack.open(conversation);
|
||||||
this.hideCompose();
|
this.hideCompose();
|
||||||
},
|
},
|
||||||
showCompose: function() {
|
showCompose: function() {
|
||||||
|
|
|
@ -17,10 +17,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
|
|
||||||
var scrollPosition,
|
|
||||||
scrollHeight,
|
|
||||||
shouldStickToBottom;
|
|
||||||
|
|
||||||
Whisper.MessageListView = Whisper.ListView.extend({
|
Whisper.MessageListView = Whisper.ListView.extend({
|
||||||
tagName: 'ul',
|
tagName: 'ul',
|
||||||
className: 'message-list',
|
className: 'message-list',
|
||||||
|
@ -28,16 +24,24 @@
|
||||||
events: {
|
events: {
|
||||||
'add': 'scrollToBottom',
|
'add': 'scrollToBottom',
|
||||||
'update *': 'scrollToBottom',
|
'update *': 'scrollToBottom',
|
||||||
'scroll': 'measureScrollPosition'
|
'scroll': 'measureScrollPosition',
|
||||||
|
'reset-scroll': 'resetScrollPosition'
|
||||||
},
|
},
|
||||||
measureScrollPosition: function() {
|
measureScrollPosition: function() {
|
||||||
scrollPosition = this.$el.scrollTop() + this.$el.outerHeight();
|
this.scrollPosition = this.$el.scrollTop() + this.$el.outerHeight();
|
||||||
scrollHeight = this.el.scrollHeight;
|
this.scrollHeight = this.el.scrollHeight;
|
||||||
shouldStickToBottom = scrollPosition === scrollHeight;
|
this.shouldStickToBottom = this.scrollPosition === this.scrollHeight;
|
||||||
|
},
|
||||||
|
resetScrollPosition: function() {
|
||||||
|
var scrollPosition = this.scrollPosition;
|
||||||
|
if (this.scrollHeight !== this.el.scrollHeight) {
|
||||||
|
scrollPosition = this.el.scrollHeight * this.scrollPosition / this.scrollHeight;
|
||||||
|
}
|
||||||
|
this.$el.scrollTop(scrollPosition - this.$el.outerHeight());
|
||||||
},
|
},
|
||||||
scrollToBottomIfNeeded: function() {
|
scrollToBottomIfNeeded: function() {
|
||||||
if (shouldStickToBottom) {
|
if (this.shouldStickToBottom) {
|
||||||
this.$el.scrollTop(scrollHeight);
|
this.$el.scrollTop(this.scrollHeight);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollToBottom: function() {
|
scrollToBottom: function() {
|
||||||
|
|
|
@ -341,10 +341,7 @@
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
$button-width: 36px;
|
$button-width: 36px;
|
||||||
|
|
||||||
position: fixed;
|
|
||||||
bottom: 1px; // offset 1 for window frame.
|
|
||||||
height: 36px;
|
height: 36px;
|
||||||
width: calc(100% - 2px);
|
|
||||||
border-top: 1px solid $grey_l;
|
border-top: 1px solid $grey_l;
|
||||||
background: white;
|
background: white;
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,8 @@ button.back {
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
|
|
||||||
.hamburger {
|
.hamburger {
|
||||||
width: 36px;
|
width: $header-height;
|
||||||
|
height: $header-height;
|
||||||
background: url('/images/menu.png') no-repeat center;
|
background: url('/images/menu.png') no-repeat center;
|
||||||
}
|
}
|
||||||
.menu-list {
|
.menu-list {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
padding: $header-height 0 0;
|
padding: $header-height 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conversation-stack,
|
||||||
.new-conversation, .inbox, .gutter {
|
.new-conversation, .inbox, .gutter {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +15,11 @@
|
||||||
// TODO: spinner
|
// TODO: spinner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gutter {
|
||||||
|
float: left;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
.socket-status {
|
.socket-status {
|
||||||
float: left;
|
float: left;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
@ -40,6 +46,31 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conversation-stack {
|
||||||
|
padding-left: 300px;
|
||||||
|
padding-top: $header-height;
|
||||||
|
.conversation {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.conversation:first-child {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation-header {
|
||||||
|
background: $grey_l;
|
||||||
|
}
|
||||||
|
.menu.conversation-menu {
|
||||||
|
float: right;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 0;
|
||||||
|
|
||||||
|
.menu-list {
|
||||||
|
right: 0;
|
||||||
|
left: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.contact {
|
.contact {
|
||||||
.number, .checkbox {
|
.number, .checkbox {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -56,7 +87,7 @@ input.search {
|
||||||
.fab {
|
.fab {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 25px;;
|
left: 215px;;
|
||||||
bottom: 22px;
|
bottom: 22px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
|
@ -99,7 +130,7 @@ input.search {
|
||||||
color: $grey_d;
|
color: $grey_d;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
|
|
||||||
.new-group-update-form {
|
.gutter .new-group-update-form {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +141,7 @@ input.search {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timestamp {
|
.gutter .timestamp {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 14px;
|
top: 14px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
|
|
|
@ -90,6 +90,7 @@ button.back {
|
||||||
padding-right: 8px; }
|
padding-right: 8px; }
|
||||||
.menu .hamburger {
|
.menu .hamburger {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
background: url("/images/menu.png") no-repeat center; }
|
background: url("/images/menu.png") no-repeat center; }
|
||||||
.menu .menu-list {
|
.menu .menu-list {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -330,6 +331,7 @@ img.emoji {
|
||||||
.gutter {
|
.gutter {
|
||||||
padding: 36px 0 0; }
|
padding: 36px 0 0; }
|
||||||
|
|
||||||
|
.conversation-stack,
|
||||||
.new-conversation, .inbox, .gutter {
|
.new-conversation, .inbox, .gutter {
|
||||||
height: 100%; }
|
height: 100%; }
|
||||||
|
|
||||||
|
@ -337,6 +339,10 @@ img.emoji {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto; }
|
overflow: auto; }
|
||||||
|
|
||||||
|
.gutter {
|
||||||
|
float: left;
|
||||||
|
max-width: 300px; }
|
||||||
|
|
||||||
.socket-status {
|
.socket-status {
|
||||||
float: left;
|
float: left;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
@ -355,6 +361,25 @@ img.emoji {
|
||||||
.socket-status .closed {
|
.socket-status .closed {
|
||||||
background: url("/images/error_red.png") no-repeat left center; }
|
background: url("/images/error_red.png") no-repeat left center; }
|
||||||
|
|
||||||
|
.conversation-stack {
|
||||||
|
padding-left: 300px;
|
||||||
|
padding-top: 36px; }
|
||||||
|
.conversation-stack .conversation {
|
||||||
|
display: none; }
|
||||||
|
.conversation-stack .conversation:first-child {
|
||||||
|
display: block; }
|
||||||
|
|
||||||
|
.conversation-header {
|
||||||
|
background: #f3f3f3; }
|
||||||
|
|
||||||
|
.menu.conversation-menu {
|
||||||
|
float: right;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 0; }
|
||||||
|
.menu.conversation-menu .menu-list {
|
||||||
|
right: 0;
|
||||||
|
left: initial; }
|
||||||
|
|
||||||
.contact .number, .contact .checkbox {
|
.contact .number, .contact .checkbox {
|
||||||
display: none; }
|
display: none; }
|
||||||
|
|
||||||
|
@ -367,7 +392,7 @@ input.search {
|
||||||
.fab {
|
.fab {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 25px;
|
left: 215px;
|
||||||
bottom: 22px;
|
bottom: 22px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
|
@ -401,14 +426,14 @@ input.search {
|
||||||
.index {
|
.index {
|
||||||
color: #454545;
|
color: #454545;
|
||||||
background: #eee; }
|
background: #eee; }
|
||||||
.index .new-group-update-form {
|
.index .gutter .new-group-update-form {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 0.5em; }
|
padding: 0.5em; }
|
||||||
.index .last-message {
|
.index .last-message {
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
font-weight: 300; }
|
font-weight: 300; }
|
||||||
.index .timestamp {
|
.index .gutter .timestamp {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 14px;
|
top: 14px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
|
@ -679,10 +704,7 @@ input.search {
|
||||||
cursor: pointer; }
|
cursor: pointer; }
|
||||||
|
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
position: fixed;
|
|
||||||
bottom: 1px;
|
|
||||||
height: 36px;
|
height: 36px;
|
||||||
width: calc(100% - 2px);
|
|
||||||
border-top: 1px solid #f3f3f3;
|
border-top: 1px solid #f3f3f3;
|
||||||
background: white; }
|
background: white; }
|
||||||
.bottom-bar button, .bottom-bar input, .bottom-bar textarea {
|
.bottom-bar button, .bottom-bar input, .bottom-bar textarea {
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue