[WO-281] implement reply all and forward

This commit is contained in:
Tankred Hase 2014-04-02 19:47:50 +02:00
parent f3c2180dfb
commit 302fc378fb
10 changed files with 137 additions and 22 deletions

View File

@ -87,7 +87,7 @@ define(function(require) {
}; };
self.onConnect = function(callback) { self.onConnect = function(callback) {
if (!self.isOnline() || !self._emailDao._account) { if (!self.isOnline() || !self._emailDao || !self._emailDao._account) {
// prevent connection infinite loop // prevent connection infinite loop
callback(); callback();
return; return;

View File

@ -285,6 +285,9 @@ define(function(require) {
this.to = [{ this.to = [{
address: 'max.musterman@gmail.com' address: 'max.musterman@gmail.com'
}]; // list of receivers }]; // list of receivers
this.cc = [{
address: 'john.doe@gmail.com'
}]; // list of receivers
if (attachments) { if (attachments) {
// body structure with three attachments // body structure with three attachments
this.bodystructure = { this.bodystructure = {

View File

@ -164,6 +164,45 @@ define(function(require) {
var ngModule = angular.module('read', []); var ngModule = angular.module('read', []);
ngModule.directive('replySelection', function() {
return function(scope, elm) {
var popover = angular.element(document.querySelector('.reply-selection')),
visible = false;
elm.on('touchstart click', function(e) {
e.preventDefault();
e.stopPropagation();
visible = true;
// set popover position
var top = elm[0].offsetTop;
var left = elm[0].offsetLeft;
var width = elm[0].offsetWidth;
var height = elm[0].offsetHeight;
popover[0].style.transition = 'opacity 0.1s linear';
popover[0].style.top = (top + height) + 'px';
popover[0].style.left = (left + width / 2 - popover[0].offsetWidth / 2) + 'px';
popover[0].style.opacity = '1';
});
elm.parent().parent().on('touchstart click', function(e) {
e.preventDefault();
e.stopPropagation();
if (!visible) {
return;
}
popover[0].style.transition = 'opacity 0.25s linear, top 0.25s step-end, left 0.25s step-end';
popover[0].style.opacity = '0';
popover[0].style.top = '-9999px';
popover[0].style.left = '-9999px';
});
};
});
ngModule.directive('frameLoad', function() { ngModule.directive('frameLoad', function() {
return function(scope, elm) { return function(scope, elm) {
elm.bind('load', function() { elm.bind('load', function() {

View File

@ -26,14 +26,14 @@ define(function(require) {
$scope.state.writer = { $scope.state.writer = {
open: false, open: false,
write: function(replyTo) { write: function(replyTo, replyAll, forward) {
this.open = true; this.open = true;
$scope.replyTo = replyTo; $scope.replyTo = replyTo;
resetFields(); resetFields();
// fill fields depending on replyTo // fill fields depending on replyTo
fillFields(replyTo); fillFields(replyTo, replyAll, forward);
$scope.updatePreview(); $scope.updatePreview();
$scope.verify($scope.to[0]); $scope.verify($scope.to[0]);
@ -60,24 +60,67 @@ define(function(require) {
$scope.attachments = []; $scope.attachments = [];
} }
function fillFields(re) { function fillFields(re, replyAll, forward) {
var from, body; var from, sentDate, body;
if (!re) { if (!re) {
return; return;
} }
$scope.writerTitle = 'Reply'; $scope.writerTitle = (forward) ? 'Forward' : 'Reply';
// fill recipient field // fill recipient field
$scope.to.unshift({ if (!forward) {
address: re.from[0].address $scope.to.unshift({
}); address: re.from[0].address
});
$scope.to.forEach($scope.verify);
}
if (replyAll) {
re.to.concat(re.cc).forEach(function(recipient) {
if (recipient.address === emailDao._account.emailAddress) {
// don't reply to yourself
return;
}
$scope.cc.unshift({
address: recipient.address
});
});
$scope.cc.forEach($scope.verify);
}
// fill subject // fill subject
$scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : ''); if (forward) {
$scope.subject = 'Fwd: ' + re.subject;
} else {
$scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : '');
}
// fill text body // fill text body
from = re.from[0].name || re.from[0].address; from = re.from[0].name || re.from[0].address;
body = '\n\n' + $filter('date')(re.sentDate, 'EEEE, MMM d, yyyy h:mm a') + ' ' + from + ' wrote:\n> '; sentDate = $filter('date')(re.sentDate, 'EEEE, MMM d, yyyy h:mm a');
function createString(array) {
var str = '';
array.forEach(function(to) {
str += (str) ? ', ' : '';
str += ((to.name) ? to.name : to.address) + ' <' + to.address + '>';
});
return str;
}
if (forward) {
body = '\n\n' +
'---------- Forwarded message ----------\n' +
'From: ' + re.from[0].name + ' <' + re.from[0].address + '>\n' +
'Date: ' + sentDate + '\n' +
'Subject: ' + re.subject + '\n' +
'To: ' + createString(re.to) + '\n' +
'Cc: ' + createString(re.cc) + '\n\n\n';
} else {
body = '\n\n' + sentDate + ' ' + from + ' wrote:\n> ';
}
// only display non html mails in reply part // only display non html mails in reply part
if (!re.html) { if (!re.html) {

View File

@ -44,7 +44,7 @@ textarea {
height: 100%; height: 100%;
// allow text selection // allow text selection
-webkit-user-select: text; user-select: none;
// make scrollbars invisible // make scrollbars invisible
::-webkit-scrollbar { ::-webkit-scrollbar {

View File

@ -13,10 +13,7 @@
text-align: left; text-align: left;
background-color: #ffffff; background-color: #ffffff;
background-clip: padding-box; background-clip: padding-box;
border: 1px solid #cccccc; border: 1px solid $color-grey-lighter;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
white-space: normal; white-space: normal;
opacity: 0; opacity: 0;

View File

@ -65,7 +65,7 @@
&:hover, &:hover,
&:focus { &:focus {
background-color: darken($color-white, 3%); background-color: darken($color-white, 2%);
cursor: pointer; cursor: pointer;
} }
} }
@ -84,6 +84,7 @@
line-height: 1.5em; line-height: 1.5em;
height: 100%; height: 100%;
overflow-y: scroll; overflow-y: scroll;
user-select: text;
.working { .working {
margin: 0 auto; margin: 0 auto;
@ -138,6 +139,24 @@
iframe { iframe {
width: 100%; width: 100%;
} }
.reply-selection {
.popover-content {
padding: 0;
}
.option {
padding: 7px 30px;
color: $color-blue;
user-select: none;
&:hover,
&:focus {
background-color: darken($color-white, 2%);
cursor: pointer;
}
}
}
} }
.controls { .controls {

View File

@ -1,6 +1,6 @@
<div class="controls"> <div class="controls">
<button ng-click="state.mailList.remove(state.mailList.selected)" class="btn-icon" title="Delete mail">&#xe005;</button> <button ng-click="state.mailList.remove(state.mailList.selected)" class="btn-icon" title="Delete mail">&#xe005;</button>
<button ng-click="state.writer.write(state.mailList.selected)" class="btn-icon" title="Reply to">&#xe002;</button> <button class="btn-icon" title="Reply to" reply-selection>&#xe002;</button>
<button ng-click="state.writer.write()" class="btn-icon" title="New mail">&#xe006;</button> <button ng-click="state.writer.write()" class="btn-icon" title="New mail">&#xe006;</button>
</div><!--/.controls--> </div><!--/.controls-->
@ -59,4 +59,15 @@
<div class="arrow"></div> <div class="arrow"></div>
<div class="popover-content">{{keyId}}</div> <div class="popover-content">{{keyId}}</div>
</div><!--/.popover--> </div><!--/.popover-->
<div class="reply-selection popover bottom">
<div class="arrow"></div>
<div class="popover-content">
<div class="option" ng-click="state.writer.write(state.mailList.selected)">Reply</div>
<div class="seperator-line"></div>
<div class="option" ng-click="state.writer.write(state.mailList.selected, true)">Reply All</div>
<div class="seperator-line"></div>
<div class="option" ng-click="state.writer.write(state.mailList.selected, null, true)">Forward</div>
</div>
</div><!--/.reply-selection-->
</div><!--/.view-read--> </div><!--/.view-read-->

View File

@ -10,16 +10,15 @@ define(function(require) {
appController = require('js/app-controller'); appController = require('js/app-controller');
describe('Navigation Controller unit test', function() { describe('Navigation Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, outboxBoMock, hasIdentity, outboxFolder; var scope, ctrl, origEmailDao, emailDaoMock, outboxBoMock, hasIdentity, outboxFolder, onConnectStub;
beforeEach(function() { beforeEach(function(done) {
hasIdentity = !! window.chrome.identity; hasIdentity = !! window.chrome.identity;
if (!hasIdentity) { if (!hasIdentity) {
window.chrome.identity = {}; window.chrome.identity = {};
} }
// remember original module to restore later // remember original module to restore later
origEmailDao = appController._emailDao; origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO); emailDaoMock = sinon.createStubInstance(EmailDAO);
emailDaoMock._account = { emailDaoMock._account = {
folders: [{ folders: [{
@ -37,6 +36,8 @@ define(function(require) {
outboxBoMock = sinon.createStubInstance(OutboxBO); outboxBoMock = sinon.createStubInstance(OutboxBO);
appController._outboxBo = outboxBoMock; appController._outboxBo = outboxBoMock;
outboxBoMock.startChecking.returns(); outboxBoMock.startChecking.returns();
onConnectStub = sinon.stub(appController, 'onConnect');
onConnectStub.yields();
angular.module('navigationtest', []); angular.module('navigationtest', []);
mocks.module('navigationtest'); mocks.module('navigationtest');
@ -46,6 +47,7 @@ define(function(require) {
ctrl = $controller(NavigationCtrl, { ctrl = $controller(NavigationCtrl, {
$scope: scope $scope: scope
}); });
done();
}); });
}); });
@ -55,6 +57,7 @@ define(function(require) {
if (hasIdentity) { if (hasIdentity) {
delete window.chrome.identity; delete window.chrome.identity;
} }
onConnectStub.restore();
}); });
describe('initial state', function() { describe('initial state', function() {

View File

@ -119,7 +119,7 @@ define(function(require) {
expect(scope.subject).to.equal('Re: ' + subject); expect(scope.subject).to.equal('Re: ' + subject);
expect(scope.body).to.contain(body); expect(scope.body).to.contain(body);
expect(scope.ciphertextPreview).to.not.be.empty; expect(scope.ciphertextPreview).to.not.be.empty;
expect(verifyMock.calledOnce).to.be.true; expect(verifyMock.called).to.be.true;
scope.verify.restore(); scope.verify.restore();
}); });