Animated loading screens on startup and first conversation load
FREEBIE
This commit is contained in:
parent
3e8b34f3d0
commit
53f2bfbb57
15 changed files with 444 additions and 79 deletions
|
@ -2,6 +2,26 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
|
<script type='text/x-tmpl-mustache' id='app-loading-screen'>
|
||||||
|
<div class='content'>
|
||||||
|
<img src='/images/icon_128.png'>
|
||||||
|
<div class='container'>
|
||||||
|
<span class='dot'></span>
|
||||||
|
<span class='dot'></span>
|
||||||
|
<span class='dot'></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<script type='text/x-tmpl-mustache' id='conversation-loading-screen'>
|
||||||
|
<div class='content'>
|
||||||
|
<img src='/images/icon_128.png'>
|
||||||
|
<div class='container'>
|
||||||
|
<span class='dot'></span>
|
||||||
|
<span class='dot'></span>
|
||||||
|
<span class='dot'></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
<script type='text/x-tmpl-mustache' id='two-column'>
|
<script type='text/x-tmpl-mustache' id='two-column'>
|
||||||
<div class='gutter'>
|
<div class='gutter'>
|
||||||
<div class='network-status-container'></div>
|
<div class='network-status-container'></div>
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
// Close and reopen existing windows
|
// Close and reopen existing windows
|
||||||
var open = false;
|
var open = false;
|
||||||
|
var initialLoadComplete = false;
|
||||||
extension.windows.getAll().forEach(function(appWindow) {
|
extension.windows.getAll().forEach(function(appWindow) {
|
||||||
open = true;
|
open = true;
|
||||||
appWindow.close();
|
appWindow.close();
|
||||||
|
@ -121,7 +122,7 @@
|
||||||
messageReceiver.addEventListener('read', onReadReceipt);
|
messageReceiver.addEventListener('read', onReadReceipt);
|
||||||
messageReceiver.addEventListener('verified', onVerified);
|
messageReceiver.addEventListener('verified', onVerified);
|
||||||
messageReceiver.addEventListener('error', onError);
|
messageReceiver.addEventListener('error', onError);
|
||||||
|
messageReceiver.addEventListener('empty', onEmpty);
|
||||||
|
|
||||||
window.textsecure.messaging = new textsecure.MessageSender(
|
window.textsecure.messaging = new textsecure.MessageSender(
|
||||||
SERVER_URL, SERVER_PORTS, USERNAME, PASSWORD
|
SERVER_URL, SERVER_PORTS, USERNAME, PASSWORD
|
||||||
|
@ -145,6 +146,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onEmpty() {
|
||||||
|
initialLoadComplete = true;
|
||||||
|
|
||||||
|
var interval = setInterval(function() {
|
||||||
|
var view = window.owsDesktopApp.inboxView;
|
||||||
|
if (view) {
|
||||||
|
clearInterval(interval);
|
||||||
|
interval = null;
|
||||||
|
view.onEmpty();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
function onContactReceived(ev) {
|
function onContactReceived(ev) {
|
||||||
var details = ev.contactDetails;
|
var details = ev.contactDetails;
|
||||||
|
|
||||||
|
@ -196,14 +210,16 @@
|
||||||
var data = ev.data;
|
var data = ev.data;
|
||||||
var message = initIncomingMessage(data);
|
var message = initIncomingMessage(data);
|
||||||
|
|
||||||
isMessageDuplicate(message).then(function(isDuplicate) {
|
return isMessageDuplicate(message).then(function(isDuplicate) {
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
console.log('Received duplicate message', message.idForLogging());
|
console.log('Received duplicate message', message.idForLogging());
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
message.handleDataMessage(data.message, ev.confirm);
|
return message.handleDataMessage(data.message, ev.confirm, {
|
||||||
|
initialLoadComplete: initialLoadComplete
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,14 +238,16 @@
|
||||||
expirationStartTimestamp: data.expirationStartTimestamp,
|
expirationStartTimestamp: data.expirationStartTimestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
isMessageDuplicate(message).then(function(isDuplicate) {
|
return isMessageDuplicate(message).then(function(isDuplicate) {
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
console.log('Received duplicate message', message.idForLogging());
|
console.log('Received duplicate message', message.idForLogging());
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
message.handleDataMessage(data.message, ev.confirm);
|
return message.handleDataMessage(data.message, ev.confirm, {
|
||||||
|
initialLoadComplete: initialLoadComplete
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,9 +330,9 @@
|
||||||
var envelope = ev.proto;
|
var envelope = ev.proto;
|
||||||
var message = initIncomingMessage(envelope.source, envelope.timestamp.toNumber());
|
var message = initIncomingMessage(envelope.source, envelope.timestamp.toNumber());
|
||||||
|
|
||||||
message.saveErrors(e).then(function() {
|
return message.saveErrors(e).then(function() {
|
||||||
var id = message.get('conversationId');
|
var id = message.get('conversationId');
|
||||||
ConversationController.findOrCreateById(id, 'private').then(function(conversation) {
|
return ConversationController.findOrCreateById(id, 'private').then(function(conversation) {
|
||||||
conversation.set({
|
conversation.set({
|
||||||
active_at: Date.now(),
|
active_at: Date.now(),
|
||||||
unreadCount: conversation.get('unreadCount') + 1
|
unreadCount: conversation.get('unreadCount') + 1
|
||||||
|
@ -327,10 +345,11 @@
|
||||||
}
|
}
|
||||||
conversation.save();
|
conversation.save();
|
||||||
conversation.trigger('newmessage', message);
|
conversation.trigger('newmessage', message);
|
||||||
conversation.notify(message);
|
if (initialLoadComplete) {
|
||||||
|
conversation.notify(message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -341,11 +360,13 @@
|
||||||
var timestamp = ev.read.timestamp;
|
var timestamp = ev.read.timestamp;
|
||||||
var sender = ev.read.sender;
|
var sender = ev.read.sender;
|
||||||
console.log('read receipt', sender, timestamp);
|
console.log('read receipt', sender, timestamp);
|
||||||
|
|
||||||
var receipt = Whisper.ReadReceipts.add({
|
var receipt = Whisper.ReadReceipts.add({
|
||||||
sender : sender,
|
sender : sender,
|
||||||
timestamp : timestamp,
|
timestamp : timestamp,
|
||||||
read_at : read_at
|
read_at : read_at
|
||||||
});
|
});
|
||||||
|
|
||||||
receipt.on('remove', ev.confirm);
|
receipt.on('remove', ev.confirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,12 +432,12 @@
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
source: pushMessage.source
|
source: pushMessage.source
|
||||||
});
|
});
|
||||||
|
|
||||||
receipt.on('remove', ev.confirm);
|
receipt.on('remove', ev.confirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.owsDesktopApp = {
|
window.owsDesktopApp = {
|
||||||
getAppView: function(destWindow) {
|
getAppView: function(destWindow) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
return ConversationController.updateInbox().then(function() {
|
return ConversationController.updateInbox().then(function() {
|
||||||
|
|
|
@ -37484,13 +37484,16 @@ window.textsecure.utils = function() {
|
||||||
this.listeners = {};
|
this.listeners = {};
|
||||||
}
|
}
|
||||||
var listeners = this.listeners[ev.type];
|
var listeners = this.listeners[ev.type];
|
||||||
|
var results = [];
|
||||||
if (typeof listeners === 'object') {
|
if (typeof listeners === 'object') {
|
||||||
for (var i=0; i < listeners.length; ++i) {
|
for (var i = 0, max = listeners.length; i < max; i += 1) {
|
||||||
if (typeof listeners[i] === 'function') {
|
var listener = listeners[i];
|
||||||
listeners[i].call(null, ev);
|
if (typeof listener === 'function') {
|
||||||
|
results.push(listener.call(null, ev));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return results;
|
||||||
},
|
},
|
||||||
addEventListener: function(eventName, callback) {
|
addEventListener: function(eventName, callback) {
|
||||||
if (typeof eventName !== 'string') {
|
if (typeof eventName !== 'string') {
|
||||||
|
@ -38284,22 +38287,25 @@ MessageReceiver.prototype.extend({
|
||||||
onerror: function(error) {
|
onerror: function(error) {
|
||||||
console.log('websocket error');
|
console.log('websocket error');
|
||||||
},
|
},
|
||||||
|
dispatchAndWait: function(event) {
|
||||||
|
return Promise.all(this.dispatchEvent(event));
|
||||||
|
},
|
||||||
onclose: function(ev) {
|
onclose: function(ev) {
|
||||||
console.log('websocket closed', ev.code, ev.reason || '');
|
console.log('websocket closed', ev.code, ev.reason || '');
|
||||||
if (ev.code === 3000) {
|
if (ev.code === 3000) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var eventTarget = this;
|
|
||||||
// possible 403 or network issue. Make an request to confirm
|
// possible 403 or network issue. Make an request to confirm
|
||||||
this.server.getDevices(this.number).
|
this.server.getDevices(this.number)
|
||||||
then(this.connect.bind(this)). // No HTTP error? Reconnect
|
.then(this.connect.bind(this)) // No HTTP error? Reconnect
|
||||||
catch(function(e) {
|
.catch(function(e) {
|
||||||
var ev = new Event('error');
|
var ev = new Event('error');
|
||||||
ev.error = e;
|
ev.error = e;
|
||||||
eventTarget.dispatchEvent(ev);
|
this.dispatchAndWait(ev);
|
||||||
});
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleRequest: function(request) {
|
handleRequest: function(request) {
|
||||||
|
this.incoming = this.incoming || [];
|
||||||
// We do the message decryption here, instead of in the ordered pending queue,
|
// We do the message decryption here, instead of in the ordered pending queue,
|
||||||
// to avoid exposing the time it took us to process messages through the time-to-ack.
|
// to avoid exposing the time it took us to process messages through the time-to-ack.
|
||||||
|
|
||||||
|
@ -38307,10 +38313,14 @@ MessageReceiver.prototype.extend({
|
||||||
if (request.path !== '/api/v1/message') {
|
if (request.path !== '/api/v1/message') {
|
||||||
console.log('got request', request.verb, request.path);
|
console.log('got request', request.verb, request.path);
|
||||||
request.respond(200, 'OK');
|
request.respond(200, 'OK');
|
||||||
|
|
||||||
|
if (request.verb === 'PUT' && request.path === '/api/v1/queue/empty') {
|
||||||
|
this.onEmpty();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
textsecure.crypto.decryptWebsocketMessage(request.body, this.signalingKey).then(function(plaintext) {
|
this.incoming.push(textsecure.crypto.decryptWebsocketMessage(request.body, this.signalingKey).then(function(plaintext) {
|
||||||
var envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
var envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
||||||
// After this point, decoding errors are not the server's
|
// After this point, decoding errors are not the server's
|
||||||
// fault, and we should handle them gracefully and tell the
|
// fault, and we should handle them gracefully and tell the
|
||||||
|
@ -38320,7 +38330,7 @@ MessageReceiver.prototype.extend({
|
||||||
return request.respond(200, 'OK');
|
return request.respond(200, 'OK');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addToCache(envelope, plaintext).then(function() {
|
return this.addToCache(envelope, plaintext).then(function() {
|
||||||
request.respond(200, 'OK');
|
request.respond(200, 'OK');
|
||||||
this.queueEnvelope(envelope);
|
this.queueEnvelope(envelope);
|
||||||
}.bind(this), function(error) {
|
}.bind(this), function(error) {
|
||||||
|
@ -38334,8 +38344,23 @@ MessageReceiver.prototype.extend({
|
||||||
console.log("Error handling incoming message:", e && e.stack ? e.stack : e);
|
console.log("Error handling incoming message:", e && e.stack ? e.stack : e);
|
||||||
var ev = new Event('error');
|
var ev = new Event('error');
|
||||||
ev.error = e;
|
ev.error = e;
|
||||||
this.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this));
|
}.bind(this)));
|
||||||
|
},
|
||||||
|
onEmpty: function() {
|
||||||
|
var incoming = this.incoming;
|
||||||
|
this.incoming = [];
|
||||||
|
|
||||||
|
var dispatchEmpty = function() {
|
||||||
|
var ev = new Event('empty');
|
||||||
|
return this.dispatchAndWait(ev);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
var scheduleDispatch = function() {
|
||||||
|
this.pending = this.pending.then(dispatchEmpty, dispatchEmpty);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
Promise.all(incoming).then(scheduleDispatch, scheduleDispatch);
|
||||||
},
|
},
|
||||||
queueAllCached: function() {
|
queueAllCached: function() {
|
||||||
this.getAllFromCache().then(function(items) {
|
this.getAllFromCache().then(function(items) {
|
||||||
|
@ -38484,12 +38509,11 @@ MessageReceiver.prototype.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDeliveryReceipt: function (envelope) {
|
onDeliveryReceipt: function (envelope) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve, reject) {
|
||||||
var ev = new Event('receipt');
|
var ev = new Event('receipt');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.proto = envelope;
|
ev.proto = envelope;
|
||||||
this.dispatchEvent(ev);
|
this.dispatchAndWait(ev).then(resolve, reject);
|
||||||
return resolve();
|
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
unpad: function(paddedPlaintext) {
|
unpad: function(paddedPlaintext) {
|
||||||
|
@ -38548,8 +38572,11 @@ MessageReceiver.prototype.extend({
|
||||||
var ev = new Event('error');
|
var ev = new Event('error');
|
||||||
ev.error = error;
|
ev.error = error;
|
||||||
ev.proto = envelope;
|
ev.proto = envelope;
|
||||||
this.dispatchEvent(ev);
|
|
||||||
return Promise.reject(error);
|
var returnError = function() {
|
||||||
|
return Promise.reject(error);
|
||||||
|
};
|
||||||
|
this.dispatchAndWait(ev).then(returnError, returnError);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
decryptPreKeyWhisperMessage: function(ciphertext, sessionCipher, address) {
|
decryptPreKeyWhisperMessage: function(ciphertext, sessionCipher, address) {
|
||||||
|
@ -38586,7 +38613,7 @@ MessageReceiver.prototype.extend({
|
||||||
if (expirationStartTimestamp) {
|
if (expirationStartTimestamp) {
|
||||||
ev.data.expirationStartTimestamp = expirationStartTimestamp.toNumber();
|
ev.data.expirationStartTimestamp = expirationStartTimestamp.toNumber();
|
||||||
}
|
}
|
||||||
this.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -38608,7 +38635,7 @@ MessageReceiver.prototype.extend({
|
||||||
timestamp : envelope.timestamp.toNumber(),
|
timestamp : envelope.timestamp.toNumber(),
|
||||||
message : message
|
message : message
|
||||||
};
|
};
|
||||||
this.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -38696,9 +38723,10 @@ MessageReceiver.prototype.extend({
|
||||||
identityKey: verified.identityKey.toArrayBuffer()
|
identityKey: verified.identityKey.toArrayBuffer()
|
||||||
};
|
};
|
||||||
ev.viaContactSync = options.viaContactSync;
|
ev.viaContactSync = options.viaContactSync;
|
||||||
this.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
},
|
},
|
||||||
handleRead: function(envelope, read) {
|
handleRead: function(envelope, read) {
|
||||||
|
var results = [];
|
||||||
for (var i = 0; i < read.length; ++i) {
|
for (var i = 0; i < read.length; ++i) {
|
||||||
var ev = new Event('read');
|
var ev = new Event('read');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
|
@ -38707,24 +38735,30 @@ MessageReceiver.prototype.extend({
|
||||||
timestamp : read[i].timestamp.toNumber(),
|
timestamp : read[i].timestamp.toNumber(),
|
||||||
sender : read[i].sender
|
sender : read[i].sender
|
||||||
}
|
}
|
||||||
this.dispatchEvent(ev);
|
results.push(this.dispatchAndWait(ev));
|
||||||
}
|
}
|
||||||
|
return Promise.all(results);
|
||||||
},
|
},
|
||||||
handleContacts: function(envelope, contacts) {
|
handleContacts: function(envelope, contacts) {
|
||||||
console.log('contact sync');
|
console.log('contact sync');
|
||||||
var eventTarget = this;
|
var eventTarget = this;
|
||||||
var attachmentPointer = contacts.blob;
|
var attachmentPointer = contacts.blob;
|
||||||
return this.handleAttachment(attachmentPointer).then(function() {
|
return this.handleAttachment(attachmentPointer).then(function() {
|
||||||
|
var results = [];
|
||||||
var contactBuffer = new ContactBuffer(attachmentPointer.data);
|
var contactBuffer = new ContactBuffer(attachmentPointer.data);
|
||||||
var contactDetails = contactBuffer.next();
|
var contactDetails = contactBuffer.next();
|
||||||
while (contactDetails !== undefined) {
|
while (contactDetails !== undefined) {
|
||||||
var ev = new Event('contact');
|
var ev = new Event('contact');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.contactDetails = contactDetails;
|
ev.contactDetails = contactDetails;
|
||||||
eventTarget.dispatchEvent(ev);
|
results.push(eventTarget.dispatchAndWait(ev));
|
||||||
|
|
||||||
if (contactDetails.verified) {
|
if (contactDetails.verified) {
|
||||||
this.handleVerified(envelope, contactDetails.verified, {viaContactSync: true});
|
results.push(this.handleVerified(
|
||||||
|
envelope,
|
||||||
|
contactDetails.verified,
|
||||||
|
{viaContactSync: true}
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
contactDetails = contactBuffer.next();
|
contactDetails = contactBuffer.next();
|
||||||
|
@ -38732,12 +38766,13 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
var ev = new Event('contactsync');
|
var ev = new Event('contactsync');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
eventTarget.dispatchEvent(ev);
|
results.push(eventTarget.dispatchAndWait(ev));
|
||||||
|
|
||||||
|
return Promise.all(results);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleGroups: function(envelope, groups) {
|
handleGroups: function(envelope, groups) {
|
||||||
console.log('group sync');
|
console.log('group sync');
|
||||||
var eventTarget = this;
|
|
||||||
var attachmentPointer = groups.blob;
|
var attachmentPointer = groups.blob;
|
||||||
return this.handleAttachment(attachmentPointer).then(function() {
|
return this.handleAttachment(attachmentPointer).then(function() {
|
||||||
var groupBuffer = new GroupBuffer(attachmentPointer.data);
|
var groupBuffer = new GroupBuffer(attachmentPointer.data);
|
||||||
|
@ -38766,7 +38801,7 @@ MessageReceiver.prototype.extend({
|
||||||
var ev = new Event('group');
|
var ev = new Event('group');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.groupDetails = groupDetails;
|
ev.groupDetails = groupDetails;
|
||||||
eventTarget.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this)).catch(function(e) {
|
}.bind(this)).catch(function(e) {
|
||||||
console.log('error processing group', e);
|
console.log('error processing group', e);
|
||||||
});
|
});
|
||||||
|
@ -38777,7 +38812,7 @@ MessageReceiver.prototype.extend({
|
||||||
Promise.all(promises).then(function() {
|
Promise.all(promises).then(function() {
|
||||||
var ev = new Event('groupsync');
|
var ev = new Event('groupsync');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
eventTarget.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -38805,9 +38840,9 @@ MessageReceiver.prototype.extend({
|
||||||
attachment.data = data;
|
attachment.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.server.getAttachment(attachment.id).
|
return this.server.getAttachment(attachment.id)
|
||||||
then(decryptAttachment).
|
.then(decryptAttachment)
|
||||||
then(updateAttachment);
|
.then(updateAttachment);
|
||||||
},
|
},
|
||||||
tryMessageAgain: function(from, ciphertext) {
|
tryMessageAgain: function(from, ciphertext) {
|
||||||
var address = libsignal.SignalProtocolAddress.fromString(from);
|
var address = libsignal.SignalProtocolAddress.fromString(from);
|
||||||
|
|
|
@ -344,7 +344,7 @@
|
||||||
|
|
||||||
// Lastly, we don't send read receipts for any message marked read due to a read
|
// Lastly, we don't send read receipts for any message marked read due to a read
|
||||||
// receipt. That's a notification explosion we don't need.
|
// receipt. That's a notification explosion we don't need.
|
||||||
this.queueJob(function() {
|
return this.queueJob(function() {
|
||||||
return this.markRead(message.get('received_at'), {sendReadReceipts: false});
|
return this.markRead(message.get('received_at'), {sendReadReceipts: false});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
|
@ -342,7 +342,10 @@
|
||||||
this.send(promise);
|
this.send(promise);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleDataMessage: function(dataMessage, confirm) {
|
handleDataMessage: function(dataMessage, confirm, options) {
|
||||||
|
options = options || {};
|
||||||
|
_.defaults(options, {initialLoadComplete: true});
|
||||||
|
|
||||||
// This function can be called from the background script on an
|
// This function can be called from the background script on an
|
||||||
// incoming message or from the frontend after the user accepts an
|
// incoming message or from the frontend after the user accepts an
|
||||||
// identity key change.
|
// identity key change.
|
||||||
|
@ -357,7 +360,7 @@
|
||||||
console.log('queuing handleDataMessage', message.idForLogging());
|
console.log('queuing handleDataMessage', message.idForLogging());
|
||||||
|
|
||||||
var conversation = ConversationController.create({id: conversationId});
|
var conversation = ConversationController.create({id: conversationId});
|
||||||
conversation.queueJob(function() {
|
return conversation.queueJob(function() {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
conversation.fetch().always(function() {
|
conversation.fetch().always(function() {
|
||||||
console.log('starting handleDataMessage', message.idForLogging());
|
console.log('starting handleDataMessage', message.idForLogging());
|
||||||
|
@ -500,7 +503,7 @@
|
||||||
// because we need to start expiration timers, etc.
|
// because we need to start expiration timers, etc.
|
||||||
message.markRead();
|
message.markRead();
|
||||||
}
|
}
|
||||||
if (message.get('unread')) {
|
if (message.get('unread') && options.initialLoadComplete) {
|
||||||
conversation.notify(message);
|
conversation.notify(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,14 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Whisper.ConversationLoadingScreen = Whisper.View.extend({
|
||||||
|
templateName: 'conversation-loading-screen',
|
||||||
|
className: 'conversation-loading-screen',
|
||||||
|
render_attributes: {
|
||||||
|
loading: i18n('loading')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Whisper.ConversationTitleView = Whisper.View.extend({
|
Whisper.ConversationTitleView = Whisper.View.extend({
|
||||||
templateName: 'conversation-title',
|
templateName: 'conversation-title',
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
|
@ -116,6 +124,11 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
|
this.loadingScreen = new Whisper.ConversationLoadingScreen();
|
||||||
|
this.loadingScreen.render();
|
||||||
|
this.loadingScreen.$el.prependTo(this.el);
|
||||||
|
|
||||||
new TimerMenuView({ el: this.$('.timer-menu'), model: this.model });
|
new TimerMenuView({ el: this.$('.timer-menu'), model: this.model });
|
||||||
|
|
||||||
emoji_util.parse(this.$('.conversation-name'));
|
emoji_util.parse(this.$('.conversation-name'));
|
||||||
|
@ -321,9 +334,15 @@
|
||||||
setTimeout(this.markRead.bind(this), 1);
|
setTimeout(this.markRead.bind(this), 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onLoaded: function () {
|
||||||
|
var view = this.loadingScreen;
|
||||||
|
if (view) {
|
||||||
|
this.loadingScreen = null;
|
||||||
|
view.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onOpened: function() {
|
onOpened: function() {
|
||||||
// TODO: we may want to show a loading dialog until this status fetch
|
|
||||||
// and potentially the below message fetch are complete.
|
|
||||||
this.statusFetch = this.throttledGetProfiles().then(function() {
|
this.statusFetch = this.throttledGetProfiles().then(function() {
|
||||||
this.model.updateVerified().then(function() {
|
this.model.updateVerified().then(function() {
|
||||||
this.onVerifiedChange();
|
this.onVerifiedChange();
|
||||||
|
@ -332,6 +351,9 @@
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
|
Promise.all([this.statusFetch, this.inProgressFetch])
|
||||||
|
.then(this.onLoaded.bind(this), this.onLoaded.bind(this));
|
||||||
|
|
||||||
this.view.resetScrollPosition();
|
this.view.resetScrollPosition();
|
||||||
this.$el.trigger('force-resize');
|
this.$el.trigger('force-resize');
|
||||||
this.focusMessageField();
|
this.focusMessageField();
|
||||||
|
|
|
@ -60,6 +60,15 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Whisper.AppLoadingScreen = Whisper.View.extend({
|
||||||
|
templateName: 'app-loading-screen',
|
||||||
|
className: 'app-loading-screen',
|
||||||
|
render_attributes: {
|
||||||
|
loading: i18n('loading')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Whisper.InboxView = Whisper.View.extend({
|
Whisper.InboxView = Whisper.View.extend({
|
||||||
templateName: 'two-column',
|
templateName: 'two-column',
|
||||||
className: 'inbox',
|
className: 'inbox',
|
||||||
|
@ -71,6 +80,7 @@
|
||||||
.addClass(theme);
|
.addClass(theme);
|
||||||
},
|
},
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
|
this.ready = false;
|
||||||
this.render();
|
this.render();
|
||||||
this.applyTheme();
|
this.applyTheme();
|
||||||
this.$el.attr('tabindex', '1');
|
this.$el.attr('tabindex', '1');
|
||||||
|
@ -80,6 +90,10 @@
|
||||||
model: { window: options.window }
|
model: { window: options.window }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.appLoadingScreen = new Whisper.AppLoadingScreen();
|
||||||
|
this.appLoadingScreen.render();
|
||||||
|
this.appLoadingScreen.$el.prependTo(this.el);
|
||||||
|
|
||||||
var inboxCollection = getInboxCollection();
|
var inboxCollection = getInboxCollection();
|
||||||
|
|
||||||
inboxCollection.on('messageError', function() {
|
inboxCollection.on('messageError', function() {
|
||||||
|
@ -146,6 +160,13 @@
|
||||||
'click .restart-signal': 'reloadBackgroundPage',
|
'click .restart-signal': 'reloadBackgroundPage',
|
||||||
'show .lightbox': 'showLightbox'
|
'show .lightbox': 'showLightbox'
|
||||||
},
|
},
|
||||||
|
onEmpty: function() {
|
||||||
|
var view = this.appLoadingScreen;
|
||||||
|
if (view) {
|
||||||
|
this.appLoadingScreen = null;
|
||||||
|
view.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
focusConversation: function(e) {
|
focusConversation: function(e) {
|
||||||
if (e && this.$(e.target).closest('.placeholder').length) {
|
if (e && this.$(e.target).closest('.placeholder').length) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -23,13 +23,16 @@
|
||||||
this.listeners = {};
|
this.listeners = {};
|
||||||
}
|
}
|
||||||
var listeners = this.listeners[ev.type];
|
var listeners = this.listeners[ev.type];
|
||||||
|
var results = [];
|
||||||
if (typeof listeners === 'object') {
|
if (typeof listeners === 'object') {
|
||||||
for (var i=0; i < listeners.length; ++i) {
|
for (var i = 0, max = listeners.length; i < max; i += 1) {
|
||||||
if (typeof listeners[i] === 'function') {
|
var listener = listeners[i];
|
||||||
listeners[i].call(null, ev);
|
if (typeof listener === 'function') {
|
||||||
|
results.push(listener.call(null, ev));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return results;
|
||||||
},
|
},
|
||||||
addEventListener: function(eventName, callback) {
|
addEventListener: function(eventName, callback) {
|
||||||
if (typeof eventName !== 'string') {
|
if (typeof eventName !== 'string') {
|
||||||
|
|
|
@ -45,22 +45,25 @@ MessageReceiver.prototype.extend({
|
||||||
onerror: function(error) {
|
onerror: function(error) {
|
||||||
console.log('websocket error');
|
console.log('websocket error');
|
||||||
},
|
},
|
||||||
|
dispatchAndWait: function(event) {
|
||||||
|
return Promise.all(this.dispatchEvent(event));
|
||||||
|
},
|
||||||
onclose: function(ev) {
|
onclose: function(ev) {
|
||||||
console.log('websocket closed', ev.code, ev.reason || '');
|
console.log('websocket closed', ev.code, ev.reason || '');
|
||||||
if (ev.code === 3000) {
|
if (ev.code === 3000) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var eventTarget = this;
|
|
||||||
// possible 403 or network issue. Make an request to confirm
|
// possible 403 or network issue. Make an request to confirm
|
||||||
this.server.getDevices(this.number).
|
this.server.getDevices(this.number)
|
||||||
then(this.connect.bind(this)). // No HTTP error? Reconnect
|
.then(this.connect.bind(this)) // No HTTP error? Reconnect
|
||||||
catch(function(e) {
|
.catch(function(e) {
|
||||||
var ev = new Event('error');
|
var ev = new Event('error');
|
||||||
ev.error = e;
|
ev.error = e;
|
||||||
eventTarget.dispatchEvent(ev);
|
this.dispatchAndWait(ev);
|
||||||
});
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleRequest: function(request) {
|
handleRequest: function(request) {
|
||||||
|
this.incoming = this.incoming || [];
|
||||||
// We do the message decryption here, instead of in the ordered pending queue,
|
// We do the message decryption here, instead of in the ordered pending queue,
|
||||||
// to avoid exposing the time it took us to process messages through the time-to-ack.
|
// to avoid exposing the time it took us to process messages through the time-to-ack.
|
||||||
|
|
||||||
|
@ -68,10 +71,14 @@ MessageReceiver.prototype.extend({
|
||||||
if (request.path !== '/api/v1/message') {
|
if (request.path !== '/api/v1/message') {
|
||||||
console.log('got request', request.verb, request.path);
|
console.log('got request', request.verb, request.path);
|
||||||
request.respond(200, 'OK');
|
request.respond(200, 'OK');
|
||||||
|
|
||||||
|
if (request.verb === 'PUT' && request.path === '/api/v1/queue/empty') {
|
||||||
|
this.onEmpty();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
textsecure.crypto.decryptWebsocketMessage(request.body, this.signalingKey).then(function(plaintext) {
|
this.incoming.push(textsecure.crypto.decryptWebsocketMessage(request.body, this.signalingKey).then(function(plaintext) {
|
||||||
var envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
var envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
||||||
// After this point, decoding errors are not the server's
|
// After this point, decoding errors are not the server's
|
||||||
// fault, and we should handle them gracefully and tell the
|
// fault, and we should handle them gracefully and tell the
|
||||||
|
@ -81,7 +88,7 @@ MessageReceiver.prototype.extend({
|
||||||
return request.respond(200, 'OK');
|
return request.respond(200, 'OK');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addToCache(envelope, plaintext).then(function() {
|
return this.addToCache(envelope, plaintext).then(function() {
|
||||||
request.respond(200, 'OK');
|
request.respond(200, 'OK');
|
||||||
this.queueEnvelope(envelope);
|
this.queueEnvelope(envelope);
|
||||||
}.bind(this), function(error) {
|
}.bind(this), function(error) {
|
||||||
|
@ -95,8 +102,23 @@ MessageReceiver.prototype.extend({
|
||||||
console.log("Error handling incoming message:", e && e.stack ? e.stack : e);
|
console.log("Error handling incoming message:", e && e.stack ? e.stack : e);
|
||||||
var ev = new Event('error');
|
var ev = new Event('error');
|
||||||
ev.error = e;
|
ev.error = e;
|
||||||
this.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this));
|
}.bind(this)));
|
||||||
|
},
|
||||||
|
onEmpty: function() {
|
||||||
|
var incoming = this.incoming;
|
||||||
|
this.incoming = [];
|
||||||
|
|
||||||
|
var dispatchEmpty = function() {
|
||||||
|
var ev = new Event('empty');
|
||||||
|
return this.dispatchAndWait(ev);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
var scheduleDispatch = function() {
|
||||||
|
this.pending = this.pending.then(dispatchEmpty, dispatchEmpty);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
Promise.all(incoming).then(scheduleDispatch, scheduleDispatch);
|
||||||
},
|
},
|
||||||
queueAllCached: function() {
|
queueAllCached: function() {
|
||||||
this.getAllFromCache().then(function(items) {
|
this.getAllFromCache().then(function(items) {
|
||||||
|
@ -245,12 +267,11 @@ MessageReceiver.prototype.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDeliveryReceipt: function (envelope) {
|
onDeliveryReceipt: function (envelope) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve, reject) {
|
||||||
var ev = new Event('receipt');
|
var ev = new Event('receipt');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.proto = envelope;
|
ev.proto = envelope;
|
||||||
this.dispatchEvent(ev);
|
this.dispatchAndWait(ev).then(resolve, reject);
|
||||||
return resolve();
|
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
unpad: function(paddedPlaintext) {
|
unpad: function(paddedPlaintext) {
|
||||||
|
@ -309,8 +330,11 @@ MessageReceiver.prototype.extend({
|
||||||
var ev = new Event('error');
|
var ev = new Event('error');
|
||||||
ev.error = error;
|
ev.error = error;
|
||||||
ev.proto = envelope;
|
ev.proto = envelope;
|
||||||
this.dispatchEvent(ev);
|
|
||||||
return Promise.reject(error);
|
var returnError = function() {
|
||||||
|
return Promise.reject(error);
|
||||||
|
};
|
||||||
|
this.dispatchAndWait(ev).then(returnError, returnError);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
decryptPreKeyWhisperMessage: function(ciphertext, sessionCipher, address) {
|
decryptPreKeyWhisperMessage: function(ciphertext, sessionCipher, address) {
|
||||||
|
@ -347,7 +371,7 @@ MessageReceiver.prototype.extend({
|
||||||
if (expirationStartTimestamp) {
|
if (expirationStartTimestamp) {
|
||||||
ev.data.expirationStartTimestamp = expirationStartTimestamp.toNumber();
|
ev.data.expirationStartTimestamp = expirationStartTimestamp.toNumber();
|
||||||
}
|
}
|
||||||
this.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -369,7 +393,7 @@ MessageReceiver.prototype.extend({
|
||||||
timestamp : envelope.timestamp.toNumber(),
|
timestamp : envelope.timestamp.toNumber(),
|
||||||
message : message
|
message : message
|
||||||
};
|
};
|
||||||
this.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -457,9 +481,10 @@ MessageReceiver.prototype.extend({
|
||||||
identityKey: verified.identityKey.toArrayBuffer()
|
identityKey: verified.identityKey.toArrayBuffer()
|
||||||
};
|
};
|
||||||
ev.viaContactSync = options.viaContactSync;
|
ev.viaContactSync = options.viaContactSync;
|
||||||
this.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
},
|
},
|
||||||
handleRead: function(envelope, read) {
|
handleRead: function(envelope, read) {
|
||||||
|
var results = [];
|
||||||
for (var i = 0; i < read.length; ++i) {
|
for (var i = 0; i < read.length; ++i) {
|
||||||
var ev = new Event('read');
|
var ev = new Event('read');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
|
@ -468,24 +493,30 @@ MessageReceiver.prototype.extend({
|
||||||
timestamp : read[i].timestamp.toNumber(),
|
timestamp : read[i].timestamp.toNumber(),
|
||||||
sender : read[i].sender
|
sender : read[i].sender
|
||||||
}
|
}
|
||||||
this.dispatchEvent(ev);
|
results.push(this.dispatchAndWait(ev));
|
||||||
}
|
}
|
||||||
|
return Promise.all(results);
|
||||||
},
|
},
|
||||||
handleContacts: function(envelope, contacts) {
|
handleContacts: function(envelope, contacts) {
|
||||||
console.log('contact sync');
|
console.log('contact sync');
|
||||||
var eventTarget = this;
|
var eventTarget = this;
|
||||||
var attachmentPointer = contacts.blob;
|
var attachmentPointer = contacts.blob;
|
||||||
return this.handleAttachment(attachmentPointer).then(function() {
|
return this.handleAttachment(attachmentPointer).then(function() {
|
||||||
|
var results = [];
|
||||||
var contactBuffer = new ContactBuffer(attachmentPointer.data);
|
var contactBuffer = new ContactBuffer(attachmentPointer.data);
|
||||||
var contactDetails = contactBuffer.next();
|
var contactDetails = contactBuffer.next();
|
||||||
while (contactDetails !== undefined) {
|
while (contactDetails !== undefined) {
|
||||||
var ev = new Event('contact');
|
var ev = new Event('contact');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.contactDetails = contactDetails;
|
ev.contactDetails = contactDetails;
|
||||||
eventTarget.dispatchEvent(ev);
|
results.push(eventTarget.dispatchAndWait(ev));
|
||||||
|
|
||||||
if (contactDetails.verified) {
|
if (contactDetails.verified) {
|
||||||
this.handleVerified(envelope, contactDetails.verified, {viaContactSync: true});
|
results.push(this.handleVerified(
|
||||||
|
envelope,
|
||||||
|
contactDetails.verified,
|
||||||
|
{viaContactSync: true}
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
contactDetails = contactBuffer.next();
|
contactDetails = contactBuffer.next();
|
||||||
|
@ -493,12 +524,13 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
var ev = new Event('contactsync');
|
var ev = new Event('contactsync');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
eventTarget.dispatchEvent(ev);
|
results.push(eventTarget.dispatchAndWait(ev));
|
||||||
|
|
||||||
|
return Promise.all(results);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleGroups: function(envelope, groups) {
|
handleGroups: function(envelope, groups) {
|
||||||
console.log('group sync');
|
console.log('group sync');
|
||||||
var eventTarget = this;
|
|
||||||
var attachmentPointer = groups.blob;
|
var attachmentPointer = groups.blob;
|
||||||
return this.handleAttachment(attachmentPointer).then(function() {
|
return this.handleAttachment(attachmentPointer).then(function() {
|
||||||
var groupBuffer = new GroupBuffer(attachmentPointer.data);
|
var groupBuffer = new GroupBuffer(attachmentPointer.data);
|
||||||
|
@ -527,7 +559,7 @@ MessageReceiver.prototype.extend({
|
||||||
var ev = new Event('group');
|
var ev = new Event('group');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.groupDetails = groupDetails;
|
ev.groupDetails = groupDetails;
|
||||||
eventTarget.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this)).catch(function(e) {
|
}.bind(this)).catch(function(e) {
|
||||||
console.log('error processing group', e);
|
console.log('error processing group', e);
|
||||||
});
|
});
|
||||||
|
@ -538,7 +570,7 @@ MessageReceiver.prototype.extend({
|
||||||
Promise.all(promises).then(function() {
|
Promise.all(promises).then(function() {
|
||||||
var ev = new Event('groupsync');
|
var ev = new Event('groupsync');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
eventTarget.dispatchEvent(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
@ -566,9 +598,9 @@ MessageReceiver.prototype.extend({
|
||||||
attachment.data = data;
|
attachment.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.server.getAttachment(attachment.id).
|
return this.server.getAttachment(attachment.id)
|
||||||
then(decryptAttachment).
|
.then(decryptAttachment)
|
||||||
then(updateAttachment);
|
.then(updateAttachment);
|
||||||
},
|
},
|
||||||
tryMessageAgain: function(from, ciphertext) {
|
tryMessageAgain: function(from, ciphertext) {
|
||||||
var address = libsignal.SignalProtocolAddress.fromString(from);
|
var address = libsignal.SignalProtocolAddress.fromString(from);
|
||||||
|
|
|
@ -32,6 +32,51 @@
|
||||||
.conversation {
|
.conversation {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.conversation-loading-screen {
|
||||||
|
z-index: 99;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #eee;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
width: 78px;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 3px solid $blue;
|
||||||
|
border-radius: 50%;
|
||||||
|
float: left;
|
||||||
|
margin: 0 6px;
|
||||||
|
transform: scale(0);
|
||||||
|
|
||||||
|
animation: loading 1500ms ease infinite 0ms;
|
||||||
|
&:nth-child(2) {
|
||||||
|
animation: loading 1500ms ease infinite 333ms;
|
||||||
|
}
|
||||||
|
&:nth-child(3) {
|
||||||
|
animation: loading 1500ms ease infinite 666ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
height: calc(100% - #{$header-height});
|
height: calc(100% - #{$header-height});
|
||||||
|
|
|
@ -539,6 +539,64 @@ input[type=text], input[type=search], textarea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inbox {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
50% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-loading-screen {
|
||||||
|
z-index: 1000;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
width: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 3px solid $blue;
|
||||||
|
border-radius: 50%;
|
||||||
|
float: left;
|
||||||
|
margin: 0 6px;
|
||||||
|
transform: scale(0);
|
||||||
|
|
||||||
|
animation: loading 1500ms ease infinite 0ms;
|
||||||
|
&:nth-child(2) {
|
||||||
|
animation: loading 1500ms ease infinite 333ms;
|
||||||
|
}
|
||||||
|
&:nth-child(3) {
|
||||||
|
animation: loading 1500ms ease infinite 666ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//yellow border fix
|
//yellow border fix
|
||||||
.inbox:focus {
|
.inbox:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
@ -88,6 +88,9 @@ $text-dark_l2: darken($text-dark, 30%);
|
||||||
.conversation.placeholder .conversation-header {
|
.conversation.placeholder .conversation-header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.conversation .conversation-loading-screen {
|
||||||
|
background-color: $grey-dark_l3;
|
||||||
|
}
|
||||||
.avatar, .conversation-header, .bubble {
|
.avatar, .conversation-header, .bubble {
|
||||||
@include dark-avatar-colors;
|
@include dark-avatar-colors;
|
||||||
}
|
}
|
||||||
|
|
|
@ -484,6 +484,49 @@ input[type=text]:active, input[type=text]:focus, input[type=search]:active, inpu
|
||||||
background: #2090ea;
|
background: #2090ea;
|
||||||
margin-left: 20px; }
|
margin-left: 20px; }
|
||||||
|
|
||||||
|
.inbox {
|
||||||
|
position: relative; }
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
50% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1; }
|
||||||
|
100% {
|
||||||
|
opacity: 0; } }
|
||||||
|
.app-loading-screen {
|
||||||
|
z-index: 1000;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center; }
|
||||||
|
.app-loading-screen .content {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%); }
|
||||||
|
.app-loading-screen .container {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
width: 78px; }
|
||||||
|
.app-loading-screen .dot {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 3px solid #2090ea;
|
||||||
|
border-radius: 50%;
|
||||||
|
float: left;
|
||||||
|
margin: 0 6px;
|
||||||
|
transform: scale(0);
|
||||||
|
animation: loading 1500ms ease infinite 0ms; }
|
||||||
|
.app-loading-screen .dot:nth-child(2) {
|
||||||
|
animation: loading 1500ms ease infinite 333ms; }
|
||||||
|
.app-loading-screen .dot:nth-child(3) {
|
||||||
|
animation: loading 1500ms ease infinite 666ms; }
|
||||||
|
|
||||||
.inbox:focus {
|
.inbox:focus {
|
||||||
outline: none; }
|
outline: none; }
|
||||||
|
|
||||||
|
@ -1051,7 +1094,41 @@ input.search {
|
||||||
|
|
||||||
.conversation {
|
.conversation {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
height: 100%; }
|
height: 100%;
|
||||||
|
position: relative; }
|
||||||
|
.conversation .conversation-loading-screen {
|
||||||
|
z-index: 99;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #eee;
|
||||||
|
display: flex;
|
||||||
|
align-items: center; }
|
||||||
|
.conversation .conversation-loading-screen .content {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%); }
|
||||||
|
.conversation .conversation-loading-screen .container {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
width: 78px;
|
||||||
|
transform: translate(-50%, 0); }
|
||||||
|
.conversation .conversation-loading-screen .dot {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 3px solid #2090ea;
|
||||||
|
border-radius: 50%;
|
||||||
|
float: left;
|
||||||
|
margin: 0 6px;
|
||||||
|
transform: scale(0);
|
||||||
|
animation: loading 1500ms ease infinite 0ms; }
|
||||||
|
.conversation .conversation-loading-screen .dot:nth-child(2) {
|
||||||
|
animation: loading 1500ms ease infinite 333ms; }
|
||||||
|
.conversation .conversation-loading-screen .dot:nth-child(3) {
|
||||||
|
animation: loading 1500ms ease infinite 666ms; }
|
||||||
.conversation .panel {
|
.conversation .panel {
|
||||||
height: calc(100% - 64px);
|
height: calc(100% - 64px);
|
||||||
overflow-y: scroll; }
|
overflow-y: scroll; }
|
||||||
|
@ -2099,6 +2176,8 @@ li.entry .error-icon-container {
|
||||||
border-color: #333333; }
|
border-color: #333333; }
|
||||||
.android-dark .conversation.placeholder .conversation-header {
|
.android-dark .conversation.placeholder .conversation-header {
|
||||||
display: none; }
|
display: none; }
|
||||||
|
.android-dark .conversation .conversation-loading-screen {
|
||||||
|
background-color: #171717; }
|
||||||
.android-dark .avatar.red, .android-dark .conversation-header.red, .android-dark .bubble.red {
|
.android-dark .avatar.red, .android-dark .conversation-header.red, .android-dark .bubble.red {
|
||||||
background-color: #D32F2F; }
|
background-color: #D32F2F; }
|
||||||
.android-dark .avatar.pink, .android-dark .conversation-header.pink, .android-dark .bubble.pink {
|
.android-dark .avatar.pink, .android-dark .conversation-header.pink, .android-dark .bubble.pink {
|
||||||
|
|
|
@ -13,14 +13,17 @@ describe("Fixtures", function() {
|
||||||
it('renders', function(done) {
|
it('renders', function(done) {
|
||||||
ConversationController.updateInbox().then(function() {
|
ConversationController.updateInbox().then(function() {
|
||||||
var view = new Whisper.InboxView({window: window});
|
var view = new Whisper.InboxView({window: window});
|
||||||
|
view.onEmpty();
|
||||||
view.$el.prependTo($('#render-android'));
|
view.$el.prependTo($('#render-android'));
|
||||||
|
|
||||||
var view = new Whisper.InboxView({window: window});
|
var view = new Whisper.InboxView({window: window});
|
||||||
view.$el.removeClass('android').addClass('ios');
|
view.$el.removeClass('android').addClass('ios');
|
||||||
|
view.onEmpty();
|
||||||
view.$el.prependTo($('#render-ios'));
|
view.$el.prependTo($('#render-ios'));
|
||||||
|
|
||||||
var view = new Whisper.InboxView({window: window});
|
var view = new Whisper.InboxView({window: window});
|
||||||
view.$el.removeClass('android').addClass('android-dark');
|
view.$el.removeClass('android').addClass('android-dark');
|
||||||
|
view.onEmpty();
|
||||||
view.$el.prependTo($('#render-android-dark'));
|
view.$el.prependTo($('#render-android-dark'));
|
||||||
}).then(done, done);
|
}).then(done, done);
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,6 +16,26 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="render-ios" class='index' style="width: 800; height: 500; margin:10px; border: solid 1px black;">
|
<div id="render-ios" class='index' style="width: 800; height: 500; margin:10px; border: solid 1px black;">
|
||||||
</div>
|
</div>
|
||||||
|
<script type='text/x-tmpl-mustache' id='app-loading-screen'>
|
||||||
|
<div class='content'>
|
||||||
|
<img src='/images/icon_128.png'>
|
||||||
|
<div class='container'>
|
||||||
|
<span class='dot'></span>
|
||||||
|
<span class='dot'></span>
|
||||||
|
<span class='dot'></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<script type='text/x-tmpl-mustache' id='conversation-loading-screen'>
|
||||||
|
<div class='content'>
|
||||||
|
<img src='/images/icon_128.png'>
|
||||||
|
<div class='container'>
|
||||||
|
<span class='dot'></span>
|
||||||
|
<span class='dot'></span>
|
||||||
|
<span class='dot'></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
<script type='text/x-tmpl-mustache' id='two-column'>
|
<script type='text/x-tmpl-mustache' id='two-column'>
|
||||||
<div class='gutter'>
|
<div class='gutter'>
|
||||||
<div class='network-status-container'></div>
|
<div class='network-status-container'></div>
|
||||||
|
|
Loading…
Reference in a new issue