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:
parent
c48484e04f
commit
3901bcb8df
7 changed files with 100 additions and 90 deletions
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'>
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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"; // ·
|
||||||
|
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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue