Style resend button as an inline link

For messages that failed to send due to network errors, this change
allows retrying them directly from the main conversation view rather
than only from the message detail view.

// FREEBIE
This commit is contained in:
lilia 2016-03-22 14:47:17 -07:00
parent c48484e04f
commit 3901bcb8df
7 changed files with 100 additions and 90 deletions

View file

@ -238,5 +238,9 @@
"restartSignal": { "restartSignal": {
"message": "Restart Signal", "message": "Restart Signal",
"description": "Menu item for restarting the program." "description": "Menu item for restarting the program."
},
"messageNotSent": {
"message": "Message not sent.",
"description": "Informational label, appears on messages that failed to send"
} }
} }

View file

@ -110,6 +110,10 @@
<img src='{{ source }}' class='preview' /> <img src='{{ source }}' class='preview' />
<div class='close'>x</div> <div class='close'>x</div>
</script> </script>
<script type='text/x-tmpl-mustache' id='hasRetry'>
{{ messageNotSent }}
<span href='#' class='retry'>{{ resend }}</span>
</script>
<script type='text/x-tmpl-mustache' id='message'> <script type='text/x-tmpl-mustache' id='message'>
{{> avatar }} {{> avatar }}
<div class='bubble'> <div class='bubble'>
@ -196,12 +200,6 @@
</script> </script>
<script type='text/x-tmpl-mustache' id='message-detail'> <script type='text/x-tmpl-mustache' id='message-detail'>
<div class='container'> <div class='container'>
{{ #hasRetry }}
<div class='hasRetry clearfix'>
<h3 class='retryMessage'>{{ failedToSend }}</h3>
<button class='retry'>{{ resend }}</button>
</div>
{{ /hasRetry }}
{{ #hasConflict }} {{ #hasConflict }}
<div class='hasConflict clearfix'> <div class='hasConflict clearfix'>
<div class='conflicts'> <div class='conflicts'>

View file

@ -211,6 +211,14 @@
this.set({errors: errors}); this.set({errors: errors});
}, },
hasNetworkError: function(number) {
var error = _.find(this.get('errors'), function(e) {
return (e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError');
});
return !!error;
},
removeOutgoingErrors: function(number) { removeOutgoingErrors: function(number) {
var errors = _.partition(this.get('errors'), function(e) { var errors = _.partition(this.get('errors'), function(e) {
return e.number === number && return e.number === number &&

View file

@ -38,8 +38,7 @@
this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'change', this.render);
}, },
events: { events: {
'click .back': 'goBack', 'click .back': 'goBack'
'click .retry': 'retryMessage',
}, },
goBack: function() { goBack: function() {
this.trigger('back'); this.trigger('back');
@ -65,16 +64,6 @@
return this.conversation.contactCollection.models; return this.conversation.contactCollection.models;
} }
}, },
retryMessage: function() {
var retrys = _.filter(this.model.get('errors'), function(e) {
return (e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError');
});
_.map(retrys, 'number').forEach(function(number) {
this.model.resend(number);
}.bind(this));
},
renderContact: function(contact) { renderContact: function(contact) {
var view = new ContactView({ var view = new ContactView({
model: contact, model: contact,
@ -105,11 +94,6 @@
}, },
render: function() { render: function() {
this.errors = _.groupBy(this.model.get('errors'), 'number'); this.errors = _.groupBy(this.model.get('errors'), 'number');
var hasRetry = _.find(this.model.get('errors'), function(e) {
return (e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError');
});
var unknownErrors = this.errors['undefined']; var unknownErrors = this.errors['undefined'];
if (unknownErrors) { if (unknownErrors) {
unknownErrors = unknownErrors.filter(function(e) { unknownErrors = unknownErrors.filter(function(e) {
@ -124,10 +108,7 @@
title : i18n('messageDetail'), title : i18n('messageDetail'),
sent : i18n('sent'), sent : i18n('sent'),
received : i18n('received'), received : i18n('received'),
resend : i18n('resend'),
failedToSend: i18n('failedToSend'),
errorLabel : i18n('error'), errorLabel : i18n('error'),
hasRetry : hasRetry,
hasConflict : this.model.hasKeyConflicts() hasConflict : this.model.hasKeyConflicts()
})); }));
this.view.$el.prependTo(this.$('.message-container')); this.view.$el.prependTo(this.$('.message-container'));

View file

@ -7,6 +7,16 @@
var URL_REGEX = /(^|[\s\n]|<br\/?>)((?:https?|ftp):\/\/[\-A-Z0-9\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFD+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/gi; var URL_REGEX = /(^|[\s\n]|<br\/?>)((?:https?|ftp):\/\/[\-A-Z0-9\u00A0-\uD7FF\uE000-\uFDCF\uFDF0-\uFFFD+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/gi;
var NetworkErrorView = Whisper.View.extend({
tagName: 'span',
className: 'hasRetry',
templateName: 'hasRetry',
render_attributes: {
messageNotSent: i18n('messageNotSent'),
resend: i18n('resend')
}
});
Whisper.MessageView = Whisper.View.extend({ Whisper.MessageView = Whisper.View.extend({
tagName: 'li', tagName: 'li',
templateName: 'message', templateName: 'message',
@ -22,9 +32,21 @@
this.timeStampView = new Whisper.ExtendedTimestampView(); this.timeStampView = new Whisper.ExtendedTimestampView();
}, },
events: { events: {
'click .meta': 'select', 'click .retry': 'retryMessage',
'click .timestamp': 'select',
'click .status': 'select',
'click .error': 'select' 'click .error': 'select'
}, },
retryMessage: function() {
var retrys = _.filter(this.model.get('errors'), function(e) {
return (e.name === 'MessageError' ||
e.name === 'OutgoingMessageError' ||
e.name === 'SendMessageNetworkError');
});
_.map(retrys, 'number').forEach(function(number) {
this.model.resend(number);
}.bind(this));
},
select: function(e) { select: function(e) {
this.$el.trigger('select', {message: this.model}); this.$el.trigger('select', {message: this.model});
e.stopPropagation(); e.stopPropagation();
@ -63,6 +85,11 @@
} else { } else {
this.$el.removeClass('error'); this.$el.removeClass('error');
} }
if (this.model.hasNetworkError()) {
this.$('.meta').prepend(new NetworkErrorView().render().el);
} else {
this.$('.meta .hasRetry').remove();
}
}, },
renderControl: function() { renderControl: function() {
if (this.model.isEndSession() || this.model.isGroupUpdate()) { if (this.model.isEndSession() || this.model.isGroupUpdate()) {
@ -79,7 +106,7 @@
message: this.model.get('body'), message: this.model.get('body'),
timestamp: this.model.get('sent_at'), timestamp: this.model.get('sent_at'),
sender: (contact && contact.getTitle()) || '', sender: (contact && contact.getTitle()) || '',
avatar: (contact && contact.getAvatar()) avatar: (contact && contact.getAvatar()),
}, this.render_partials()) }, this.render_partials())
); );
this.timeStampView.setElement(this.$('.timestamp')); this.timeStampView.setElement(this.$('.timestamp'));

View file

@ -70,8 +70,7 @@
} }
.message-detail { .message-detail {
.key-conflict-dialogue, .key-conflict-dialogue {
.hasRetry {
background: #F3F3A7; background: #F3F3A7;
border-radius: 5px; border-radius: 5px;
padding: 1em; padding: 1em;
@ -92,24 +91,6 @@
} }
} }
.hasRetry {
padding: 10px 20px;
button {
margin: 5px;
background: $blue;
&:before {
content: '';
display: inline-block;
vertical-align: middle;
width: 18px;
height: 18px;
background: url('/images/refresh.png') no-repeat center center;
background-size: 100%;
}
}
}
.key-conflict-dialogue { .key-conflict-dialogue {
.content p { .content p {
max-width: 40em; max-width: 40em;
@ -247,7 +228,6 @@
} }
.timestamp { .timestamp {
font-size: smaller;
margin-right: 3px; margin-right: 3px;
} }
@ -300,28 +280,43 @@
} }
.meta { .meta {
font-size: smaller;
margin-top: 3px; margin-top: 3px;
float: right; float: right;
cursor: pointer;
.timestamp, .status { .hasRetry + .timestamp {
opacity: 0.5; &:before {
content:"\00b7"; // &middot
font-weight: bold;
padding: 0 5px 0 4px;
text-decoration: none;
opacity: 0.5;
}
} }
&:hover { .retry {
.timestamp, .status { text-decoration: underline;
cursor: pointer;
}
.hasRetry, .timestamp, .status {
float: left;
}
.timestamp, .status {
cursor: pointer;
opacity: 0.5;
&:hover {
opacity: 1.0; opacity: 1.0;
} }
.timestamp {
text-decoration: underline;
}
} }
} }
.status { .status {
float: right;
width: 18px; width: 18px;
height: 1em; height: 14px;
line-height: 1em;
} }
.sent .status { .sent .status {
display: inline-block; display: inline-block;

View file

@ -675,14 +675,12 @@ input.search {
.key-verification .placeholder { .key-verification .placeholder {
font-weight: bold; } font-weight: bold; }
.message-detail .key-conflict-dialogue, .message-detail .key-conflict-dialogue {
.message-detail .hasRetry {
background: #F3F3A7; background: #F3F3A7;
border-radius: 5px; border-radius: 5px;
padding: 1em; padding: 1em;
margin: 1em; } margin: 1em; }
.message-detail .key-conflict-dialogue button, .message-detail .key-conflict-dialogue button {
.message-detail .hasRetry button {
outline: none; outline: none;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
@ -690,22 +688,8 @@ input.search {
padding: 0.5em 1em; padding: 0.5em 1em;
font-weight: bold; font-weight: bold;
line-height: 18px; } line-height: 18px; }
.message-detail .key-conflict-dialogue button span, .message-detail .key-conflict-dialogue button span {
.message-detail .hasRetry button span {
vertical-align: middle; } vertical-align: middle; }
.message-detail .hasRetry {
padding: 10px 20px; }
.message-detail .hasRetry button {
margin: 5px;
background: #2090ea; }
.message-detail .hasRetry button:before {
content: '';
display: inline-block;
vertical-align: middle;
width: 18px;
height: 18px;
background: url("/images/refresh.png") no-repeat center center;
background-size: 100%; }
.message-detail .key-conflict-dialogue .content p { .message-detail .key-conflict-dialogue .content p {
max-width: 40em; max-width: 40em;
margin: 1em auto; } margin: 1em auto; }
@ -807,7 +791,6 @@ input.search {
font-weight: bold; } font-weight: bold; }
.timestamp { .timestamp {
font-size: smaller;
margin-right: 3px; } margin-right: 3px; }
.message-container, .message-container,
@ -853,25 +836,39 @@ input.search {
margin: 0; } margin: 0; }
.message-container .meta, .message-container .meta,
.message-list .meta { .message-list .meta {
font-size: smaller;
margin-top: 3px; margin-top: 3px;
float: right; float: right; }
cursor: pointer; } .message-container .meta .hasRetry + .timestamp:before,
.message-list .meta .hasRetry + .timestamp:before {
content: "\00b7";
font-weight: bold;
padding: 0 5px 0 4px;
text-decoration: none;
opacity: 0.5; }
.message-container .meta .retry,
.message-list .meta .retry {
text-decoration: underline;
cursor: pointer; }
.message-container .meta .hasRetry, .message-container .meta .timestamp, .message-container .meta .status,
.message-list .meta .hasRetry,
.message-list .meta .timestamp,
.message-list .meta .status {
float: left; }
.message-container .meta .timestamp, .message-container .meta .status, .message-container .meta .timestamp, .message-container .meta .status,
.message-list .meta .timestamp, .message-list .meta .timestamp,
.message-list .meta .status { .message-list .meta .status {
cursor: pointer;
opacity: 0.5; } opacity: 0.5; }
.message-container .meta:hover .timestamp, .message-container .meta:hover .status, .message-container .meta .timestamp:hover, .message-container .meta .status:hover,
.message-list .meta:hover .timestamp, .message-list .meta .timestamp:hover,
.message-list .meta:hover .status { .message-list .meta .status:hover {
opacity: 1.0; } opacity: 1.0; }
.message-container .meta:hover .timestamp,
.message-list .meta:hover .timestamp {
text-decoration: underline; }
.message-container .status, .message-container .status,
.message-list .status { .message-list .status {
float: right;
width: 18px; width: 18px;
height: 1em; } height: 14px;
line-height: 1em; }
.message-container .sent .status, .message-container .sent .status,
.message-list .sent .status { .message-list .sent .status {
display: inline-block; display: inline-block;