From 9d68b6475c3388e055ccc3ca71d3889d1048e3d6 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 17 Feb 2015 13:04:45 +0100 Subject: [PATCH] Use iframe-resizer * dynamically resize iframe based on content * scroll on complete read-view instead of just iframe-content * open writer when email address is clicked in iframe * convert tabs to spaces in read-directive file * Scale html mails to viewport only from the outside * Delete release branch before each release in aws_release.sh script * Make read-controls in read view always visible --- Gruntfile.js | 2 + package.json | 3 +- res/aws_release.sh | 4 +- src/js/controller/app/read-sandbox.js | 54 +++--- src/js/controller/app/write.js | 2 +- src/js/directive/read.js | 241 +++++++++++++++----------- src/js/util/dummy.js | 2 +- src/sass/blocks/views/_read.scss | 70 ++++---- src/sass/read-sandbox.scss | 13 -- src/tpl/create-account.html | 2 +- src/tpl/read.html | 198 ++++++++++----------- 11 files changed, 314 insertions(+), 277 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 459d9d3..bc0aac7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -265,6 +265,7 @@ module.exports = function(grunt) { 'src/lib/angular/angular-animate.js', 'src/lib/ngtagsinput/ng-tags-input.min.js', 'node_modules/ng-infinite-scroll/build/ng-infinite-scroll.min.js', + 'node_modules/iframe-resizer/js/iframeResizer.min.js', 'src/lib/fastclick/fastclick.js', 'src/lib/lawnchair/lawnchair-git.js', 'src/lib/lawnchair/lawnchair-adapter-webkit-sqlite-git.js', @@ -283,6 +284,7 @@ module.exports = function(grunt) { readSandbox: { src: [ 'node_modules/dompurify/purify.js', + 'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js', 'src/js/controller/app/read-sandbox.js' ], dest: 'dist/js/read-sandbox.min.js' diff --git a/package.json b/package.json index 7285942..901e889 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "grunt-string-replace": "~1.0.0", "grunt-svgmin": "~1.0.0", "grunt-svgstore": "~0.3.4", + "iframe-resizer": "^2.8.3", "imap-client": "~0.11.0", "jquery": "~2.1.1", "mailreader": "~0.4.0", @@ -74,4 +75,4 @@ "time-grunt": "^1.0.0", "wo-smtpclient": "~0.6.0" } -} \ No newline at end of file +} diff --git a/res/aws_release.sh b/res/aws_release.sh index cb92824..41d9a92 100755 --- a/res/aws_release.sh +++ b/res/aws_release.sh @@ -14,8 +14,8 @@ fi # switch branch git checkout $2 -git branch release/$1 -git checkout release/$1 +git branch -D release/$1 +git checkout -b release/$1 git merge $2 --no-edit # build and test diff --git a/src/js/controller/app/read-sandbox.js b/src/js/controller/app/read-sandbox.js index 20e4fd3..54c5d32 100644 --- a/src/js/controller/app/read-sandbox.js +++ b/src/js/controller/app/read-sandbox.js @@ -6,7 +6,7 @@ window.onmessage = function(e) { if (e.data.html) { // display html mail body - html = '
' + e.data.html + '
'; + html = e.data.html; } else if (e.data.text) { // diplay text mail body by with colored conversation nodes html = renderNodes(parseConversation(e.data.text)); @@ -26,10 +26,34 @@ window.onmessage = function(e) { document.body.innerHTML = html; - scaleToFit(); + attachClickHandlers(); }; -window.addEventListener('resize', scaleToFit); +/** + * Send a message to the main window when email address is clicked + */ +function attachClickHandlers() { + var elements = document.getElementsByTagName('a'); + for (var i = 0, len = elements.length; i < len; i++) { + elements[i].onclick = handle; + } + + function handle(e) { + var text = e.target.textContent || e.target.innerText; + if (checkEmailAddress(text)) { + e.preventDefault(); + window.parentIFrame.sendMessage({ + type: 'email', + address: text + }); + } + } + + function checkEmailAddress(text) { + var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(text); + } +} /** * Parse email body and generate conversation nodes @@ -188,28 +212,4 @@ function renderNodes(root) { } return '
' + body + '
'; -} - -/** - * Transform scale content to fit iframe width - */ -function scaleToFit() { - var view = document.getElementsByClassName('scale-body').item(0); - if (!view) { - return; - } - - var parentWidth = view.parentNode.offsetWidth; - var w = view.offsetWidth; - var scale = ''; - - if (w > parentWidth) { - scale = parentWidth / w; - scale = 'scale(' + scale + ',' + scale + ')'; - } - - view.style['-webkit-transform-origin'] = '0 0'; - view.style.transformOrigin = '0 0'; - view.style['-webkit-transform'] = scale; - view.style.transform = scale; } \ No newline at end of file diff --git a/src/js/controller/app/write.js b/src/js/controller/app/write.js index 6b9dec1..6ab5b86 100644 --- a/src/js/controller/app/write.js +++ b/src/js/controller/app/write.js @@ -158,7 +158,7 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain if (forward) { $scope.subject = 'Fwd: ' + re.subject; } else { - $scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : ''); + $scope.subject = re.subject ? 'Re: ' + re.subject.replace('Re: ', '') : ''; } // fill text body diff --git a/src/js/directive/read.js b/src/js/directive/read.js index 32dffeb..e86fd7d 100644 --- a/src/js/directive/read.js +++ b/src/js/directive/read.js @@ -3,131 +3,166 @@ var ngModule = angular.module('woDirectives'); ngModule.directive('replySelection', function() { - return function(scope, elm) { - var popover, visible; + return function(scope, elm) { + var popover, visible; - popover = angular.element(document.querySelector('.reply-selection')); - visible = false; + popover = angular.element(document.querySelector('.reply-selection')); + visible = false; - elm.on('touchstart click', appear); - elm.parent().parent().on('touchstart click', disappear); - popover.on('touchstart click', disappear); + elm.on('touchstart click', appear); + elm.parent().parent().on('touchstart click', disappear); + popover.on('touchstart click', disappear); - function appear(e) { - e.preventDefault(); - e.stopPropagation(); + function appear(e) { + e.preventDefault(); + e.stopPropagation(); - visible = true; + 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; + // 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'; - } + 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'; + } - function disappear() { - if (!visible) { - return; - } + function disappear() { + 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'; - visible = false; - } - }; + 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'; + visible = false; + } + }; }); -ngModule.directive('frameLoad', function($timeout, $window) { - return function(scope, elm) { - var iframe = elm[0]; +ngModule.directive('frameLoad', function($window) { + return function(scope, elm) { + var iframe = elm[0]; - scope.$watch('state.read.open', function(open) { - if (open) { - // trigger rendering of iframe - // otherwise scale to fit would not compute correct dimensions on mobile - displayText(scope.state.mailList.selected ? scope.state.mailList.selected.body : undefined); - displayHtml(scope.state.mailList.selected ? scope.state.mailList.selected.html : undefined); - } - }); + scope.$watch('state.read.open', function(open) { + if (open) { + // trigger rendering of iframe + // otherwise scale to fit would not compute correct dimensions on mobile + displayText(scope.state.mailList.selected ? scope.state.mailList.selected.body : undefined); + displayHtml(scope.state.mailList.selected ? scope.state.mailList.selected.html : undefined); + } + }); - $window.addEventListener('resize', scaleToFit); + scope.$on('$destroy', function() { + $window.removeEventListener('resize', resetWidth); + $window.removeEventListener('orientationchange', resetWidth); + }); - iframe.onload = function() { - // set listeners - scope.$watch('state.mailList.selected.body', displayText); - scope.$watch('state.mailList.selected.html', displayHtml); - // display initial message body - scope.$apply(); - }; + $window.addEventListener('resize', resetWidth); + $window.addEventListener('orientationchange', resetWidth); - function displayText(body) { - var mail = scope.state.mailList.selected; - if ((mail && mail.html) || (mail && mail.encrypted && !mail.decrypted)) { - return; - } + // use iframe-resizer to dynamically adapt iframe size to its content + elm.iFrameResize({ + enablePublicMethods: true, + sizeWidth: true, + resizedCallback: scaleToFit, + messageCallback: function(e) { + if (e.message.type === 'email') { + scope.state.writer.write({ + from: [{ + address: e.message.address + }] + }); + } + } + }); - // send text body for rendering in iframe - iframe.contentWindow.postMessage({ - text: body - }, '*'); + iframe.onload = function() { + // set listeners + scope.$watch('state.mailList.selected.body', displayText); + scope.$watch('state.mailList.selected.html', displayHtml); + // display initial message body + scope.$apply(); + }; - $timeout(scaleToFit, 0); - } + function displayText(body) { + var mail = scope.state.mailList.selected; + if ((mail && mail.html) || (mail && mail.encrypted && !mail.decrypted)) { + return; + } - function displayHtml(html) { - if (!html) { - return; - } + resetWidth(); - // if there are image tags in the html? - var hasImages = /]+\bsrc=['"][^'">]+['"]/ig.test(html); - scope.showImageButton = hasImages; + // send text body for rendering in iframe + iframe.contentWindow.postMessage({ + text: body + }, '*'); + } - iframe.contentWindow.postMessage({ - html: html, - removeImages: hasImages // avoids doing unnecessary work on the html - }, '*'); + function displayHtml(html) { + if (!html) { + return; + } - // only add a scope function to reload the html if there are images - if (hasImages) { - // reload WITH images - scope.displayImages = function() { - scope.showImageButton = false; - iframe.contentWindow.postMessage({ - html: html, - removeImages: false - }, '*'); - }; - } + resetWidth(); - $timeout(scaleToFit, 0); - } + // if there are image tags in the html? + var hasImages = /]+\bsrc=['"][^'">]+['"]/ig.test(html); + scope.showImageButton = hasImages; - // transform scale iframe (necessary on iOS) to fit container width - function scaleToFit() { - var parentWidth = elm.parent().width(); - var w = elm.width(); - var scale = ''; + iframe.contentWindow.postMessage({ + html: html, + removeImages: hasImages // avoids doing unnecessary work on the html + }, '*'); - if (w > parentWidth) { - scale = parentWidth / w; - scale = 'scale(' + scale + ',' + scale + ')'; - } + // only add a scope function to reload the html if there are images + if (hasImages) { + // reload WITH images + scope.displayImages = function() { + scope.showImageButton = false; + iframe.contentWindow.postMessage({ + html: html, + removeImages: false + }, '*'); + }; + } + } - elm.css({ - '-webkit-transform-origin': '0 0', - 'transform-origin': '0 0', - '-webkit-transform': scale, - 'transform': scale - }); - } - }; + // reset the iframe width to the original (min-width:100%) + // usually required before a new scaleToFit event + function resetWidth() { + elm.css('width', ''); + } + + // transform scale iframe to fit container width + // necessary if iframe is wider than container + function scaleToFit() { + var parentWidth = elm.parent().width(); + var w = elm.width(); + var scale = 'none'; + + // only scale html mails + var mail = scope.state.mailList.selected; + if (mail && mail.html && (w > parentWidth)) { + scale = parentWidth / w; + scale = 'scale(' + scale + ',' + scale + ')'; + } + + elm.css({ + '-webkit-transform-origin': '0 0', + '-moz-transform-origin': '0 0', + '-ms-transform-origin': '0 0', + 'transform-origin': '0 0', + '-webkit-transform': scale, + '-moz-transform': scale, + '-ms-transform': scale, + 'transform': scale + }); + } + }; }); \ No newline at end of file diff --git a/src/js/util/dummy.js b/src/js/util/dummy.js index 2c9ee89..fb61d76 100644 --- a/src/js/util/dummy.js +++ b/src/js/util/dummy.js @@ -101,7 +101,7 @@ Dummy.prototype.listMails = function() { '>> from 0.7.0.1\n' + '>>\n' + '>> God speed!'; // plaintext body - //this.html = '

Hello there' + Math.random() + '

'; + this.html = '

Hello there' + Math.random() + '

'; this.encrypted = true; this.decrypted = true; }; diff --git a/src/sass/blocks/views/_read.scss b/src/sass/blocks/views/_read.scss index 4a5eb9d..9d50498 100644 --- a/src/sass/blocks/views/_read.scss +++ b/src/sass/blocks/views/_read.scss @@ -23,11 +23,32 @@ display: none; } } + &__working { + position: relative; + flex-grow: 1; + padding: 0 $padding-horizontal; + & > div { + @include scut-vcenter-tt; + width: 100%; + text-align: center; + font-size: $font-size-bigger; + + strong { + color: $color-text-light; + vertical-align: middle; + } + } + } + &__content { + flex-grow: 1; + overflow: auto; + // allow scrolling on iOS + -webkit-overflow-scrolling: touch; + } // Header components &__header { - flex-shrink: 0; margin-bottom: 1em; padding: $padding-vertical $padding-horizontal 0; @@ -41,15 +62,28 @@ } &__controls { display: none; - float: right; - margin-left: 1em; - .btn-icon-light { + position: absolute; + top: 0; + right: $scrollbar-width; // don't cover scrollbar + padding: $padding-vertical $padding-horizontal; + background-color: $color-white; + .btn-icon-light + .btn-icon-light { margin-left: 1.4em; } @include respond-to(md) { display: block; } } + &__controls__dummy { + display: none; + float: right; + // the size of the real controls + width: 242px; + height: 39px; + @include respond-to(md) { + display: block; + } + } &__subject { font-weight: normal; color: $color-text; @@ -103,7 +137,6 @@ // Content components &__signature-status { - flex-shrink: 0; margin-top: 0; margin-bottom: 0.5em; text-align: center; @@ -111,40 +144,17 @@ padding: 0 $padding-horizontal; } &__display-images { - flex-shrink: 0; margin-bottom: 0.5em; text-align: center; padding: 0 $padding-horizontal; } - &__working { - position: relative; - flex-grow: 1; - padding: 0 $padding-horizontal; - & > div { - @include scut-vcenter-tt; - width: 100%; - text-align: center; - font-size: $font-size-bigger; - - strong { - color: $color-text-light; - vertical-align: middle; - } - } - } &__body { - flex-grow: 1; - display: flex; - flex-direction: column; - // allow scrolling on iOS - overflow: auto; - -webkit-overflow-scrolling: touch; padding: 0 $padding-horizontal $padding-vertical; + overflow: hidden; // necessary due to iframe scaling via transitions iframe { - flex-grow: 1; border: none; - width: 100%; + min-width: 100%; } } diff --git a/src/sass/read-sandbox.scss b/src/sass/read-sandbox.scss index 7ff282b..522c2f3 100644 --- a/src/sass/read-sandbox.scss +++ b/src/sass/read-sandbox.scss @@ -5,24 +5,11 @@ // Mixins @import "mixins/responsive"; -@import "mixins/scrollbar"; - -@include scrollbar(); - -html { - // use overflow auto and not scroll otherwise IE shows scrollbars always - overflow: auto; -} body { margin: 0; } -.scale-body { - // necessary to compute overflowing content width in JS - float: left; -} - .view-read-body { font-family: $font-family-base; font-size: $font-size-base; diff --git a/src/tpl/create-account.html b/src/tpl/create-account.html index cc5fb7e..7a7c0b3 100644 --- a/src/tpl/create-account.html +++ b/src/tpl/create-account.html @@ -5,7 +5,7 @@

Create Whiteout account

-

Sign up for an encrypted mailbox hosted in Germany.
Already have an account? Log in here.

+

Sign up for an encrypted mailbox hosted in Germany. Already have an account? Log in here.

{{errMsg}}

diff --git a/src/tpl/read.html b/src/tpl/read.html index ca5461e..603d33a 100644 --- a/src/tpl/read.html +++ b/src/tpl/read.html @@ -13,90 +13,32 @@
-
-
- - - - - - - - - - - - - -
- -

- - + + + + - {{state.mailList.selected.subject ? state.mailList.selected.subject : 'No subject'}} -

-

- Back - {{state.mailList.selected.subject ? state.mailList.selected.subject : 'No subject'}} -

- - -
-
- - - - {{u.name || u.address}} - - - - -
-
- - - - {{u.name || u.address}} - - - -
-
- - - - {{u.name || u.address}} - - - -
-
- -
    -
  • - - - {{attachment.filename}} -
  • -
-
+ + + + + +
- + -

- Invalid PGP signature. This message could have been tampered with. -

- -
- -
- - -
- -
+
+
+ +

+ + + + {{state.mailList.selected.subject ? state.mailList.selected.subject : 'No subject'}} +

+

+ Back + {{state.mailList.selected.subject ? state.mailList.selected.subject : 'No subject'}} +

+ + +
+
+ + + + {{u.name || u.address}} + + + + +
+
+ + + + {{u.name || u.address}} + + + +
+
+ + + + {{u.name || u.address}} + + + +
+
+ +
    +
  • + + + {{attachment.filename}} +
  • +
+
+ +

+ Invalid PGP signature. This message could have been tampered with. +

+ +
+ +
+ +
+ + +
+
@@ -179,4 +181,4 @@ -
+
\ No newline at end of file