1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-22 17:02:17 -05:00

[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) {
if (!self.isOnline() || !self._emailDao._account) {
if (!self.isOnline() || !self._emailDao || !self._emailDao._account) {
// prevent connection infinite loop
callback();
return;

View File

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

View File

@ -164,6 +164,45 @@ define(function(require) {
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() {
return function(scope, elm) {
elm.bind('load', function() {

View File

@ -26,14 +26,14 @@ define(function(require) {
$scope.state.writer = {
open: false,
write: function(replyTo) {
write: function(replyTo, replyAll, forward) {
this.open = true;
$scope.replyTo = replyTo;
resetFields();
// fill fields depending on replyTo
fillFields(replyTo);
fillFields(replyTo, replyAll, forward);
$scope.updatePreview();
$scope.verify($scope.to[0]);
@ -60,24 +60,67 @@ define(function(require) {
$scope.attachments = [];
}
function fillFields(re) {
var from, body;
function fillFields(re, replyAll, forward) {
var from, sentDate, body;
if (!re) {
return;
}
$scope.writerTitle = 'Reply';
$scope.writerTitle = (forward) ? 'Forward' : 'Reply';
// fill recipient field
if (!forward) {
$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
if (forward) {
$scope.subject = 'Fwd: ' + re.subject;
} else {
$scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : '');
}
// fill text body
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
if (!re.html) {

View File

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

View File

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

View File

@ -65,7 +65,7 @@
&:hover,
&:focus {
background-color: darken($color-white, 3%);
background-color: darken($color-white, 2%);
cursor: pointer;
}
}
@ -84,6 +84,7 @@
line-height: 1.5em;
height: 100%;
overflow-y: scroll;
user-select: text;
.working {
margin: 0 auto;
@ -138,6 +139,24 @@
iframe {
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 {

View File

@ -1,6 +1,6 @@
<div class="controls">
<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>
</div><!--/.controls-->
@ -59,4 +59,15 @@
<div class="arrow"></div>
<div class="popover-content">{{keyId}}</div>
</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-->

View File

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

View File

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