Merge pull request #298 from whiteout-io/dev/wo-897

Use iframe-resizer
This commit is contained in:
Felix Hammerl 2015-02-19 12:45:40 +01:00
commit 9aebecd45f
11 changed files with 314 additions and 277 deletions

View File

@ -265,6 +265,7 @@ module.exports = function(grunt) {
'src/lib/angular/angular-animate.js', 'src/lib/angular/angular-animate.js',
'src/lib/ngtagsinput/ng-tags-input.min.js', 'src/lib/ngtagsinput/ng-tags-input.min.js',
'node_modules/ng-infinite-scroll/build/ng-infinite-scroll.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/fastclick/fastclick.js',
'src/lib/lawnchair/lawnchair-git.js', 'src/lib/lawnchair/lawnchair-git.js',
'src/lib/lawnchair/lawnchair-adapter-webkit-sqlite-git.js', 'src/lib/lawnchair/lawnchair-adapter-webkit-sqlite-git.js',
@ -283,6 +284,7 @@ module.exports = function(grunt) {
readSandbox: { readSandbox: {
src: [ src: [
'node_modules/dompurify/purify.js', 'node_modules/dompurify/purify.js',
'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js',
'src/js/controller/app/read-sandbox.js' 'src/js/controller/app/read-sandbox.js'
], ],
dest: 'dist/js/read-sandbox.min.js' dest: 'dist/js/read-sandbox.min.js'

View File

@ -62,6 +62,7 @@
"grunt-string-replace": "~1.0.0", "grunt-string-replace": "~1.0.0",
"grunt-svgmin": "~1.0.0", "grunt-svgmin": "~1.0.0",
"grunt-svgstore": "~0.3.4", "grunt-svgstore": "~0.3.4",
"iframe-resizer": "^2.8.3",
"imap-client": "~0.11.0", "imap-client": "~0.11.0",
"jquery": "~2.1.1", "jquery": "~2.1.1",
"mailreader": "~0.4.0", "mailreader": "~0.4.0",
@ -74,4 +75,4 @@
"time-grunt": "^1.0.0", "time-grunt": "^1.0.0",
"wo-smtpclient": "~0.6.0" "wo-smtpclient": "~0.6.0"
} }
} }

View File

@ -14,8 +14,8 @@ fi
# switch branch # switch branch
git checkout $2 git checkout $2
git branch release/$1 git branch -D release/$1
git checkout release/$1 git checkout -b release/$1
git merge $2 --no-edit git merge $2 --no-edit
# build and test # build and test

View File

@ -6,7 +6,7 @@ window.onmessage = function(e) {
if (e.data.html) { if (e.data.html) {
// display html mail body // display html mail body
html = '<div class="scale-body">' + e.data.html + '</div>'; html = e.data.html;
} else if (e.data.text) { } else if (e.data.text) {
// diplay text mail body by with colored conversation nodes // diplay text mail body by with colored conversation nodes
html = renderNodes(parseConversation(e.data.text)); html = renderNodes(parseConversation(e.data.text));
@ -26,10 +26,34 @@ window.onmessage = function(e) {
document.body.innerHTML = html; 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 * Parse email body and generate conversation nodes
@ -188,28 +212,4 @@ function renderNodes(root) {
} }
return '<div class="view-read-body">' + body + '</div>'; return '<div class="view-read-body">' + body + '</div>';
}
/**
* 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;
} }

View File

@ -158,7 +158,7 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain
if (forward) { if (forward) {
$scope.subject = 'Fwd: ' + re.subject; $scope.subject = 'Fwd: ' + re.subject;
} else { } else {
$scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : ''); $scope.subject = re.subject ? 'Re: ' + re.subject.replace('Re: ', '') : '';
} }
// fill text body // fill text body

View File

@ -3,131 +3,166 @@
var ngModule = angular.module('woDirectives'); var ngModule = angular.module('woDirectives');
ngModule.directive('replySelection', function() { ngModule.directive('replySelection', function() {
return function(scope, elm) { return function(scope, elm) {
var popover, visible; var popover, visible;
popover = angular.element(document.querySelector('.reply-selection')); popover = angular.element(document.querySelector('.reply-selection'));
visible = false; visible = false;
elm.on('touchstart click', appear); elm.on('touchstart click', appear);
elm.parent().parent().on('touchstart click', disappear); elm.parent().parent().on('touchstart click', disappear);
popover.on('touchstart click', disappear); popover.on('touchstart click', disappear);
function appear(e) { function appear(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
visible = true; visible = true;
// set popover position // set popover position
var top = elm[0].offsetTop; var top = elm[0].offsetTop;
var left = elm[0].offsetLeft; var left = elm[0].offsetLeft;
var width = elm[0].offsetWidth; var width = elm[0].offsetWidth;
var height = elm[0].offsetHeight; var height = elm[0].offsetHeight;
popover[0].style.transition = 'opacity 0.1s linear'; popover[0].style.transition = 'opacity 0.1s linear';
popover[0].style.top = (top + height) + 'px'; popover[0].style.top = (top + height) + 'px';
popover[0].style.left = (left + width / 2 - popover[0].offsetWidth / 2) + 'px'; popover[0].style.left = (left + width / 2 - popover[0].offsetWidth / 2) + 'px';
popover[0].style.opacity = '1'; popover[0].style.opacity = '1';
} }
function disappear() { function disappear() {
if (!visible) { if (!visible) {
return; return;
} }
popover[0].style.transition = 'opacity 0.25s linear, top 0.25s step-end, left 0.25s step-end'; 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.opacity = '0';
popover[0].style.top = '-9999px'; popover[0].style.top = '-9999px';
popover[0].style.left = '-9999px'; popover[0].style.left = '-9999px';
visible = false; visible = false;
} }
}; };
}); });
ngModule.directive('frameLoad', function($timeout, $window) { ngModule.directive('frameLoad', function($window) {
return function(scope, elm) { return function(scope, elm) {
var iframe = elm[0]; var iframe = elm[0];
scope.$watch('state.read.open', function(open) { scope.$watch('state.read.open', function(open) {
if (open) { if (open) {
// trigger rendering of iframe // trigger rendering of iframe
// otherwise scale to fit would not compute correct dimensions on mobile // otherwise scale to fit would not compute correct dimensions on mobile
displayText(scope.state.mailList.selected ? scope.state.mailList.selected.body : undefined); displayText(scope.state.mailList.selected ? scope.state.mailList.selected.body : undefined);
displayHtml(scope.state.mailList.selected ? scope.state.mailList.selected.html : 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() { $window.addEventListener('resize', resetWidth);
// set listeners $window.addEventListener('orientationchange', resetWidth);
scope.$watch('state.mailList.selected.body', displayText);
scope.$watch('state.mailList.selected.html', displayHtml);
// display initial message body
scope.$apply();
};
function displayText(body) { // use iframe-resizer to dynamically adapt iframe size to its content
var mail = scope.state.mailList.selected; elm.iFrameResize({
if ((mail && mail.html) || (mail && mail.encrypted && !mail.decrypted)) { enablePublicMethods: true,
return; 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.onload = function() {
iframe.contentWindow.postMessage({ // set listeners
text: body 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) { resetWidth();
if (!html) {
return;
}
// if there are image tags in the html? // send text body for rendering in iframe
var hasImages = /<img[^>]+\bsrc=['"][^'">]+['"]/ig.test(html); iframe.contentWindow.postMessage({
scope.showImageButton = hasImages; text: body
}, '*');
}
iframe.contentWindow.postMessage({ function displayHtml(html) {
html: html, if (!html) {
removeImages: hasImages // avoids doing unnecessary work on the html return;
}, '*'); }
// only add a scope function to reload the html if there are images resetWidth();
if (hasImages) {
// reload WITH images
scope.displayImages = function() {
scope.showImageButton = false;
iframe.contentWindow.postMessage({
html: html,
removeImages: false
}, '*');
};
}
$timeout(scaleToFit, 0); // if there are image tags in the html?
} var hasImages = /<img[^>]+\bsrc=['"][^'">]+['"]/ig.test(html);
scope.showImageButton = hasImages;
// transform scale iframe (necessary on iOS) to fit container width iframe.contentWindow.postMessage({
function scaleToFit() { html: html,
var parentWidth = elm.parent().width(); removeImages: hasImages // avoids doing unnecessary work on the html
var w = elm.width(); }, '*');
var scale = '';
if (w > parentWidth) { // only add a scope function to reload the html if there are images
scale = parentWidth / w; if (hasImages) {
scale = 'scale(' + scale + ',' + scale + ')'; // reload WITH images
} scope.displayImages = function() {
scope.showImageButton = false;
iframe.contentWindow.postMessage({
html: html,
removeImages: false
}, '*');
};
}
}
elm.css({ // reset the iframe width to the original (min-width:100%)
'-webkit-transform-origin': '0 0', // usually required before a new scaleToFit event
'transform-origin': '0 0', function resetWidth() {
'-webkit-transform': scale, elm.css('width', '');
'transform': scale }
});
} // 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
});
}
};
}); });

View File

@ -101,7 +101,7 @@ Dummy.prototype.listMails = function() {
'>> from 0.7.0.1\n' + '>> from 0.7.0.1\n' +
'>>\n' + '>>\n' +
'>> God speed!'; // plaintext body '>> God speed!'; // plaintext body
//this.html = '<!DOCTYPE html><html><head></head><body><h1 style="border: 1px solid red; width: 500px;">Hello there' + Math.random() + '</h1></body></html>'; this.html = '<!DOCTYPE html><html><head></head><body><h1 style="border: 1px solid red; width: 500px; margin:0;">Hello there' + Math.random() + '</h1></body></html>';
this.encrypted = true; this.encrypted = true;
this.decrypted = true; this.decrypted = true;
}; };

View File

@ -23,11 +23,32 @@
display: none; 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 components
&__header { &__header {
flex-shrink: 0;
margin-bottom: 1em; margin-bottom: 1em;
padding: $padding-vertical $padding-horizontal 0; padding: $padding-vertical $padding-horizontal 0;
@ -41,15 +62,28 @@
} }
&__controls { &__controls {
display: none; display: none;
float: right; position: absolute;
margin-left: 1em; top: 0;
.btn-icon-light { 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; margin-left: 1.4em;
} }
@include respond-to(md) { @include respond-to(md) {
display: block; 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 { &__subject {
font-weight: normal; font-weight: normal;
color: $color-text; color: $color-text;
@ -103,7 +137,6 @@
// Content components // Content components
&__signature-status { &__signature-status {
flex-shrink: 0;
margin-top: 0; margin-top: 0;
margin-bottom: 0.5em; margin-bottom: 0.5em;
text-align: center; text-align: center;
@ -111,40 +144,17 @@
padding: 0 $padding-horizontal; padding: 0 $padding-horizontal;
} }
&__display-images { &__display-images {
flex-shrink: 0;
margin-bottom: 0.5em; margin-bottom: 0.5em;
text-align: center; text-align: center;
padding: 0 $padding-horizontal; 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 { &__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; padding: 0 $padding-horizontal $padding-vertical;
overflow: hidden; // necessary due to iframe scaling via transitions
iframe { iframe {
flex-grow: 1;
border: none; border: none;
width: 100%; min-width: 100%;
} }
} }

View File

@ -5,24 +5,11 @@
// Mixins // Mixins
@import "mixins/responsive"; @import "mixins/responsive";
@import "mixins/scrollbar";
@include scrollbar();
html {
// use overflow auto and not scroll otherwise IE shows scrollbars always
overflow: auto;
}
body { body {
margin: 0; margin: 0;
} }
.scale-body {
// necessary to compute overflowing content width in JS
float: left;
}
.view-read-body { .view-read-body {
font-family: $font-family-base; font-family: $font-family-base;
font-size: $font-size-base; font-size: $font-size-base;

View File

@ -5,7 +5,7 @@
</header> </header>
<main class="page__main"> <main class="page__main">
<h2 class="typo-title">Create Whiteout account</h2> <h2 class="typo-title">Create Whiteout account</h2>
<p class="typo-paragraph">Sign up for an encrypted mailbox hosted in Germany.<br>Already have an account? <a href="#" wo-touch="$event.preventDefault(); loginToExisting()">Log in here</a>.</p> <p class="typo-paragraph">Sign up for an encrypted mailbox hosted in Germany. Already have an account? <a href="#" wo-touch="$event.preventDefault(); loginToExisting()">Log in here</a>.</p>
<form class="form" name="form"> <form class="form" name="form">
<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p> <p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
<div class="form__row"> <div class="form__row">

View File

@ -13,90 +13,32 @@
</div> </div>
</div><!--/read__folder-toolbar--> </div><!--/read__folder-toolbar-->
<header class="read__header"> <div class="read__controls">
<div class="read__controls"> <span class="u-hidden-lg" ng-controller="ActionBarCtrl">
<span class="u-hidden-lg" ng-controller="ActionBarCtrl"> <button wo-touch="flagMessage(state.mailList.selected, !state.mailList.selected.flagged)" class="btn-icon-light" title="{{state.mailList.selected.flagged ? 'Remove Star' : 'Add Star'}}">
<button wo-touch="flagMessage(state.mailList.selected, !state.mailList.selected.flagged)" class="btn-icon-light" title="{{state.mailList.selected.flagged ? 'Remove Star' : 'Add Star'}}"> <svg ng-show="state.mailList.selected.flagged"><use xlink:href="#icon-star_filled" /><title>Starred</title></svg>
<svg ng-show="state.mailList.selected.flagged"><use xlink:href="#icon-star_filled" /><title>Starred</title></svg> <svg ng-show="!state.mailList.selected.flagged"><use xlink:href="#icon-star" /><title>Not Starred</title></svg>
<svg ng-show="!state.mailList.selected.flagged"><use xlink:href="#icon-star" /><title>Not Starred</title></svg>
</button>
<button class="btn-icon-light" title="Move mail" wo-dropdown="#read-dropdown-folder" wo-dropdown-position="center">
<svg><use xlink:href="#icon-folder" /><title>Move mail</title></svg>
</button>
<button wo-touch="deleteMessage(state.mailList.selected)" class="btn-icon-light" title="Delete mail">
<svg><use xlink:href="#icon-delete" /><title>Delete mail</title></svg>
</button>
<button class="btn-icon-light" title="Reply to" wo-dropdown="#read-reply-selection" wo-dropdown-position="center">
<svg><use xlink:href="#icon-reply_light" /><title>Reply to</title></svg>
</button>
<button wo-touch="state.writer.write()" class="btn-icon-light" title="New mail">
<svg><use xlink:href="#icon-write" /><title>New mail</title></svg>
</button>
</span>
<span class="u-visible-lg">
<button class="btn-icon-light" wo-touch="state.writer.write(state.mailList.selected)" title="Reply"><svg><use xlink:href="#icon-reply_light" /></svg></button>
<button class="btn-icon-light" wo-touch="state.writer.write(state.mailList.selected, true)" title="Reply All"><svg><use xlink:href="#icon-reply_all_light" /></svg></button>
<button class="btn-icon-light" wo-touch="state.writer.write(state.mailList.selected, null, true)" title="Forward"><svg><use xlink:href="#icon-forward_light" /></svg></button>
</span>
</div><!--/read__controls-->
<h2 class="read__subject" wo-touch="notStripped = !notStripped">
<button ng-hide="notStripped" class="btn-icon-very-light">
<svg><use xlink:href="#icon-dropdown" /><title>More</title></svg>
</button> </button>
<button ng-show="notStripped" class="btn-icon-very-light"> <button class="btn-icon-light" title="Move mail" wo-dropdown="#read-dropdown-folder" wo-dropdown-position="center">
<svg><use xlink:href="#icon-dropup" /><title>Less</title></svg> <svg><use xlink:href="#icon-folder" /><title>Move mail</title></svg>
</button> </button>
<button wo-touch="deleteMessage(state.mailList.selected)" class="btn-icon-light" title="Delete mail">
<svg><use xlink:href="#icon-delete" /><title>Delete mail</title></svg>
</button>
<button class="btn-icon-light" title="Reply to" wo-dropdown="#read-reply-selection" wo-dropdown-position="center">
<svg><use xlink:href="#icon-reply_light" /><title>Reply to</title></svg>
</button>
<button wo-touch="state.writer.write()" class="btn-icon-light" title="New mail">
<svg><use xlink:href="#icon-write" /><title>New mail</title></svg>
</button>
</span>
{{state.mailList.selected.subject ? state.mailList.selected.subject : 'No subject'}} <span class="u-visible-lg">
</h2> <button class="btn-icon-light" wo-touch="state.writer.write(state.mailList.selected)" title="Reply"><svg><use xlink:href="#icon-reply_light" /></svg></button>
<h2 class="read__subject-md" wo-touch="close()"> <button class="btn-icon-light" wo-touch="state.writer.write(state.mailList.selected, true)" title="Reply All"><svg><use xlink:href="#icon-reply_all_light" /></svg></button>
<svg><use xlink:href="#icon-back" /><title>Back</title></svg> <button class="btn-icon-light" wo-touch="state.writer.write(state.mailList.selected, null, true)" title="Forward"><svg><use xlink:href="#icon-forward_light" /></svg></button>
{{state.mailList.selected.subject ? state.mailList.selected.subject : 'No subject'}} </span>
</h2> </div><!--/read__controls-->
<time class="read__time">{{state.mailList.selected.sentDate | date:'EEEE, MMM d, yyyy h:mm a'}}</time>
<div class="read__addresses">
<div class="mail-addresses">
<label>From:</label>
<span ng-repeat="u in state.mailList.selected.from">
<span class="label" ng-class="{'label--invalid': u.secure === false, 'label--invalid-clickable': u.secure === false}" ng-mouseover="getKeyId(u.address)" wo-touch="invite(u)" wo-tooltip="#fingerprint-info">
{{u.name || u.address}}
<svg ng-show="u.secure === false"><use xlink:href="#icon-add_contact" /></svg>
</span>
<span class="mail-addresses__stripped"></span>
</span>
</div>
<div class="mail-addresses">
<label>To:</label>
<span ng-repeat="u in state.mailList.selected.to">
<span class="label" ng-class="{'label--invalid': u.secure === false, 'label--invalid-clickable': u.secure === false}" ng-mouseover="getKeyId(u.address)" wo-touch="invite(u)" wo-tooltip="#fingerprint-info">
{{u.name || u.address}}
<svg ng-show="u.secure === false"><use xlink:href="#icon-add_contact" /></svg>
</span>
</span>
</div>
<div class="mail-addresses" ng-show="state.mailList.selected.cc && state.mailList.selected.cc.length > 0">
<label>Cc:</label>
<span ng-repeat="u in state.mailList.selected.cc">
<span class="label" ng-class="{'label--invalid': u.secure === false, 'label--invalid-clickable': u.secure === false}" ng-mouseover="getKeyId(u.address)" wo-touch="invite(u)" wo-tooltip="#fingerprint-info">
{{u.name || u.address}}
<svg ng-show="u.secure === false"><use xlink:href="#icon-add_contact" /></svg>
</span>
</span>
</div>
</div>
<ul class="attachments" ng-show="state.mailList.selected.attachments !== undefined && state.mailList.selected.attachments.length > 0">
<li ng-repeat="attachment in state.mailList.selected.attachments"
wo-touch="download(attachment)">
<span ng-if="attachment.busy" class="spinner"></span>
<svg ng-hide="attachment.busy"><use xlink:href="#icon-attachment" /></svg>
{{attachment.filename}}
</li>
</ul>
</header><!--/read__header-->
<!-- working spinner --> <!-- working spinner -->
<div class="read__working" <div class="read__working"
@ -105,24 +47,84 @@
<span class="spinner"></span> <span class="spinner"></span>
<strong ng-bind="(state.mailList.selected.decryptingBody) ? 'Decrypting...' : 'Loading...' "></strong> <strong ng-bind="(state.mailList.selected.decryptingBody) ? 'Decrypting...' : 'Loading...' "></strong>
</div> </div>
</div> </div><!--/read__working-->
<p class="read__signature-status" <div class="read__content"
ng-show="(state.mailList.selected.body || state.mailList.selected.html) && state.mailList.selected.signed && !state.mailList.selected.signaturesValid">
Invalid PGP signature. This message could have been tampered with.
</p>
<div class="read__display-images" ng-show="state.mailList.selected.html && showImageButton">
<button class="btn btn--light" wo-touch="displayImages()">Display images</button>
</div>
<!-- Render html body in sandboxed iframe -->
<div class="read__body"
ng-show="state.mailList.selected && (!state.mailList.selected.encrypted && (state.mailList.selected.body || state.mailList.selected.html)) || (state.mailList.selected.encrypted && state.mailList.selected.decrypted)"> ng-show="state.mailList.selected && (!state.mailList.selected.encrypted && (state.mailList.selected.body || state.mailList.selected.html)) || (state.mailList.selected.encrypted && state.mailList.selected.decrypted)">
<iframe sandbox="allow-popups allow-scripts" src="tpl/read-sandbox.html" <header class="read__header">
frame-load> <div class="read__controls__dummy"></div>
</iframe>
</div><!--/read__body--> <h2 class="read__subject" wo-touch="notStripped = !notStripped">
<button ng-hide="notStripped" class="btn-icon-very-light">
<svg><use xlink:href="#icon-dropdown" /><title>More</title></svg>
</button>
<button ng-show="notStripped" class="btn-icon-very-light">
<svg><use xlink:href="#icon-dropup" /><title>Less</title></svg>
</button>
{{state.mailList.selected.subject ? state.mailList.selected.subject : 'No subject'}}
</h2>
<h2 class="read__subject-md" wo-touch="close()">
<svg><use xlink:href="#icon-back" /><title>Back</title></svg>
{{state.mailList.selected.subject ? state.mailList.selected.subject : 'No subject'}}
</h2>
<time class="read__time">{{state.mailList.selected.sentDate | date:'EEEE, MMM d, yyyy h:mm a'}}</time>
<div class="read__addresses">
<div class="mail-addresses">
<label>From:</label>
<span ng-repeat="u in state.mailList.selected.from">
<span class="label" ng-class="{'label--invalid': u.secure === false, 'label--invalid-clickable': u.secure === false}" ng-mouseover="getKeyId(u.address)" wo-touch="invite(u)" wo-tooltip="#fingerprint-info">
{{u.name || u.address}}
<svg ng-show="u.secure === false"><use xlink:href="#icon-add_contact" /></svg>
</span>
<span class="mail-addresses__stripped"></span>
</span>
</div>
<div class="mail-addresses">
<label>To:</label>
<span ng-repeat="u in state.mailList.selected.to">
<span class="label" ng-class="{'label--invalid': u.secure === false, 'label--invalid-clickable': u.secure === false}" ng-mouseover="getKeyId(u.address)" wo-touch="invite(u)" wo-tooltip="#fingerprint-info">
{{u.name || u.address}}
<svg ng-show="u.secure === false"><use xlink:href="#icon-add_contact" /></svg>
</span>
</span>
</div>
<div class="mail-addresses" ng-show="state.mailList.selected.cc && state.mailList.selected.cc.length > 0">
<label>Cc:</label>
<span ng-repeat="u in state.mailList.selected.cc">
<span class="label" ng-class="{'label--invalid': u.secure === false, 'label--invalid-clickable': u.secure === false}" ng-mouseover="getKeyId(u.address)" wo-touch="invite(u)" wo-tooltip="#fingerprint-info">
{{u.name || u.address}}
<svg ng-show="u.secure === false"><use xlink:href="#icon-add_contact" /></svg>
</span>
</span>
</div>
</div><!--/read__addresses-->
<ul class="attachments" ng-show="state.mailList.selected.attachments !== undefined && state.mailList.selected.attachments.length > 0">
<li ng-repeat="attachment in state.mailList.selected.attachments"
wo-touch="download(attachment)">
<span ng-if="attachment.busy" class="spinner"></span>
<svg ng-hide="attachment.busy"><use xlink:href="#icon-attachment" /></svg>
{{attachment.filename}}
</li>
</ul>
</header><!--/read__header-->
<p class="read__signature-status"
ng-show="(state.mailList.selected.body || state.mailList.selected.html) && state.mailList.selected.signed && !state.mailList.selected.signaturesValid">
Invalid PGP signature. This message could have been tampered with.
</p>
<div class="read__display-images" ng-show="state.mailList.selected.html && showImageButton">
<button class="btn btn--light" wo-touch="displayImages()">Display images</button>
</div>
<div class="read__body">
<!-- Render html body in sandboxed iframe -->
<iframe sandbox="allow-popups allow-scripts" src="tpl/read-sandbox.html" scrolling="no" frame-load></iframe>
</div>
</div><!--/read__content-->
<div class="read__action-toolbar" ng-controller="ActionBarCtrl"> <div class="read__action-toolbar" ng-controller="ActionBarCtrl">
<div class="toolbar"> <div class="toolbar">
@ -179,4 +181,4 @@
</li> </li>
</ul><!--/dropdown--> </ul><!--/dropdown-->
</div> </div><!--/read-->