From 302fc378fb444d0e456ea5d61a128b56bf113110 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 2 Apr 2014 19:47:50 +0200 Subject: [PATCH] [WO-281] implement reply all and forward --- src/js/app-controller.js | 2 +- src/js/controller/mail-list.js | 3 ++ src/js/controller/read.js | 39 +++++++++++++++++ src/js/controller/write.js | 63 ++++++++++++++++++++++----- src/sass/_scaffolding.scss | 2 +- src/sass/components/_popover.scss | 5 +-- src/sass/views/_read.scss | 21 ++++++++- src/tpl/read.html | 13 +++++- test/new-unit/navigation-ctrl-test.js | 9 ++-- test/new-unit/write-ctrl-test.js | 2 +- 10 files changed, 137 insertions(+), 22 deletions(-) diff --git a/src/js/app-controller.js b/src/js/app-controller.js index 6066c61..f30cd10 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -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; diff --git a/src/js/controller/mail-list.js b/src/js/controller/mail-list.js index a8f1c18..e2a18fe 100644 --- a/src/js/controller/mail-list.js +++ b/src/js/controller/mail-list.js @@ -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 = { diff --git a/src/js/controller/read.js b/src/js/controller/read.js index d6d872d..abf85f6 100644 --- a/src/js/controller/read.js +++ b/src/js/controller/read.js @@ -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() { diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 9b526d5..4cade92 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -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 - $scope.to.unshift({ - address: re.from[0].address - }); + 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 - $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 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) { diff --git a/src/sass/_scaffolding.scss b/src/sass/_scaffolding.scss index 0c8a6cf..fcc60c3 100755 --- a/src/sass/_scaffolding.scss +++ b/src/sass/_scaffolding.scss @@ -44,7 +44,7 @@ textarea { height: 100%; // allow text selection - -webkit-user-select: text; + user-select: none; // make scrollbars invisible ::-webkit-scrollbar { diff --git a/src/sass/components/_popover.scss b/src/sass/components/_popover.scss index 3ad8d73..233b2f4 100644 --- a/src/sass/components/_popover.scss +++ b/src/sass/components/_popover.scss @@ -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; diff --git a/src/sass/views/_read.scss b/src/sass/views/_read.scss index cd1a456..7d9f64b 100644 --- a/src/sass/views/_read.scss +++ b/src/sass/views/_read.scss @@ -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 { diff --git a/src/tpl/read.html b/src/tpl/read.html index 2269267..316b66c 100644 --- a/src/tpl/read.html +++ b/src/tpl/read.html @@ -1,6 +1,6 @@
- +
@@ -59,4 +59,15 @@
{{keyId}}
+ +
+
+
+
Reply
+
+
Reply All
+
+
Forward
+
+
diff --git a/test/new-unit/navigation-ctrl-test.js b/test/new-unit/navigation-ctrl-test.js index 78917f6..6c6b1fd 100644 --- a/test/new-unit/navigation-ctrl-test.js +++ b/test/new-unit/navigation-ctrl-test.js @@ -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() { diff --git a/test/new-unit/write-ctrl-test.js b/test/new-unit/write-ctrl-test.js index 25f2d92..3690de6 100644 --- a/test/new-unit/write-ctrl-test.js +++ b/test/new-unit/write-ctrl-test.js @@ -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(); });