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.