Merge pull request #52 from whiteout-io/dev/css

Dev/css
This commit is contained in:
Tankred Hase 2014-04-24 17:34:37 +02:00
commit 5fef73ab99
50 changed files with 2009 additions and 400 deletions

View File

@ -12,8 +12,15 @@
<script src="require-config.js"></script>
<script src="js/app.js"></script>
</head>
<body key-shortcuts>
<div ng-view class="main-app-view"></div>
<!-- error dialog lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div><!--/.lightbox-overlay-->
</body>
</html>

View File

@ -20,6 +20,13 @@
</head>
<body>
<div ng-view class="main-app-view ios-spacer"></div>
<!-- error dialog lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div><!--/.lightbox-overlay-->
</body>
</html>

View File

@ -19,9 +19,11 @@ requirejs([
'js/controller/write',
'js/controller/navigation',
'cryptoLib/util',
'js/util/error',
'angularSanitize',
'angularRoute',
'angularTouch'
'angularTouch',
'angularAnimate'
], function(
angular,
DialogCtrl,
@ -39,7 +41,8 @@ requirejs([
ReadCtrl,
WriteCtrl,
NavigationCtrl,
util
util,
errorUtil
) {
'use strict';
@ -51,6 +54,7 @@ requirejs([
'ngSanitize',
'ngRoute',
'ngTouch',
'ngAnimate',
'navigation',
'mail-list',
'write',
@ -91,6 +95,13 @@ requirejs([
});
});
app.run(function($rootScope) {
// global state... inherited to all child scopes
$rootScope.state = {};
// attach global error handler
errorUtil.attachHandler($rootScope);
});
// inject controllers from ng-included view templates
app.controller('ReadCtrl', ReadCtrl);
app.controller('WriteCtrl', WriteCtrl);

View File

@ -10,9 +10,8 @@ define(function(require) {
var AboutCtrl = function($scope) {
$scope.state.about = {
open: false,
toggle: function(to) {
this.open = to;
$scope.state.lightbox = (to) ? 'about' : undefined;
}
};

View File

@ -15,9 +15,8 @@ define(function(require) {
pgp = appController._crypto;
$scope.state.account = {
open: false,
toggle: function(to) {
this.open = to;
$scope.state.lightbox = (to) ? 'account' : undefined;
}
};

View File

@ -1,15 +1,9 @@
define(function(require) {
'use strict';
var appController = require('js/app-controller'),
errorUtil = require('js/util/error');
var appController = require('js/app-controller');
var AddAccountCtrl = function($scope, $location) {
// global state... inherited to all child scopes
$scope.$root.state = {};
// attach global error handler
errorUtil.attachHandler($scope);
$scope.connectToGoogle = function() {
appController._auth.getCredentials({}, function(err) {
if (err) {

View File

@ -15,9 +15,9 @@ define(function(require) {
pgp = appController._crypto;
$scope.state.contacts = {
open: false,
toggle: function(to) {
this.open = to;
$scope.state.lightbox = (to) ? 'contacts' : undefined;
$scope.listKeys();
}
};

View File

@ -1,15 +1,9 @@
define(function(require) {
'use strict';
var appController = require('js/app-controller'),
errorUtil = require('js/util/error');
var appController = require('js/app-controller');
var LoginExistingCtrl = function($scope, $location) {
// global state... inherited to all child scopes
$scope.$root.state = {};
// attach global error handler
errorUtil.attachHandler($scope);
var emailDao = appController._emailDao;
$scope.buttonEnabled = true;

View File

@ -1,18 +1,12 @@
define(function(require) {
'use strict';
var appController = require('js/app-controller'),
errorUtil = require('js/util/error');
var appController = require('js/app-controller');
var LoginInitialCtrl = function($scope, $location) {
var emailDao = appController._emailDao,
states, termsMsg = 'You must accept the Terms of Service to continue.';
// global state... inherited to all child scopes
$scope.$root.state = {};
// attach global error handler
errorUtil.attachHandler($scope);
states = {
IDLE: 1,
PROCESSING: 2,

View File

@ -2,15 +2,9 @@ define(function(require) {
'use strict';
var angular = require('angular'),
errorUtil = require('js/util/error'),
appController = require('js/app-controller');
var LoginExistingCtrl = function($scope, $location) {
// global state... inherited to all child scopes
$scope.$root.state = {};
// attach global error handler
errorUtil.attachHandler($scope);
var emailDao = appController._emailDao,
pgp = appController._crypto;

View File

@ -1,15 +1,9 @@
define(function(require) {
'use strict';
var appController = require('js/app-controller'),
errorUtil = require('js/util/error');
var appController = require('js/app-controller');
var LoginCtrl = function($scope, $location) {
// global state... inherited to all child scopes
$scope.$root.state = {};
// attach global error handler
errorUtil.attachHandler($scope);
// check for app update
appController.checkForUpdate();

View File

@ -44,7 +44,7 @@ define(function(require) {
}
// display fetched body
$scope.$apply();
$scope.$digest();
// automatically decrypt if it's the selected email
if (email === $scope.state.mailList.selected) {
@ -164,7 +164,7 @@ define(function(require) {
var index = getFolder().messages.indexOf(email);
// show the next mail
if (getFolder().messages.length > 1) {
// if we're about to delete the last entry of the array, show the previous (i.e. the one below in the list),
// if we're about to delete the last entry of the array, show the previous (i.e. the one below in the list),
// otherwise show the next one (i.e. the one above in the list)
$scope.select(_.last(getFolder().messages) === email ? getFolder().messages[index - 1] : getFolder().messages[index + 1]);
} else {
@ -209,7 +209,6 @@ define(function(require) {
$timeout(function() {
// display and select first
selectFirstMessage();
$scope.$apply();
});
$scope.synchronize();
@ -289,6 +288,8 @@ define(function(require) {
}]; // sender address
this.to = [{
address: 'max.musterman@gmail.com'
}, {
address: 'max.musterman@gmail.com'
}]; // list of receivers
this.cc = [{
address: 'john.doe@gmail.com'
@ -399,11 +400,13 @@ define(function(require) {
//
var ngModule = angular.module('mail-list', []);
ngModule.directive('ngIscroll', function() {
ngModule.directive('ngIscroll', function($timeout) {
return {
link: function(scope, elm, attrs) {
var model = attrs.ngIscroll,
listEl = elm[0];
listEl = elm[0],
myScroll;
/*
* iterates over the mails in the mail list and loads their bodies if they are visible in the viewport
@ -418,7 +421,7 @@ define(function(require) {
isPartiallyVisibleTop, isPartiallyVisibleBottom, isVisible;
for (var i = 0, len = listItems.length; i < len; i++) {
// the n-th list item (the dom representation of an email) corresponds to
// the n-th list item (the dom representation of an email) corresponds to
// the n-th message model in the filteredMessages array
listItem = listItems.item(i).getBoundingClientRect();
message = scope.filteredMessages[i];
@ -439,18 +442,22 @@ define(function(require) {
}
};
// re-init iScroll when model length changes
scope.$watch(model, function() {
var myScroll;
// activate iscroll
myScroll = new IScroll(listEl, {
mouseWheel: true
});
// activate iscroll
myScroll = new IScroll(listEl, {
mouseWheel: true,
scrollbars: true,
fadeScrollbars: true
});
myScroll.on('scrollEnd', scope.loadVisibleBodies);
// refresh iScroll when model length changes
scope.$watchCollection(model, function() {
$timeout(function() {
myScroll.refresh();
});
// load the visible message bodies, when the list is re-initialized and when scrolling stopped
scope.loadVisibleBodies();
myScroll.on('scrollEnd', scope.loadVisibleBodies);
}, true);
});
}
};
});

View File

@ -4,7 +4,6 @@ define(function(require) {
var angular = require('angular'),
str = require('js/app-config').string,
appController = require('js/app-controller'),
errorUtil = require('js/util/error'),
notification = require('js/util/notification'),
_ = require('underscore'),
emailDao, outboxBo;
@ -14,11 +13,6 @@ define(function(require) {
//
var NavigationCtrl = function($scope) {
// global state... inherited to all child scopes
$scope.$root.state = {};
// attach global error handler
errorUtil.attachHandler($scope);
emailDao = appController._emailDao;
outboxBo = appController._outboxBo;
@ -149,51 +143,46 @@ define(function(require) {
var modifier = e.ctrlKey || e.metaKey;
if (modifier && e.keyCode === 78 && scope.state.writer && !scope.state.writer.open) {
if (modifier && e.keyCode === 78 && scope.state.lightbox !== 'write') {
// n -> new mail
e.preventDefault();
scope.state.writer.write();
scope.$apply();
} else if (modifier && e.keyCode === 70 && !scope.state.writer.open) {
} else if (modifier && e.keyCode === 70 && scope.state.lightbox !== 'write') {
// f -> find
e.preventDefault();
scope.state.mailList.searching = true;
$timeout(function() {
scope.state.mailList.searching = false;
}, 200);
scope.$apply();
} else if (modifier && e.keyCode === 82 && scope.state.writer && !scope.state.writer.open && scope.state.mailList.selected) {
} else if (modifier && e.keyCode === 82 && scope.state.lightbox !== 'write' && scope.state.mailList.selected) {
// r -> reply
e.preventDefault();
scope.state.writer.write(scope.state.mailList.selected);
scope.$apply();
} else if (modifier && e.keyCode === 83 && scope.state.writer && !scope.state.writer.open && scope.state.mailList.synchronize) {
} else if (modifier && e.keyCode === 83 && scope.state.lightbox !== 'write' && scope.state.mailList.synchronize) {
// s -> sync folder
e.preventDefault();
scope.state.mailList.synchronize();
scope.$apply();
} else if (e.keyCode === 27 && scope.state.writer.open) {
// escape -> close writer
} else if (e.keyCode === 27 && scope.state.lightbox !== undefined) {
// escape -> close current lightbox
e.preventDefault();
scope.state.writer.close();
} else if (e.keyCode === 27 && scope.state.account.open) {
// escape -> close account view
e.preventDefault();
scope.state.account.toggle(false);
} else if (e.keyCode === 27 && scope.state.contacts.open) {
// escape -> close contacts view
e.preventDefault();
scope.state.contacts.toggle(false);
scope.state.lightbox = undefined;
scope.$apply();
} else if (e.keyCode === 27 && scope.state.nav.open) {
// escape -> close nav view
e.preventDefault();
scope.state.nav.toggle(false);
scope.$apply();
}
scope.$apply();
});
};
});

View File

@ -13,9 +13,8 @@ define(function(require) {
pgp = appController._crypto;
$scope.state.setPassphrase = {
open: false,
toggle: function(to) {
this.open = to;
$scope.state.lightbox = (to) ? 'set-passphrase' : undefined;
$scope.newPassphrase = undefined;
$scope.oldPassphrase = undefined;

View File

@ -25,9 +25,8 @@ define(function(require) {
//
$scope.state.writer = {
open: false,
write: function(replyTo, replyAll, forward) {
this.open = true;
$scope.state.lightbox = 'write';
$scope.replyTo = replyTo;
resetFields();
@ -39,7 +38,7 @@ define(function(require) {
$scope.verify($scope.to[0]);
},
close: function() {
this.open = false;
$scope.state.lightbox = undefined;
}
};
@ -48,9 +47,11 @@ define(function(require) {
$scope.to = [{
address: ''
}];
$scope.showCC = false;
$scope.cc = [{
address: ''
}];
$scope.showBCC = false;
$scope.bcc = [{
address: ''
}];
@ -85,6 +86,7 @@ define(function(require) {
$scope.cc.unshift({
address: recipient.address
});
$scope.showCC = true;
});
$scope.cc.forEach($scope.verify);
}
@ -172,7 +174,7 @@ define(function(require) {
}
$scope.checkSendStatus();
$scope.$apply();
$scope.$digest();
});
};
@ -332,10 +334,9 @@ define(function(require) {
link: function(scope, elm, attrs, ctrl) {
// view -> model
elm.on('keyup keydown', function() {
scope.$apply(function() {
// set model
ctrl.$setViewValue(elm[0].innerText);
});
// set model
ctrl.$setViewValue(elm[0].innerText);
scope.$digest();
});
// model -> view
@ -404,6 +405,11 @@ define(function(require) {
scope.$apply();
}
function removeInput(field, index, scope) {
field.splice(index, 1);
scope.$apply();
}
function checkForEmptyInput(field) {
var emptyFieldExists = false;
field.forEach(function(recipient) {
@ -415,6 +421,18 @@ define(function(require) {
return emptyFieldExists;
}
function cleanupEmptyInputs(field, scope) {
var i;
for (i = field.length - 2; i >= 0; i--) {
if (!field[i].address) {
field.splice(i, 1);
}
}
scope.$apply();
}
ngModule.directive('field', function() {
return {
//scope: true, // optionally create a child scope
@ -455,12 +473,14 @@ define(function(require) {
// create new field input
addInput(field, scope);
}
cleanupEmptyInputs(field, scope);
});
element.on('keydown', function(e) {
var code = e.keyCode;
scope.$apply();
scope.$digest();
if (code === 32 || code === 188 || code === 186) {
// catch space, comma, semicolon
@ -476,8 +496,7 @@ define(function(require) {
// backspace, delete on empty input
// remove input
e.preventDefault();
field.splice(index, 1);
scope.$apply();
removeInput(field, index, scope);
// focus on previous id
var previousId = fieldName + (index - 1);
document.getElementById(previousId).focus();
@ -503,7 +522,7 @@ define(function(require) {
mimeType: file.type,
content: new Uint8Array(e.target.result)
});
scope.$apply();
scope.$digest();
};
reader.readAsArrayBuffer(file);
}

View File

@ -4,7 +4,7 @@ define(function() {
var er = {};
er.attachHandler = function(scope) {
scope.$root.onError = function(options) {
scope.onError = function(options) {
if (!options) {
scope.$apply();
return;

27
src/lib/angular/angular-animate.min.js vendored Executable file
View File

@ -0,0 +1,27 @@
/*
AngularJS v1.2.13
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(z,f,T){'use strict';f.module("ngAnimate",["ng"]).factory("$$animateReflow",["$window","$timeout","$document",function(f,h,d){var n=f.requestAnimationFrame||f.webkitRequestAnimationFrame||function(d){return h(d,10,!1)},w=f.cancelAnimationFrame||f.webkitCancelAnimationFrame||function(d){return h.cancel(d)};return function(d){var f=n(function(){d()});return function(){w(f)}}}]).factory("$$asyncQueueBuffer",["$timeout",function(f){var h,d=[];return function(n){f.cancel(h);d.push(n);h=f(function(){for(var f=
0;f<d.length;f++)d[f]();d=[]},0,!1)}}]).config(["$provide","$animateProvider",function($,h){function d(d){for(var f=0;f<d.length;f++){var l=d[f];if(l.nodeType==da)return l}}function n(l){return f.element(d(l))}var w=f.noop,D=f.forEach,ia=h.$$selectors,da=1,l="$$ngAnimateState",U="ng-animate",s={running:!0};$.decorator("$animate",["$delegate","$injector","$sniffer","$rootElement","$$asyncQueueBuffer","$rootScope","$document",function(x,z,ca,F,J,B,T){function V(a){if(a){var c=[],e={};a=a.substr(1).split(".");
(ca.transitions||ca.animations)&&a.push("");for(var A=0;A<a.length;A++){var d=a[A],f=ia[d];f&&!e[d]&&(c.push(z.get(f)),e[d]=!0)}return c}}function t(a,c,e,d,k,C,s){function t(b){var g=e.data(l);b=b||!g||!g.active[c]||m&&g.active[c].event!=a;K();!0===b?G():(g.active[c].done=G,n(L,"after",G))}function n(b,g,ja){"after"==g?H():x();var fa=g+"End";D(b,function(d,f){var A=function(){a:{var a=g+"Complete",c=b[f];c[a]=!0;(c[fa]||w)();for(c=0;c<b.length;c++)if(!b[c][a])break a;ja()}};"before"!=g||"enter"!=
a&&"move"!=a?d[g]?d[fa]=F?d[g](e,E,z,A):m?d[g](e,c,A):d[g](e,A):A():A()})}function h(b){var g="$animate:"+b;u&&(u[g]&&0<u[g].length)&&J(function(){e.triggerHandler(g,{event:a,className:c})})}function x(){h("before")}function H(){h("after")}function B(){h("close");s&&J(function(){s()})}function K(){K.hasBeenRun||(K.hasBeenRun=!0,C())}function G(){if(!G.hasBeenRun){G.hasBeenRun=!0;var b=e.data(l);b&&(m?M(e,c):(J(function(){var b=e.data(l)||{};Q==b.index&&M(e,c,a)}),e.data(l,b)));B()}}var E,z,F="setClass"==
a;F&&(E=c[0],z=c[1],c=E+" "+z);var v,y=e[0];y&&(v=y.className,v=v+" "+c);if(y&&W(v)){var u=f.element._data(y),u=u&&u.events,y=(" "+v).replace(/\s+/g,".");d||(d=k?k.parent():e.parent());var q=V(y),m="addClass"==a||"removeClass"==a||F,I=e.data(l)||{};k=I.active||{};y=I.totalActive||0;v=I.last;if(R(e,d)||0===q.length)K(),x(),H(),G();else{var L=[];m&&(I.disabled||v&&!v.classBased)||D(q,function(b){if(!b.allowCancel||b.allowCancel(e,a,c)){var g=b[a];"leave"==a?(b=g,g=null):b=b["before"+a.charAt(0).toUpperCase()+
a.substr(1)];L.push({before:b,after:g})}});if(0===L.length)K(),x(),H(),B();else{d=!1;if(0<y){q=[];if(m)"setClass"==v.event?(q.push(v),M(e,c)):k[c]&&(N=k[c],N.event==a?d=!0:(q.push(N),M(e,c)));else if("leave"==a&&k["ng-leave"])d=!0;else{for(var N in k)q.push(k[N]),M(e,N);k={};y=0}0<q.length&&f.forEach(q,function(b){(b.done||w)(!0);X(b.animations)})}!m||(F||d)||(d="addClass"==a==e.hasClass(c));if(d)x(),H(),B();else{e.addClass(U);var Q=S++;v={classBased:m,event:a,animations:L,done:t};y++;k[c]=v;e.data(l,
{last:v,active:k,index:Q,totalActive:y});n(L,"before",t)}}}}else K(),x(),H(),B()}function Y(a){a=d(a);D(a.querySelectorAll("."+U),function(a){a=f.element(a);(a=a.data(l))&&a.active&&f.forEach(a.active,function(a){(a.done||w)(!0);X(a.animations)})})}function X(a){D(a,function(a){a.beforeComplete||(a.beforeEnd||w)(!0);a.afterComplete||(a.afterEnd||w)(!0)})}function M(a,c){if(d(a)==d(F))s.disabled||(s.running=!1,s.structural=!1);else if(c){var e=a.data(l)||{},f=!0===c;!f&&(e.active&&e.active[c])&&(e.totalActive--,
delete e.active[c]);if(f||!e.totalActive)a.removeClass(U),a.removeData(l)}}function R(a,c){if(s.disabled)return!0;if(d(a)==d(F))return s.disabled||s.running;do{if(0===c.length)break;var e=d(c)==d(F),f=e?s:c.data(l),f=f&&(!!f.disabled||f.running||0<f.totalActive);if(e||f)return f;if(e)break}while(c=c.parent());return!0}var S=0;F.data(l,s);B.$$postDigest(function(){B.$$postDigest(function(){s.running=!1})});var Z=h.classNameFilter(),W=Z?function(a){return Z.test(a)}:function(){return!0};return{enter:function(a,
c,e,d){this.enabled(!1,a);x.enter(a,c,e);B.$$postDigest(function(){a=n(a);t("enter","ng-enter",a,c,e,w,d)})},leave:function(a,c){Y(a);this.enabled(!1,a);B.$$postDigest(function(){a=n(a);t("leave","ng-leave",a,null,null,function(){x.leave(a)},c)})},move:function(a,c,e,d){Y(a);this.enabled(!1,a);x.move(a,c,e);B.$$postDigest(function(){a=n(a);t("move","ng-move",a,c,e,w,d)})},addClass:function(a,c,d){a=n(a);t("addClass",c,a,null,null,function(){x.addClass(a,c)},d)},removeClass:function(a,c,d){a=n(a);
t("removeClass",c,a,null,null,function(){x.removeClass(a,c)},d)},setClass:function(a,c,d,f){a=n(a);t("setClass",[c,d],a,null,null,function(){x.setClass(a,c,d)},f)},enabled:function(a,c){switch(arguments.length){case 2:if(a)M(c);else{var d=c.data(l)||{};d.disabled=!0;c.data(l,d)}break;case 1:s.disabled=!a;break;default:a=!s.disabled}return!!a}}}]);h.register("",["$window","$sniffer","$timeout","$$animateReflow",function(l,s,h,n){function J(b,g){I&&I();m.push(g);I=n(function(){D(m,function(b){b()});
m=[];I=null;u={}})}function B(b,g){var a=Date.now()+1E3*g;if(!(a<=N)){h.cancel(L);var c=d(b);b=f.element(c);Q.push(b);N=a;L=h(function(){U(Q);Q=[]},g,!1)}}function U(b){D(b,function(b){(b=b.data(E))&&(b.closeAnimationFn||w)()})}function V(b,g){var a=g?u[g]:null;if(!a){var c=0,d=0,f=0,e=0,k,p,r,h;D(b,function(b){if(b.nodeType==da){b=l.getComputedStyle(b)||{};r=b[O+ea];c=Math.max(t(r),c);h=b[O+H];k=b[O+ha];d=Math.max(t(k),d);p=b[P+ha];e=Math.max(t(p),e);var g=t(b[P+ea]);0<g&&(g*=parseInt(b[P+K],10)||
1);f=Math.max(g,f)}});a={total:0,transitionPropertyStyle:h,transitionDurationStyle:r,transitionDelayStyle:k,transitionDelay:d,transitionDuration:c,animationDelayStyle:p,animationDelay:e,animationDuration:f};g&&(u[g]=a)}return a}function t(b){var g=0;b=f.isString(b)?b.split(/\s*,\s*/):[];D(b,function(b){g=Math.max(parseFloat(b)||0,g)});return g}function Y(b){var g=b.parent(),a=g.data(G);a||(g.data(G,++q),a=q);return a+"-"+d(b).className}function X(b,g,a,c){var e=Y(g),k=e+" "+a,l=u[k]?++u[k].total:
0,h={};if(0<l){var p=a+"-stagger",h=e+" "+p;(e=!u[h])&&g.addClass(p);h=V(g,h);e&&g.removeClass(p)}c=c||function(b){return b()};g.addClass(a);var p=g.data(E)||{},r=c(function(){return V(g,k)});c=r.transitionDuration;e=r.animationDuration;if(0===c&&0===e)return g.removeClass(a),!1;g.data(E,{running:p.running||0,itemIndex:l,stagger:h,timings:r,closeAnimationFn:f.noop});b=0<p.running||"setClass"==b;0<c&&M(g,a,b);0<e&&(d(g).style[P]="none 0s");return!0}function M(b,a,c){"ng-enter"!=a&&("ng-move"!=a&&"ng-leave"!=
a)&&c?b.addClass(ga):d(b).style[O+H]="none"}function R(b,a){var c=O+H,e=d(b);e.style[c]&&0<e.style[c].length&&(e.style[c]="");b.removeClass(ga)}function S(b){var a=P;b=d(b);b.style[a]&&0<b.style[a].length&&(b.style[a]="")}function Z(b,a,c,e){function f(b){a.off(w,k);a.removeClass(l);A(a,c);b=d(a);for(var e in q)b.style.removeProperty(q[e])}function k(b){b.stopPropagation();var a=b.originalEvent||b;b=a.$manualTimeStamp||a.timeStamp||Date.now();a=parseFloat(a.elapsedTime.toFixed($));Math.max(b-x,0)>=
u&&a>=s&&e()}var h=d(a);b=a.data(E);if(-1!=h.className.indexOf(c)&&b){var l="";D(c.split(" "),function(b,a){l+=(0<a?" ":"")+b+"-active"});var p=b.stagger,r=b.timings,n=b.itemIndex,s=Math.max(r.transitionDuration,r.animationDuration),t=Math.max(r.transitionDelay,r.animationDelay),u=t*y,x=Date.now(),w=ba+" "+aa,m="",q=[];if(0<r.transitionDuration){var z=r.transitionPropertyStyle;-1==z.indexOf("all")&&(m+=C+"transition-property: "+z+";",m+=C+"transition-duration: "+r.transitionDurationStyle+";",q.push(C+
"transition-property"),q.push(C+"transition-duration"))}0<n&&(0<p.transitionDelay&&0===p.transitionDuration&&(m+=C+"transition-delay: "+W(r.transitionDelayStyle,p.transitionDelay,n)+"; ",q.push(C+"transition-delay")),0<p.animationDelay&&0===p.animationDuration&&(m+=C+"animation-delay: "+W(r.animationDelayStyle,p.animationDelay,n)+"; ",q.push(C+"animation-delay")));0<q.length&&(r=h.getAttribute("style")||"",h.setAttribute("style",r+" "+m));a.on(w,k);a.addClass(l);b.closeAnimationFn=function(){f();
e()};h=(n*(Math.max(p.animationDelay,p.transitionDelay)||0)+(t+s)*v)*y;b.running++;B(a,h);return f}e()}function W(b,a,c){var d="";D(b.split(","),function(b,e){d+=(0<e?",":"")+(c*a+parseInt(b,10))+"s"});return d}function a(b,a,c,d){if(X(b,a,c,d))return function(b){b&&A(a,c)}}function c(b,a,c,d){if(a.data(E))return Z(b,a,c,d);A(a,c);d()}function e(b,g,d,e){var f=a(b,g,d);if(f){var h=f;J(g,function(){R(g,d);S(g);h=c(b,g,d,e)});return function(b){(h||w)(b)}}e()}function A(b,a){b.removeClass(a);var c=
b.data(E);c&&(c.running&&c.running--,c.running&&0!==c.running||b.removeData(E))}function k(b,a){var c="";b=f.isArray(b)?b:b.split(/\s+/);D(b,function(b,d){b&&0<b.length&&(c+=(0<d?" ":"")+b+a)});return c}var C="",O,aa,P,ba;z.ontransitionend===T&&z.onwebkittransitionend!==T?(C="-webkit-",O="WebkitTransition",aa="webkitTransitionEnd transitionend"):(O="transition",aa="transitionend");z.onanimationend===T&&z.onwebkitanimationend!==T?(C="-webkit-",P="WebkitAnimation",ba="webkitAnimationEnd animationend"):
(P="animation",ba="animationend");var ea="Duration",H="Property",ha="Delay",K="IterationCount",G="$$ngAnimateKey",E="$$ngAnimateCSS3Data",ga="ng-animate-block-transitions",$=3,v=1.5,y=1E3,u={},q=0,m=[],I,L=null,N=0,Q=[];return{enter:function(b,a){return e("enter",b,"ng-enter",a)},leave:function(b,a){return e("leave",b,"ng-leave",a)},move:function(a,c){return e("move",a,"ng-move",c)},beforeSetClass:function(b,c,d,e){var f=k(d,"-remove")+" "+k(c,"-add"),h=a("setClass",b,f,function(a){var e=b.attr("class");
b.removeClass(d);b.addClass(c);a=a();b.attr("class",e);return a});if(h)return J(b,function(){R(b,f);S(b);e()}),h;e()},beforeAddClass:function(b,c,d){var e=a("addClass",b,k(c,"-add"),function(a){b.addClass(c);a=a();b.removeClass(c);return a});if(e)return J(b,function(){R(b,c);S(b);d()}),e;d()},setClass:function(a,d,e,f){e=k(e,"-remove");d=k(d,"-add");return c("setClass",a,e+" "+d,f)},addClass:function(a,d,e){return c("addClass",a,k(d,"-add"),e)},beforeRemoveClass:function(b,c,d){var e=a("removeClass",
b,k(c,"-remove"),function(a){var d=b.attr("class");b.removeClass(c);a=a();b.attr("class",d);return a});if(e)return J(b,function(){R(b,c);S(b);d()}),e;d()},removeClass:function(a,d,e){return c("removeClass",a,k(d,"-remove"),e)}}}])}])})(window,window.angular);
//# sourceMappingURL=angular-animate.min.js.map

View File

@ -16,6 +16,7 @@
angularRoute: 'angular/angular-route.min',
angularTouch: 'angular/angular-touch.min',
angularSanitize: 'angular/angular-sanitize.min',
angularAnimate: 'angular/angular-animate.min',
uuid: 'uuid/uuid',
forge: 'forge/forge.min',
punycode: 'punycode.min',
@ -41,6 +42,10 @@
exports: 'angular',
deps: ['angular']
},
angularAnimate: {
exports: 'angular',
deps: ['angular']
},
iscroll: {
exports: 'IScroll'
},

View File

@ -4,6 +4,9 @@
*:before,
*:after {
box-sizing: border-box;
// remove flickering on item touch selection in ios
-webkit-tap-highlight-color: transparent !important;
}
// Body reset
@ -34,6 +37,24 @@ textarea {
-moz-osx-font-smoothing: grayscale;
}
// Custom scrollbars in webkit
// @see http://css-tricks.com/custom-scrollbars-in-webkit/
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: $color-grey-lighter;
border: 3px solid transparent;
background-clip: content-box;
cursor: pointer !important;
&:hover {
background-color: $color-blue;
}
}
// add space at the top since ios7 apps are now fullscreen
.ios-spacer {
padding-top: 20px;
@ -45,14 +66,4 @@ textarea {
// allow text selection
user-select: none;
// make scrollbars invisible
::-webkit-scrollbar {
width: 0px;
}
// remove flickering on item touch selection in ios
* {
-webkit-tap-highlight-color: transparent !important;
}
}

View File

@ -51,7 +51,6 @@ $lightbox-padding: 15px;
$lightbox-max-width: 662px;
$lightbox-width: 90%;
$lightbox-min-height: 644px;
$lightbox-top: 7.5%;
$btn-back-color: $color-blue;
$btn-color: #fff;

View File

@ -3,6 +3,11 @@
@import "functions";
@import "grid";
// Third party libs
@import "lib/angular-csp"; // use angular csp specific classes
@import "lib/scut";
// Bootstrap
@import "normalize";
@import "variables"; // Modify this for custom colors, font-sizes, etc
@import "fonts";
@ -20,6 +25,8 @@
@import "components/layout";
@import "components/popover";
@import "components/input";
@import "components/mail-addresses";
@import "components/spinner";
// Views
@import "views/shared";
@ -33,4 +40,4 @@
@import "views/mail-list";
@import "views/read";
@import "views/write";
@import "views/login";
@import "views/login";

View File

@ -46,6 +46,14 @@
}
}
.label-blank {
background-color: transparent;
color: $color-black;
text-align: left;
padding-left: 0;
padding-right: 0;
}
.label-light {
background-color: $label-light-back-color;
color: $label-light-color;

View File

@ -5,22 +5,27 @@
width: 100%;
height: 100%;
z-index: 2000;
visibility: hidden;
backface-visibility: hidden;
@include respond-to(desktop) {
top: $lightbox-top;
margin: 0 auto;
width: $lightbox-width;
max-width: $lightbox-max-width;
max-height: $lightbox-min-height;
height: auto;
text-align: left;
}
.lightbox-body {
position: relative;
height: 100%;
padding: $lightbox-padding;
background: #fff;
backface-visibility: hidden;
@include respond-to(desktop) {
max-height: $lightbox-min-height;
}
header {
text-align: center;
position: relative;
@ -52,36 +57,54 @@
}
}
.lightbox-overlay.show .lightbox {
visibility: visible;
}
.lightbox-overlay {
position: absolute;
width: 100%;
height: 100%;
visibility: hidden;
display: none;
top: 0;
left: 0;
z-index: 1000;
opacity: 0;
background: $color-grey-dark-alpha;
transition: all 0.3s;
}
.lightbox-overlay.show {
opacity: 1;
visibility: visible;
}
@include respond-to(desktop) {
@include scut-vcenter-ib(".lightbox");
text-align: center;
}
// effect
.lightbox-effect .lightbox-body {
transform: scale(0.7);
opacity: 0;
transition: all 0.3s;
}
&.show {
display: block;
}
.lightbox-overlay.show .lightbox-effect .lightbox-body {
transform: scale(1);
opacity: 1;
/* ngAnimate */
&.show-add {
display: block;
opacity: 0;
transition: opacity 0.3s;
.lightbox-body {
transform: scale(0.7);
transition: transform 0.3s;
}
}
&.show-add-active {
opacity: 1;
.lightbox-body {
transform: scale(1);
}
}
&.show-remove {
display: block;
opacity: 1;
transition: opacity 0.3s;
.lightbox-body {
transform: scale(1);
transition: transform 0.3s;
}
}
&.show-remove-active {
opacity: 0;
.lightbox-body {
transform: scale(0.7);
}
}
}

View File

@ -0,0 +1,17 @@
.mail-addresses {
p {
margin: 0.4em 0 0.2em;
cursor: text;
}
.label {
margin-bottom: 0.2em;
margin-right: 0.2em;
}
label {
display: inline-block;
width: 2.75em;
color: $color-grey;
}
}

View File

@ -19,6 +19,10 @@
cursor: pointer;
transition: background-color $time-li-fade, color $time-li-fade;
&.ng-animate {
transition: none;
}
h3 {
@include text-overflow;

View File

@ -0,0 +1,22 @@
.spinner {
vertical-align: middle;
display: inline-block;
margin-right: 0.2em;
height: 1em;
width: 1em;
animation: spinner-rotation .6s linear infinite;
border-left: 0.17em solid $color-grey-light;
border-right: 0.17em solid $color-grey-light;
border-bottom: 0.17em solid $color-grey-light;
border-top: 0.17em solid $color-grey;
border-radius: 100%;
}
@keyframes spinner-rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}

18
src/sass/lib/_angular-csp.scss Executable file
View File

@ -0,0 +1,18 @@
/* Include this file in your html if you are using the CSP mode. */
@charset "UTF-8";
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
.ng-cloak, .x-ng-cloak,
.ng-hide {
display: none !important;
}
ng\:form {
display: block;
}
.ng-animate-block-transitions {
transition:0s all!important;
-webkit-transition:0s all!important;
}

1485
src/sass/lib/_scut.scss Normal file

File diff suppressed because it is too large Load Diff

View File

@ -15,9 +15,13 @@
}
.key-list {
max-height: 400px;
position: relative;
margin: 20px;
overflow-y: scroll;
.key-list-scroll {
max-height: 400px;
overflow-y: scroll;
}
table {
th, td {
@ -37,6 +41,11 @@
outline: none;
}
}
// pull popover upwards to keep popup inside lightbox
.popover {
margin-top: -20px;
}
}
}

View File

@ -3,7 +3,6 @@
@include respond-to(desktop) {
max-width: 350px;
top: 30%;
}
p {

View File

@ -63,32 +63,14 @@
}
}
.spinner {
display:none;
}
&.syncing {
.spinner {
top: 6.5px;
left: $padding-horizontal;
height: 13px;
width: 13px;
position: absolute;
animation: rotation .6s linear infinite;
border-left: 2px solid $color-grey-light;
border-right: 2px solid $color-grey-light;
border-bottom: 2px solid $color-grey-light;
border-top: 2px solid $color-grey;
border-radius: 100%;
}
.text {
padding-left: 1.5em;
display: inline-block;
}
}
}
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}

View File

@ -1,19 +1,19 @@
.view-read {
display: flex;
flex-direction: column;
margin: 0px;
padding: 10px 15px;
height: 100%;
width: 100%;
color: $color-grey-dark;
.headers {
flex-shrink: 0;
margin-bottom: 1em;
p {
margin: 0px;
padding: 0px;
}
.subject {
font-size: $font-size-bigger;
margin: 0;
}
.date {
@ -21,24 +21,31 @@
font-size: $font-size-small;
margin-top: 0.25em;
margin-bottom: 1.5em;
padding: 0px;
}
.address {
color: $color-grey;
padding: 0.2em 0;
.mail-addresses {
.label {
margin-left: 0.3em;
cursor: pointer;
}
&:hover {
cursor: pointer;
}
p {
margin-top: 0.2em;
margin-bottom: 0;
}
}
.controls {
float: right;
margin: 0 15px 10px;
button {
margin-left: 7px;
}
}
}
.attachments {
flex-shrink: 0;
position: relative;
width: inherit;
border: 1px;
@ -72,54 +79,30 @@
}
.seperator-line {
flex-shrink: 0;
height: 1px;
color: $color-grey-lighter;
background-color: $color-grey-lighter;
}
.body {
flex-grow: 1;
position: relative;
margin-top: 1.75em;
cursor: text;
padding-bottom: 250px;
line-height: 1.5em;
height: 100%;
overflow-y: scroll;
user-select: text;
.working {
margin: 0 auto;
height: 100%;
width: 230px;
display: table;
@include scut-vcenter-tt;
width: 100%;
text-align: center;
font-size: 2em;
.container {
display: table-cell;
strong {
color: $color-grey-input;
vertical-align: middle;
.spinner {
position: relative;
div {
position: absolute;
top: 0;
left: 0;
height: 30px;
width: 30px;
animation: rotation .6s linear infinite;
border-left: 5px solid $color-grey-light;
border-right: 5px solid $color-grey-light;
border-bottom: 5px solid $color-grey-light;
border-top: 5px solid $color-grey;
border-radius: 100%;
}
}
h1 {
margin: 0;
padding-left: 40px;
line-height: 30px;
color: $color-grey-input;
}
}
}
@ -137,6 +120,7 @@
}
iframe {
flex-grow: 1;
width: 100%;
}
@ -145,43 +129,39 @@
padding: 0;
}
table {
ul {
list-style: none;
margin: 0;
}
li {
border-bottom: 1px solid $color-grey-lighter;
&:last-child {
border-bottom: 0;
}
}
button {
display: block;
background: none;
width: 100%;
border: 0;
outline: 0;
padding: 0.5em 1em 0.5em 0.3em;
color: $color-blue;
user-select: none;
transition: background-color 0.3s;
text-align: left;
tr {
&:hover,
&:focus {
background-color: darken($color-white, 2%);
cursor: pointer;
}
&.seperator {
border-bottom: 1px solid $color-grey-lighter;
}
&:before {
display: inline-block;
width: 2.5em;
text-align: center;
vertical-align: middle;
}
td {
padding: 7px 5px;
&.left {
padding-left: 15px;
padding-top: 10px;
}
&.right {
padding-right: 15px;
}
&:hover,
&:focus {
background-color: darken($color-white, 2%);
}
}
}
}
.controls {
float: right;
margin: 10px 15px;
button {
margin-left: 7px;
}
}

View File

@ -1,4 +1,6 @@
.view-write {
display: flex;
flex-direction: column;
margin: 0px;
padding: 0px;
color: $color-grey-dark;
@ -6,7 +8,8 @@
height: 100%;
@include respond-to(desktop) {
height: 600px;
// this number depends on max-height set on .lightbox
height: 590px; // magic number
}
input {
@ -18,26 +21,35 @@
border: 0!important;
}
.headers {
.mail-addresses {
flex-shrink: 0;
margin-top: 10px;
}
p {
margin: 0.2em 0;
padding: 0.2em 0;
cursor: text;
}
.mail-addresses-more {
float: right;
margin: 0.4em 0;
span {
color: $color-grey;
}
button {
display: inline-block;
background: none;
padding: 0 0.5em;
margin: 0;
text-decoration: none;
color: $color-black;
transition: color 0.3s;
outline: 0;
input {
margin-left: 0.3em;
width: 80%;
&:hover,
&:focus {
color: $color-blue;
text-decoration: underline;
}
}
}
.subject-box {
flex-shrink: 0;
position: relative;
margin: 20px 0 7px 0;
width: inherit;
@ -47,7 +59,6 @@
height: em(44);
.subject-line {
float: left;
padding: 10px;
width: 80%;
color: $color-grey;
@ -82,6 +93,7 @@
}
.attachments-box {
flex-shrink: 0;
position: relative;
margin: 0 0 5px 0;
width: inherit;
@ -126,13 +138,10 @@
}
.body {
flex-grow: 1;
line-height: 1.5em;
cursor: text;
@include respond-to(desktop) {
height: 445px;
overflow-y: scroll;
}
overflow-y: scroll;
*[contentEditable] {
outline: 0px;

View File

@ -25,11 +25,6 @@
</div>
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>
<!-- popovers -->
<div id="google-info" class="popover right" ng-controller="PopoverCtrl">
<div class="arrow"></div>

View File

@ -16,30 +16,34 @@
</div>
<div class="key-list">
<table>
<tr>
<th>Key ID</th>
<th>Email</th>
<th>Created</th>
<th>Size</th>
</tr>
<tr ng-repeat="key in keys | orderBy:'userId' | filter:searchText">
<td class="hover" ng-mouseover="getFingerprint(key)" popover="#fingerprint-contact">{{key._id.slice(8)}}</td>
<td>{{key.userId}}</td>
<td>{{key.created | date:'mediumDate'}}</td>
<td>{{key.bitSize}} bit</td>
<td><button class="remove" ng-click="removeKey(key)">&#xe007;</button></td>
</tr>
</table>
</div>
<div class="key-list-scroll">
<table>
<tr>
<th>Key ID</th>
<th>Email</th>
<th>Created</th>
<th>Size</th>
</tr>
<tr ng-repeat="key in keys | orderBy:'userId' | filter:searchText">
<td class="hover" ng-mouseover="getFingerprint(key)" popover="#fingerprint-contact">{{key._id.slice(8)}}</td>
<td>{{key.userId}}</td>
<td>{{key.created | date:'mediumDate'}}</td>
<td>{{key.bitSize}} bit</td>
<td><button class="remove" ng-click="removeKey(key)">&#xe007;</button></td>
</tr>
</table>
</div><!--/.key-list-scroll-->
<!-- popovers -->
<div id="fingerprint-contact" class="popover right" ng-controller="PopoverCtrl">
<div class="popover-title"><b>Fingerprint</b></div>
<div class="popover-content">{{fingerprint}}</div>
</div><!--/.popover-->
</div><!--/.key-list-->
</div><!-- /.view-contacts -->
</div><!-- /.content -->
<!-- popovers -->
<div id="fingerprint-contact" class="popover right" ng-controller="PopoverCtrl">
<div class="popover-title"><b>Fingerprint</b></div>
<div class="popover-content">{{fingerprint}}</div>
</div><!--/.popover-->
</div><!-- /.lightbox-body -->

View File

@ -19,21 +19,22 @@
</div><!--/.nav-container-->
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.writer.open}">
<div class="lightbox lightbox-effect" ng-include="'tpl/write.html'"></div>
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'write'}">
<div class="lightbox" ng-include="'tpl/write.html'"></div>
</div>
<div class="lightbox-overlay" ng-class="{'show': state.account.open}">
<div class="lightbox lightbox-effect" ng-include="'tpl/account.html'"></div>
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'account'}">
<div class="lightbox" ng-include="'tpl/account.html'"></div>
</div>
<div class="lightbox-overlay" ng-class="{'show': state.setPassphrase.open}">
<div class="lightbox lightbox-effect" ng-include="'tpl/set-passphrase.html'"></div>
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'set-passphrase'}">
<div class="lightbox" ng-include="'tpl/set-passphrase.html'"></div>
</div>
<div class="lightbox-overlay" ng-class="{'show': state.contacts.open}">
<div class="lightbox lightbox-effect" ng-include="'tpl/contacts.html'"></div>
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'contacts'}">
<div class="lightbox" ng-include="'tpl/contacts.html'"></div>
</div>
<div class="lightbox-overlay" ng-class="{'show': state.about.open}">
<div class="lightbox lightbox-effect view-about" ng-include="'tpl/about.html'"></div>
</div>
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
<div class="lightbox-overlay" ng-class="{'show': state.lightbox === 'about'}">
<div class="lightbox view-about" ng-include="'tpl/about.html'"></div>
</div>

View File

@ -18,11 +18,6 @@
</div><!--/content-->
</div>
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>
<!-- popovers -->
<div id="passphrase-info" class="popover right" ng-controller="PopoverCtrl">
<div class="arrow"></div>

View File

@ -33,11 +33,6 @@
</div><!--/content-->
</div>
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>
<!-- popovers -->
<div id="passphrase-info" class="popover right" ng-controller="PopoverCtrl">
<div class="arrow"></div>

View File

@ -5,7 +5,7 @@
<div class="content">
<p><b>Import keyfile.</b> To access your emails on this device, please import your existing key file.</p>
<form>
<div>
<input type="file" accept=".asc" file-reader tabindex="1">
@ -20,11 +20,6 @@
</div>
</div>
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>
<!-- popovers -->
<div id="keyfile-info" class="popover right" ng-controller="PopoverCtrl">
<div class="arrow"></div>

View File

@ -8,9 +8,4 @@
<p><b>Login.</b> Authenticating with the mail server...</p>
</div><!--/content-->
</div>
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>

View File

@ -8,7 +8,7 @@
<input class="input-text input-search" type="search" results="5" ng-model="searchText" placeholder=" Filter..." focus-me="state.mailList.searching">
</div>
<div class="list-wrapper" ng-iscroll="filteredMessages.length">
<div class="list-wrapper" ng-iscroll="filteredMessages">
<ul class="mail-list">
<li ng-class="{'mail-list-active': email === state.mailList.selected, 'mail-list-attachment': email.attachments !== undefined && email.attachments.length > 0, 'mail-list-unread': email.unread, 'mail-list-replied': !email.unread && email.answered}" ng-click="select(email)" ng-repeat="email in (filteredMessages = (state.nav.currentFolder.messages | filter:searchText | orderBy:'uid':true | limitTo:100))">
<h3>{{email.from[0].name || email.from[0].address}}</h3>

View File

@ -1,38 +1,43 @@
<div class="controls">
<button ng-click="state.mailList.remove(state.mailList.selected)" class="btn-icon" title="Delete mail">&#xe005;</button>
<button class="btn-icon" title="Reply to" reply-selection>&#xe002;</button>
<button ng-click="state.writer.write()" class="btn-icon" title="New mail">&#xe006;</button>
</div><!--/.controls-->
<div class="view-read" ng-controller="ReadCtrl">
<div class="headers">
<div class="controls">
<button ng-click="state.mailList.remove(state.mailList.selected)" class="btn-icon" title="Delete mail">&#xe005;</button>
<button class="btn-icon" title="Reply to" reply-selection>&#xe002;</button>
<button ng-click="state.writer.write()" class="btn-icon" title="New mail">&#xe006;</button>
</div><!--/.controls-->
<p class="subject" ng-click="state.read.toggle(false)">{{(state.mailList.selected.subject) ? state.mailList.selected.subject : 'No subject'}}</p>
<p class="date">{{state.mailList.selected.sentDate | date:'EEEE, MMM d, yyyy h:mm a'}}</p>
<p class="address">
From: <span ng-repeat="u in state.mailList.selected.from" class="label" ng-class="{'label-primary': u.secure === false, 'label-primary-click': u.secure === false}" data-icon-append="{{(u.secure === false) ? '&#xe001;' : ''}}" ng-mouseover="getKeyId(u.address)" ng-click="invite(u)" popover="#fingerprint-info">{{u.name || u.address}}</span>
</p>
<p class="address">
To: <span ng-repeat="u in state.mailList.selected.to" class="label" ng-class="{'label-primary': u.secure === false, 'label-primary-click': u.secure === false}" data-icon-append="{{(u.secure === false) ? '&#xe001;' : ''}}" ng-mouseover="getKeyId(u.address)" ng-click="invite(u)" popover="#fingerprint-info">{{u.name || u.address}}</span>
</p>
<div ng-switch="state.mailList.selected.cc && state.mailList.selected.cc.length > 0">
<p class="address" ng-switch-when="true">
Cc: <span ng-repeat="u in state.mailList.selected.cc" class="label" ng-class="{'label-primary': u.secure === false, 'label-primary-click': u.secure === false}" data-icon-append="{{(u.secure === false) ? '&#xe001;' : ''}}" ng-mouseover="getKeyId(u.address)" ng-click="invite(u)" popover="#fingerprint-info">{{u.name || u.address}}</span>
<div class="mail-addresses">
<p>
<label>From:</label>
<span ng-repeat="u in state.mailList.selected.from">
<span class="label" ng-class="{'label-primary': u.secure === false, 'label-primary-click': u.secure === false}" data-icon-append="{{(u.secure === false) ? '&#xe001;' : ''}}" ng-mouseover="getKeyId(u.address)" ng-click="invite(u)" popover="#fingerprint-info">{{u.name || u.address}}</span>
</span>
</p>
</div>
<p>
<label>To:</label>
<span ng-repeat="u in state.mailList.selected.to">
<span class="label" ng-class="{'label-primary': u.secure === false, 'label-primary-click': u.secure === false}" data-icon-append="{{(u.secure === false) ? '&#xe001;' : ''}}" ng-mouseover="getKeyId(u.address)" ng-click="invite(u)" popover="#fingerprint-info">{{u.name || u.address}}</span>
</span>
</p>
<p 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-primary': u.secure === false, 'label-primary-click': u.secure === false}" data-icon-append="{{(u.secure === false) ? '&#xe001;' : ''}}" ng-mouseover="getKeyId(u.address)" ng-click="invite(u)" popover="#fingerprint-info">{{u.name || u.address}}</span>
</span>
</p>
</div><!--/.mail-addresses-->
</div><!--/.headers-->
<div ng-switch="state.mailList.selected.attachments !== undefined && state.mailList.selected.attachments.length > 0">
<div ng-switch-when="true">
<div class="attachments">
<span class="attachment" ng-repeat="attachment in state.mailList.selected.attachments" ng-click="download(attachment)">
<span data-icon="&#xe003;"></span>
{{attachment.filename}}
</span><!--/.attachment-->
</div><!--/.attachments-->
</div>
<div ng-switch-default>
<div class="seperator-line"></div>
</div>
<div ng-switch-when="true" class="attachments">
<span class="attachment" ng-repeat="attachment in state.mailList.selected.attachments" ng-click="download(attachment)">
<span data-icon="&#xe003;"></span>
{{attachment.filename}}
</span><!--/.attachment-->
</div><!--/.attachments-->
<div ng-switch-default class="seperator-line"></div>
</div><!--/.ng-switch-->
<div class="body" ng-switch="state.mailList.selected === undefined || (state.mailList.selected.encrypted === false && state.mailList.selected.body !== undefined) || (state.mailList.selected.encrypted === true && state.mailList.selected.decrypted === true)">
@ -41,17 +46,12 @@
<div class="line" ng-repeat="line in state.mailList.selected.body.split('\n') track by $index" ng-class="{'empty-line': lineEmpty(line)}">
<span ng-bind-html="line | createAnchors"></span>
<br>
</div>
</div>
</div><!--/.line-->
</div><!--/ng-switch-when-->
<div class="working" ng-switch-default>
<div class="container">
<div class="spinner"><div></div></div>
<span ng-switch="state.mailList.selected.loadingBody === true || state.mailList.selected.body === undefined || state.mailList.selected.body === null">
<h1 ng-switch-when="true">Loading...</h1>
<h1 ng-switch-default>Decrypting...</h1>
</span>
</div><!--/.container-->
</div>
<span class="spinner"></span>
<strong ng-bind="(state.mailList.selected.loadingBody === true || state.mailList.selected.body === undefined || state.mailList.selected.body === null) ? 'Loading...' : 'Decrypting...'"></strong>
</div><!--/.working-->
</div><!--/.body-->
<!-- popovers -->
@ -62,21 +62,10 @@
<div class="reply-selection popover bottom">
<div class="arrow"></div>
<div class="popover-content">
<table>
<tr class="seperator" ng-click="state.writer.write(state.mailList.selected)">
<td class="left" data-icon="&#xe014;"></td>
<td class="right">Reply</td>
</tr>
<tr class="seperator" ng-click="state.writer.write(state.mailList.selected, true)">
<td class="left" data-icon="&#xe013;"></td>
<td class="right">Reply All</td>
</tr>
<tr ng-click="state.writer.write(state.mailList.selected, null, true)">
<td class="left" data-icon="&#xe015;"></td>
<td class="right">Forward</td>
</tr>
</table>
</div>
<ul class="popover-content">
<li><button data-icon="&#xe014;" ng-click="state.writer.write(state.mailList.selected)">Reply</button></li>
<li><button data-icon="&#xe013;" ng-click="state.writer.write(state.mailList.selected, true)">Reply All</button></li>
<li><button data-icon="&#xe015;" ng-click="state.writer.write(state.mailList.selected, null, true)">Forward</button></li>
</ul>
</div><!--/.reply-selection-->
</div><!--/.view-read-->

View File

@ -6,20 +6,30 @@
<div class="content">
<div class="view-write">
<div class="headers">
<div class="mail-addresses">
<div class="mail-addresses-more">
<button ng-click="showCC = true;" ng-hide="showCC">Cc</button>
<button ng-click="showBCC = true;" ng-hide="showBCC">Bcc</button>
</div>
<p field="to">
<span>To:</span>
<label>To:</label>
<span ng-repeat="recipient in to track by $index">
<input id="to{{$index}}" value="{{recipient.address}}" ng-model="recipient.address" ng-trim="false" ng-class="{'label': recipient.secure === true, 'label label-primary': recipient.secure === false && recipient.valid !== true}" auto-size="recipient.address" spellcheck="false" ng-change="onAddressUpdate(to, $index)" address-input="to" tabindex="1" ng-mouseover="getKeyId(recipient)" focus-me="state.writer.open && writerTitle !== 'Reply'">
<input id="to{{$index}}" value="{{recipient.address}}" ng-model="recipient.address" ng-trim="false" class="label" ng-class="{'label-blank': !recipient.address || recipient.secure === undefined, 'label-primary': recipient.secure === false}" auto-size="recipient.address" spellcheck="false" ng-change="onAddressUpdate(to, $index)" address-input="to" tabindex="1" ng-mouseover="getKeyId(recipient)" focus-me="state.lightbox === 'write' && writerTitle !== 'Reply'">
</span>
</p>
<p field="cc">
<span>Cc:</span>
<p field="cc" ng-show="showCC === true">
<label>Cc:</label>
<span ng-repeat="recipient in cc track by $index">
<input id="cc{{$index}}" value="{{recipient.address}}" ng-model="recipient.address" ng-trim="false" ng-class="{'label': recipient.secure === true, 'label label-primary': recipient.secure === false && recipient.valid !== true}" auto-size="recipient.address" spellcheck="false" ng-change="onAddressUpdate(cc, $index)" address-input="cc" tabindex="1" ng-mouseover="getKeyId(recipient)">
<input id="cc{{$index}}" value="{{recipient.address}}" ng-model="recipient.address" ng-trim="false" class="label" ng-class="{'label-blank': !recipient.address || recipient.secure === undefined, 'label-primary': recipient.secure === false}" auto-size="recipient.address" spellcheck="false" ng-change="onAddressUpdate(cc, $index)" address-input="cc" tabindex="1" ng-mouseover="getKeyId(recipient)">
</span>
</p>
</div><!--/.address-headers-->
<p field="bcc" ng-show="showBCC === true">
<label>Bcc:</label>
<span ng-repeat="recipient in bcc track by $index">
<input id="bcc{{$index}}" value="{{recipient.address}}" ng-model="recipient.address" ng-trim="false" class="label" ng-class="{'label-blank': !recipient.address || recipient.secure === undefined, 'label-primary': recipient.secure === false}" auto-size="recipient.address" spellcheck="false" ng-change="onAddressUpdate(bcc, $index)" address-input="bcc" tabindex="1" ng-mouseover="getKeyId(recipient)">
</span>
</p>
</div><!--/.mail-addresses-->
<div class="subject-box">
<div class="subject-line">
@ -44,9 +54,9 @@
</div><!--/ng-switch-->
<div class="body" focus-child>
<p ng-model="body" contentEditable="true" spellcheck="false" ng-change="updatePreview()" tabindex="3" focus-me="state.writer.open && writerTitle === 'Reply'"></p>
<p ng-model="body" contentEditable="true" spellcheck="false" ng-change="updatePreview()" tabindex="3" focus-me="state.lightbox === 'write' && writerTitle === 'Reply'"></p>
<div class="encrypt-preview" ng-class="{'invisible': !ciphertextPreview || !sendBtnSecure}">
<div class="encrypt-preview" ng-show="ciphertextPreview && sendBtnSecure">
<p>-----BEGIN ENCRYPTED PREVIEW-----<br>{{ciphertextPreview}}<br>-----END ENCRYPTED PREVIEW-----</p>
</div><!--/.encrypt-preview-->
</div><!--/.body-->

View File

@ -79,7 +79,7 @@ define(function(require) {
})).yields();
scope.onError = function(err) {
expect(err.title).to.equal('Success');
expect(scope.state.account.open).to.be.false;
expect(scope.state.lightbox).to.equal(undefined);
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(dl.createDownload.calledOnce).to.be.true;
dl.createDownload.restore();

View File

@ -40,7 +40,6 @@ define(function(require) {
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.fingerprint).to.equal('XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX');
expect(scope.state.contacts.open).to.be.false;
expect(scope.state.contacts.toggle).to.exist;
});
});

View File

@ -97,10 +97,13 @@ define(function(require) {
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(new Error('asd'));
scope.confirmPassphrase();
scope.onError = function(err) {
expect(err.message).to.equal('asd');
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
};
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
scope.confirmPassphrase();
});
});
});

View File

@ -176,10 +176,14 @@ define(function(require) {
expect(state).to.equal(1);
expect(emailDaoMock.unlock.calledOnce).to.be.true;
scope.setState.restore();
done();
}
});
scope.onError = function(err) {
expect(err.message).to.equal('asd');
done();
};
scope.confirmPassphrase();
});
});

View File

@ -98,7 +98,7 @@ define(function(require) {
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
it('should not work when keypair upload fails', function() {
it('should not work when keypair upload fails', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
@ -113,6 +113,11 @@ define(function(require) {
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
@ -120,7 +125,7 @@ define(function(require) {
expect(keychainMock.putUserKeyPair.calledOnce).to.be.true;
});
it('should not work when unlock fails', function() {
it('should not work when unlock fails', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
@ -134,6 +139,11 @@ define(function(require) {
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(scope.incorrect).to.be.true;
@ -141,13 +151,18 @@ define(function(require) {
expect(emailDaoMock.unlock.calledOnce).to.be.true;
});
it('should not work when keypair retrieval', function() {
it('should not work when keypair retrieval', function(done) {
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields({
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;

View File

@ -63,10 +63,8 @@ define(function(require) {
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.state).to.exist;
expect(scope.state.nav.open).to.be.false;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.account.folders).to.not.be.empty;
expect(scope.onError).to.exist;
expect(scope.openFolder).to.exist;
});
});

View File

@ -55,7 +55,7 @@ define(function(require) {
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.state.writer).to.exist;
expect(scope.state.writer.open).to.be.false;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.state.writer.write).to.exist;
expect(scope.state.writer.close).to.exist;
expect(scope.verify).to.exist;
@ -68,11 +68,11 @@ define(function(require) {
describe('close', function() {
it('should close the writer', function() {
scope.state.writer.open = true;
scope.state.lightbox = 'write';
scope.state.writer.close();
expect(scope.state.writer.open).to.be.false;
expect(scope.state.lightbox).to.be.undefined;
});
});
@ -200,7 +200,7 @@ define(function(require) {
keychainMock.getReceiverPublicKey.yields(null, {
userId: 'asdf@example.com'
});
scope.$apply = function() {
scope.$digest = function() {
expect(recipient.key).to.deep.equal({
userId: 'asdf@example.com'
});
@ -311,8 +311,7 @@ define(function(require) {
expect(outboxMock.put.calledOnce).to.be.true;
expect(emailDaoMock.sync.calledOnce).to.be.true;
expect(scope.state.writer.open).to.be.false;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.replyTo.answered).to.be.true;
});
});