mirror of
https://github.com/moparisthebest/mail
synced 2024-11-24 18:02:15 -05:00
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
This commit is contained in:
parent
2c1e1f669e
commit
9d68b6475c
@ -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'
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
@ -189,27 +213,3 @@ 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;
|
|
||||||
}
|
|
@ -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
|
||||||
|
@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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">
|
||||||
|
@ -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-->
|
Loading…
Reference in New Issue
Block a user