diff --git a/src/chrome.html b/src/chrome.html index 4fadb62..b0d886a 100644 --- a/src/chrome.html +++ b/src/chrome.html @@ -12,8 +12,15 @@ - + +
+ + + + diff --git a/src/ios.html b/src/ios.html index b1b708d..a14c5a4 100644 --- a/src/ios.html +++ b/src/ios.html @@ -20,6 +20,13 @@ +
+ + + + diff --git a/src/js/app.js b/src/js/app.js index 3669788..18f12f1 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -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); diff --git a/src/js/controller/about.js b/src/js/controller/about.js index 7df3cfd..7e952a0 100644 --- a/src/js/controller/about.js +++ b/src/js/controller/about.js @@ -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; } }; diff --git a/src/js/controller/account.js b/src/js/controller/account.js index 5e0a5c5..3eceaaf 100644 --- a/src/js/controller/account.js +++ b/src/js/controller/account.js @@ -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; } }; diff --git a/src/js/controller/add-account.js b/src/js/controller/add-account.js index cbcf2c0..6c3c479 100644 --- a/src/js/controller/add-account.js +++ b/src/js/controller/add-account.js @@ -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) { diff --git a/src/js/controller/contacts.js b/src/js/controller/contacts.js index a49200a..db97fb6 100644 --- a/src/js/controller/contacts.js +++ b/src/js/controller/contacts.js @@ -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(); } }; diff --git a/src/js/controller/login-existing.js b/src/js/controller/login-existing.js index 9fd14b3..4fe0bd2 100644 --- a/src/js/controller/login-existing.js +++ b/src/js/controller/login-existing.js @@ -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; diff --git a/src/js/controller/login-initial.js b/src/js/controller/login-initial.js index c557af0..c6588ff 100644 --- a/src/js/controller/login-initial.js +++ b/src/js/controller/login-initial.js @@ -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, diff --git a/src/js/controller/login-new-device.js b/src/js/controller/login-new-device.js index 9125dc5..f3f274b 100644 --- a/src/js/controller/login-new-device.js +++ b/src/js/controller/login-new-device.js @@ -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; diff --git a/src/js/controller/login.js b/src/js/controller/login.js index 39fd912..ac96b47 100644 --- a/src/js/controller/login.js +++ b/src/js/controller/login.js @@ -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(); diff --git a/src/js/controller/mail-list.js b/src/js/controller/mail-list.js index 6b52512..b637929 100644 --- a/src/js/controller/mail-list.js +++ b/src/js/controller/mail-list.js @@ -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); + }); } }; }); diff --git a/src/js/controller/navigation.js b/src/js/controller/navigation.js index 1c918ee..44510f1 100644 --- a/src/js/controller/navigation.js +++ b/src/js/controller/navigation.js @@ -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(); }); }; }); diff --git a/src/js/controller/set-passphrase.js b/src/js/controller/set-passphrase.js index a280135..d1eb1b6 100644 --- a/src/js/controller/set-passphrase.js +++ b/src/js/controller/set-passphrase.js @@ -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; diff --git a/src/js/controller/write.js b/src/js/controller/write.js index a58a7ef..055e225 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -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); } diff --git a/src/js/util/error.js b/src/js/util/error.js index 05749a6..ba9b8bd 100644 --- a/src/js/util/error.js +++ b/src/js/util/error.js @@ -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; diff --git a/src/lib/angular/angular-animate.min.js b/src/lib/angular/angular-animate.min.js new file mode 100755 index 0000000..4502865 --- /dev/null +++ b/src/lib/angular/angular-animate.min.js @@ -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= +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 li { + float: $dir; + } + + @if $space { + & > li + li { + margin-#{$dir}: $space; + } + } + +} + +%scut-list-floated { + @include scut-list-floated; +} + +@function scut-autoOrValue ($val) { + @if $val == a or $val == auto { + @return auto; + } + @else { + @return $val; + } +} + +@mixin scut-coords ( + $coordinates: n n n n +) { + + $top: nth($coordinates, 1); + $right: nth($coordinates, 2); + $bottom: nth($coordinates, 3); + $left: nth($coordinates, 4); + + @if $top != n { + top: scut-autoOrValue($top); + } + @if $right != n { + right: scut-autoOrValue($right); + } + @if $bottom != n { + bottom: scut-autoOrValue($bottom); + } + @if $left != n { + left: scut-autoOrValue($left); + } + +} +@function scut-strip-unit ( + $num +) { + + @return $num / ($num * 0 + 1); + +} +// Depends on `scut-strip-unit`. + +$scut-em-base: 16 !default; + +@function scut-em ( + $pixels, + $base: $scut-em-base +) { + + // $base could be in em or px (no unit = px). + // Adjust accordingly to create a $divisor that + // serves as context for $pixels. + $multiplier: if(unit($base) == em, 16, 1); + $divisor: scut-strip-unit($base) * $multiplier; + + $em-vals: (); + @each $val in $pixels { + $val-in-ems: (scut-strip-unit($val) / $divisor) * 1em; + $em-vals: append($em-vals, $val-in-ems); + } + + @if length($em-vals) == 1 { + // return a single value instead of a list, + // so it can be used in calculations + @return nth($em-vals, 1); + } + @else { + @return $em-vals; + } + +} +// Depends on `scut-strip-unit`. + +$scut-rem-base: 16 !default; + +@function scut-rem ( + $pixels +) { + + $rem-vals: (); + @each $val in $pixels { + $val-in-rems: scut-strip-unit($val) / $scut-rem-base * 1rem; + $rem-vals: append($rem-vals, $val-in-rems); + } + + @if length($rem-vals) == 1 { + // return a single value instead of a list, + // so it can be used in calculations + @return nth($rem-vals, 1); + } + @else { + @return $rem-vals; + } + +} +@mixin scut-border ( + $style, + $sides: n y +) { + + @if length($sides) == 2 { + @if nth($sides, 1) != n { + border-top: $style; + border-bottom: $style; + } + @if nth($sides, 2) != n { + border-left: $style; + border-right: $style; + } + } + + @else if length($sides) == 4 { + @if nth($sides, 1) != n { + border-top: $style; + } + @if nth($sides, 2) != n { + border-right: $style; + } + @if nth($sides, 3) != n { + border-bottom: $style; + } + @if nth($sides, 4) != n { + border-left: $style; + } + } + + @else { + @warn "Scut-border requires a $sides argument of 2 or 4 values." + } + +} +@mixin scut-circle ( + $size, + $color: inherit +) { + + border-radius: 50%; + display: inline-block; + + @if $color == inherit { + // If user wants to inherit the color, + // take advantage of the fact that border + // color defaults to the text color of the element. + border-width: $size / 2; + border-style: solid; + height: 0; + width: 0; + } + @else { + // Otherwise, just use background-color. + background-color: $color; + height: $size; + width: $size; + } + +} +@mixin scut-color-swap ( + $off, + $on, + $duration: 0, + $bg: false +) { + + $transition-properties: null; + $off-is-list: type-of($off) == list; + $on-is-list: type-of($on) == list; + + // If $off IS a list, + // assign color and background-color. + @if $off-is-list { + color: nth($off, 1); + background-color: nth($off, 2); + $transition-properties: background-color, color; + } + + // If $off IS NOT a list and $bg is TRUE, + // assign background-color. + @else if $bg and not $off-is-list { + background-color: $off; + $transition-properties: background-color; + } + + // If $off IS NOT a list and $bg is FALSE, + // assign color. + @else { + color: $off; + $transition-properties: color; + } + + // Only set-up transition if $duration != 0. + @if $duration != 0 { + transition-property: $transition-properties; + transition-duration: $duration; + } + + &:hover, + &:focus { + + // $on is treated the same as $off, above. + @if $on-is-list { + color: nth($on, 1); + background-color: nth($on, 2); + } + + @else if $bg and not $on-is-list { + background-color: $on; + } + + @else { + color: $on; + } + } + +} +@mixin scut-hd-bp ( + $ratio: 1.3 +) { + + @media (-o-min-device-pixel-ratio: #{$ratio}/1), + (-webkit-min-device-pixel-ratio: #{$ratio}), + (min-resolution: #{round(96 * $ratio)}dpi) { + @content; + } + +} +@mixin scut-hide-visually { + + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + +} + +%scut-hide-visually { + @include scut-hide-visually; +} +@mixin scut-image-replace { + + text-indent: 102%; + white-space: nowrap; + overflow: hidden; + +} + +%scut-image-replace { + @include scut-image-replace; +} +// Depends on scut-rem and scut-strip-unit + +@mixin scut-rem-fallback ( + $pixels, + $property: font-size +) { + + $px-vals: null; + @each $val in $pixels { + $val-in-px: scut-strip-unit($val) * 1px; + $px-vals: append($px-vals, $val-in-px); + } + $rem-vals: scut-rem($pixels); + + #{$property}: $px-vals; + #{$property}: $rem-vals; + +} +@mixin scut-reset-border-box { + // Make everything a border-box, because why not? + *, *:before, *:after { + -moz-box-sizing: border-box; + box-sizing: border-box; + } +} + +@mixin scut-reset-antialias { + // Antialias! + body { + -webkit-font-smoothing: antialiased; + } +} + +@mixin scut-reset-semanticize { + // Make headers and semantic, not presentational. + h1, + h2, + h3, + h4, + h5, + h6 { + font-size: 1em; + font-weight: normal; + margin: 0; + } + b { + font-weight: normal; + } +} + +@mixin scut-reset-pointer { + // Clickable form elements should have a pointer. + label, + select, + option, + button { + cursor: pointer; + } +} + +@mixin scut-reset-form { + fieldset { + border: 0; + margin: 0; + padding: 0; + } + textarea { + resize: vertical; + } +} + +@mixin scut-reset-button { + // Reset default button styles, which are never used. + button, + input[type="button"], + input[type="submit"], + input[type="reset"] { + background: transparent; + border: 0; + color: inherit; + font: inherit; + margin: 0; + outline: none; + padding: 0; + width: auto; + -webkit-appearance: none; + -webkit-font-smoothing: antialiased; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + &::-moz-focus-inner { + padding: 0; + border: 0; + } + } +} + +@mixin scut-reset-paragraph { + // Some paragraph margins just get in the way. + p:first-of-type { + margin-top: 0; + } + p:last-of-type { + margin-bottom: 0; + } +} + +@mixin scut-reset-media { + // You want these elements fluid, probably. + img, + video { + max-width: 100%; + height: auto; + } +} + +@mixin scut-reset-figure { + // Remove default margins: + figure { + margin: 0; + } +} + +// Call them all, minus exclusions! +@mixin scut-reset ($exclude: false) { + @if not index($exclude, border-box) { + @include scut-reset-border-box; + } + @if not index($exclude, antialias) { + @include scut-reset-antialias; + } + @if not index($exclude, semanticize) { + @include scut-reset-semanticize; + } + @if not index($exclude, pointer) { + @include scut-reset-pointer; + } + @if not index($exclude, form) { + @include scut-reset-form; + } + @if not index($exclude, button) { + @include scut-reset-button; + } + @if not index($exclude, paragraph) { + @include scut-reset-paragraph; + } + @if not index($exclude, media) { + @include scut-reset-media; + } + @if not index($exclude, figure) { + @include scut-reset-figure; + } +} +@mixin scut-selected ( + $active: false +) { + + @if $active { + &:hover, + &:focus, + &:active { + @content; + } + } + @else { + &:hover, + &:focus { + @content; + } + } + +} +@mixin scut-triangle ( + $direction: right, + $size: 0.75em, + $color: inherit +) { + + display: inline-block; + height: 0; + width: 0; + // For improved appearance in some Webkit browsers + -webkit-transform: rotate(360deg); + + // Set up some variables + $width: null; + $height: null; + $border-widths: null; + + @if type-of($size) == list { + $width: nth($size, 1); + $height: nth($size, 2); + } + @else { + $width: $size; + $height: $size; + } + + @if ($direction == up) or ($direction == down) { + // For up and down, width gets two borders but height only one, + // so divide second border-width value by 2 + $border-widths: $height ($width / 2); + } + @else if ($direction == right) or ($direction == left) { + // For right and left, height gets two borders but width only one, + // so divide first border-width value by 2 + $border-widths: ($height / 2) $width; + } + @else { + // For right triangles (the rest), both sides get two borders, + // so divide both by 2 + $border-widths: ($height / 2) ($width / 2); + } + + border-width: $border-widths; + border-style: solid; + + + // STANDARD TRIANGLES + + @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { + border-color: transparent; + @if $direction == up { + border-bottom-color: $color; + border-top-width: 0; + } + @else if $direction == right { + border-left-color: $color; + border-right-width: 0; + } + @else if $direction == down { + border-top-color: $color; + border-bottom-width: 0; + } + @else if $direction == left { + border-right-color: $color; + border-left-width: 0; + } + } + + + // CORNER TRIANGLES + + @else if ($direction == top-right) or ($direction == top-left) { + border-top-color: $color; + border-bottom-color: transparent; + @if $direction == top-right { + border-left-color: transparent; + border-right-color: $color; + } + @else if $direction == top-left { + border-left-color: $color; + border-right-color: transparent; + } + } + + @else if ($direction == bottom-right) or ($direction == bottom-left) { + border-top-color: transparent; + border-bottom-color: $color; + @if $direction == bottom-right { + border-left-color: transparent; + border-right-color: $color; + } + @else if $direction == bottom-left { + border-left-color: $color; + border-right-color: transparent; + } + } + +} + +%scut-triangle { + @include scut-triangle; +} +@mixin scut-center-absolutely ( + $dimensions +) { + + $width: nth($dimensions, 1); + $height: nth($dimensions, 2); + + position: absolute; + + @if $width != n { + width: $width; + left: 50%; + margin-left: (-$width / 2); + } + + @if $height != n { + height: $height; + top: 50%; + margin-top: (-$height / 2); + } + +} +@mixin scut-center-block ( + $max-width: false +) { + + margin-left: auto; + margin-right: auto; + @if $max-width { + max-width: $max-width; + } + +} + +%scut-center-block { + @include scut-center-block; +} + +@mixin scut-center-transform ( + $axis: false // or x or y +) { + + position: absolute; + + @if $axis != x { + top: 50%; + margin-top: auto; + margin-bottom: auto; + } + + @if $axis != y { + left: 50%; + margin-left: auto; + margin-right: auto; + } + + $translate-val: null; + + @if not $axis { + $translate-val: translate(-50%, -50%); + } + @else if $axis != x { + $translate-val: translateY(-50%); + } + @else if $axis != y { + $translate-val: translateX(-50%); + } + + -webkit-transform: $translate-val; + -ms-transform: $translate-val; + transform: $translate-val; +} + +%scut-center-transform { + @include scut-center-transform; +} + +%scut-center-transform-x { + @include scut-center-transform(x); +} + +%scut-center-transform-y { + @include scut-center-transform(y); +} + +@mixin scut-fill ( + $width-height: false +) { + + position: absolute; + left: 0; + top: 0; + @if $width-height { + width: 100%; + height: 100%; + } + @else { + right: 0; + bottom: 0; + } + +} + +%scut-fill { + @include scut-fill; +} +@mixin scut-list-custom ( + $content: "\2022", + $marker-width: 0.75em, + $pad: 0, + $no-margin: false +) { + + $content-val: null; + $counter: index($content, count); + @if $counter { + @if length($content) == 3 { + $content-val: counter(scutlistcounter, nth($content, 3))nth($content,2); + } + @else if length($content) == 2 { + $content-val: counter(scutlistcounter)nth($content,2); + } + @else { + $content-val: counter(scutlistcounter); + } + } + @else { + $content-val: $content; + } + + padding-left: $marker-width + $pad; + list-style-type: none; + + @if $no-margin { + margin-top: 0; + margin-bottom: 0; + } + + & > li { + position: relative; + @if $counter { + counter-increment: scutlistcounter; + } + &:before { + content: $content-val; + display: block; + position: absolute; + top: 0; + left: -$marker-width; + width: $marker-width; + @content; + } + } + +} +// Depends on `list-floated`, which depends in turn on `list-unstyled` and `clearfix`. + +@mixin scut-list-divided ( + $divider: "|", + $space: 0.5em, + $dir: left, + $height: false, + $no-margin: true +) { + + @include scut-list-floated($dir: $dir, $no-margin: $no-margin); + + $pseudo: if($dir == left, 'before', 'after'); + + // If an explicit height is passed, + // things are different: All
  • s + // need the pseudo-element (to force height), + // but the first's must be hidden. + + @if $height { + & > li { + height: $height; + } + & > li:#{$pseudo} { + height: $height; + content: $divider; + display: inline-block; + vertical-align: middle; + @content; + } + & > li:first-child:#{$pseudo} { + width: 0; + overflow: hidden; + } + } + + & > li + li:#{$pseudo} { + @if not $height { + content: $divider; + display: inline-block; + @content; + } + margin-left: $space; + margin-right: $space; + } + +} + +%scut-list-bar { + @include scut-list-divided; +} + +%scut-list-breadcrumb { + @include scut-list-divided("/"); +} +// Depends on `list-unstyled`. + +@mixin scut-list-inline ( + $space: false, + $no-margin: true +) { + + @include scut-list-unstyled($no-margin); + + & > li { + display: inline-block; + } + + @if $space { + & > li + li { + margin-left: $space; + } + } + +} + +%scut-list-inline { + @include scut-list-inline; +} +// Depends on `list-unstyled`. + +@mixin scut-list-punctuated ( + $divider: ", ", + $display: inline, + $no-margin: true +) { + + @include scut-list-unstyled($no-margin); + + & > li { + display: $display; + &:not(:last-child):after { + content: $divider; + } + } + +} + +%scut-list-comma { + @include scut-list-punctuated; +} +@mixin scut-margin ( + $margin +) { + + @if length($margin) == 1 and $margin != n { + margin-top: $margin; + margin-right: $margin; + margin-bottom: $margin; + margin-left: $margin; + } + + @if length($margin) == 2 { + $margin-y: nth($margin, 1); + $margin-x: nth($margin, 2); + @if $margin-y != n { + margin-top: $margin-y; + margin-bottom: $margin-y; + } + @if $margin-x != n { + margin-left: $margin-x; + margin-right: $margin-x; + } + } + + @if length($margin) == 3 { + $margin-y-top: nth($margin, 1); + $margin-x: nth($margin, 2); + $margin-y-bottom: nth($margin, 3); + @if $margin-y-top != n { + margin-top: $margin-y-top; + } + @if $margin-x != n { + margin-right: $margin-x; + margin-left: $margin-x; + } + @if $margin-y-bottom != n { + margin-bottom: $margin-y-bottom; + } + } + + @if length($margin) == 4 { + $margin-top: nth($margin, 1); + $margin-right: nth($margin, 2); + $margin-bottom: nth($margin, 3); + $margin-left: nth($margin, 4); + @if $margin-top != n { + margin-top: $margin-top; + } + @if $margin-right != n { + margin-right: $margin-right; + } + @if $margin-bottom != n { + margin-bottom: $margin-bottom; + } + @if $margin-left != n { + margin-left: $margin-left; + } + } + +} +@mixin scut-padding ( + $padding +) { + + @if length($padding) == 1 and $padding != n { + padding-top: $padding; + padding-right: $padding; + padding-bottom: $padding; + padding-left: $padding; + } + + @if length($padding) == 2 { + $padding-y: nth($padding, 1); + $padding-x: nth($padding, 2); + @if $padding-y != n { + padding-top: $padding-y; + padding-bottom: $padding-y; + } + @if $padding-x != n { + padding-left: $padding-x; + padding-right: $padding-x; + } + } + + @if length($padding) == 3 { + $padding-y-top: nth($padding, 1); + $padding-x: nth($padding, 2); + $padding-y-bottom: nth($padding, 3); + @if $padding-y-top != n { + padding-top: $padding-y-top; + } + @if $padding-x != n { + padding-right: $padding-x; + padding-left: $padding-x; + } + @if $padding-y-bottom != n { + padding-bottom: $padding-y-bottom; + } + } + + @if length($padding) == 4 { + $padding-top: nth($padding, 1); + $padding-right: nth($padding, 2); + $padding-bottom: nth($padding, 3); + $padding-left: nth($padding, 4); + @if $padding-top != n { + padding-top: $padding-top; + } + @if $padding-right != n { + padding-right: $padding-right; + } + @if $padding-bottom != n { + padding-bottom: $padding-bottom; + } + @if $padding-left != n { + padding-left: $padding-left; + } + } +} +// Depends on `positioning-coordinates`. + +@mixin scut-absolute ( + $coordinates: 0 n n 0 +) { + + position: absolute; + @include scut-coords($coordinates); + +} + +%scut-absolute { + @include scut-absolute; +} +// Depends on `positioning-coordinates`. + +@mixin scut-fixed ( + $coordinates: 0 n n 0 +) { + + position: fixed; + @include scut-coords($coordinates); + +} + +%scut-fixed { + @include scut-fixed; +} +// Depends on `positioning-coordinates`. + +@mixin scut-relative ( + $coordinates: n n n n +) { + + position: relative; + @include scut-coords($coordinates); + +} +@mixin scut-ratio-box ( + $ratio: 1/1 +) { + + overflow: hidden; + position: relative; + + // The container's height, as a percentage of the + // container's width, is set by assigning + // padding-top to a pseudo-element. + &:before { + content: ""; + display: block; + height: 0; + padding-top: (1 / $ratio) * 100%; + } + +} + +%scut-ratio-box { + @include scut-ratio-box; +} +@mixin scut-size( + $size +) { + + @if length($size) == 1 { + width: $size; + height: $size; + } + @else if length($size) == 2 { + width: nth($size, 1); + height: nth($size, 2); + } + +} +@mixin scut-sticky-footer-fixed ( + $height, + $wrapper: ".wrapper", + $footer: ".scut-sticky" +) { + + html, + body { + height: 100%; + margin: 0; + padding: 0; + } + + #{$wrapper} { + min-height: 100%; + margin-bottom: -$height; + &:after { + content: ""; + display: block; + } + } + + #{$wrapper}:after, + #{$footer} { + height: $height; + } + +} + +// deprecated +@mixin scut-sticky-footer ( + $height, + $wrapper: ".wrapper", + $footer: ".scut-sticky" +){ + @include scut-sticky-footer-fixed($height, $wrapper, $footer); +} +@mixin scut-sticky-footer-fluid ( + $wrapper: ".wrapper", + $footer: ".scut-sticky" +) { + + html, + body { + height: 100%; + margin: 0; + padding: 0; + } + + #{$wrapper} { + display: table; + height: 100%; + width: 100%; + } + + #{$footer} { + display: table-row; + height: 1px; + } + +} +@mixin scut-vcenter-ib ( + $inner: ".scut-inner" +) { + + // The inner element is vertically centered + // by middle-aligning it with an inline pseudo-element + // whose height is 100%. + + &:before { + content: ""; + height: 100%; + display: inline-block; + vertical-align: middle; + // A small negative right margin is set + // to account for the default + // word-spacing of inline-block. + margin-right: -0.25em; + } + + & > #{$inner} { + display: inline-block; + vertical-align: middle; + } + +} + +%scut-vcenter-ib { + @include scut-vcenter-ib; +} + +@mixin scut-vcenter-lh ( + $height +) { + + height: $height; + line-height: $height; + +} +@mixin scut-vcenter-td ( + $inner: ".scut-inner" +) { + + display: table; + + & > #{$inner} { + display: table-cell; + vertical-align: middle; + } + +} + + +%scut-vcenter-td { + @include scut-vcenter-td; +} +// Depends on scut-center-transform + +@mixin scut-vcenter-tt () { + @include scut-center-transform(y); +} + +%scut-vcenter-tt { + @include scut-vcenter-tt; +} +// space +$scut-space: "\0020"; +// non-breaking space +$scut-nbsp: "\00a0"; + +// quotation mark +$scut-quot: "\0022"; +// left single curly quote +$scut-lsquo: "\2018"; +// right single curly quote +$scut-rsquo: "\2019"; +// left double curly quote +$scut-ldquo: "\201C"; +// right double curly quote +$scut-rdquo: "\201D"; +// left single angle quote (guillemet) +$scut-lsaquo: "\2039"; +// right single angle quote (guillemet) +$scut-rsaquo: "\203A"; +// left double angle quote (guillemet) +$scut-laquo: "\00ab"; +// right double angle quote (guillemet) +$scut-raquo: "\00bb"; + +// em dash (mutton) +$scut-mdash: "\2014"; +// en dash (nut) +$scut-ndash: "\2013"; +// hyphen +$scut-hyphen: "\2010"; + +// ampersand +$scut-amp: "\0026"; +// greater than +$scut-gt: "\003e"; +// less than +$scut-lt: "\003c"; +// times +$scut-times: "\00D7"; +// big times +$scut-bigtimes: "\2715"; +// checkmark +$scut-checkmark: "\2713"; + +// section sign (double S, hurricane, sectional symbol, the legal doughnut, signum sectionis) +$scut-sect: "\00a7"; +// paragraph symbol (pilcrow) +$scut-para: "\00b6"; + +// middot (interpunct, interpoint) +$scut-middot: "\00b7"; +// o-slash (slashed o) +$scut-oslash: "\00f8"; +// bullet +$scut-bull: "\2022"; +// white bullet +$scut-whibull: "\25E6"; +// horizontal ellipsis +$scut-hellip: "\2026"; +// vertical ellipsis +$scut-vellip: "\22EE"; +// midline horizontal ellipsis +$scut-midhellip: "\22EF"; + +// up-pointing triangle +$scut-utri: "\25b2"; +// down-pointing triangle +$scut-dtri: "\25bc"; +// left-pointing triangle +$scut-ltri: "\25c0"; +// right-pointing triangle +$scut-rtri: "\25b6"; +// up-pointing small triangle +$scut-ustri: "\25b4"; +// down-pointing small triangle +$scut-dstri: "\25be"; +// left-pointing small triangle +$scut-lstri: "\25c2"; +// right-pointing small triangle +$scut-rstri: "\25b8"; +// diamond +$scut-diamond: "\25c6"; +// fisheye +$scut-fisheye: "\25c9"; +// bullseye +$scut-bullseye: "\25ce"; +// circle +$scut-circle: "\25cf"; +// white circle +$scut-whitecircle: "\25cb"; +// square +$scut-square: "\25a0"; +// white square +$scut-whitesquare: "\25a1"; +// small square +$scut-ssquare: "\25aa"; +// small white square +$scut-swhitesquare: "\25ab"; +@function main-src($formats, $file-path, $font-family) { + // Return the list of `src` values, in order, that + // a good `@font-face` will need, including only + // those formats specified in the list `$formats`. + $result: (); + @if index($formats, eot) { + $eot-val: url('#{$file-path}.eot?#iefix') format('embedded-opentype'); + $result: append($result, $eot-val, comma); + } + @if index($formats, woff) { + $woff-val: url('#{$file-path}.woff') format('woff'); + $result: append($result, $woff-val, comma); + } + @if index($formats, ttf) { + $ttf-val: url('#{$file-path}.ttf') format('truetype'); + $result: append($result, $ttf-val, comma); + } + @if index($formats, svg) { + $svg-val: url('#{$file-path}.svg##{$font-family}') format('svg'); + $result: append($result, $svg-val, comma); + } + @return $result; +} + +@mixin scut-font-face ( + $font-family, + $file-path, + $weight: normal, + $style: normal, + $formats: eot woff ttf svg +) { + + @if index('italic' 'oblique', $weight) { + $style: $weight; + $weight: normal; + } + + @font-face { + font-family: $font-family; + font-weight: $weight; + font-style: $style; + + @if index($formats, eot) { + src: url('#{$file-path}.eot'); + } + src: main-src($formats, $file-path, $font-family); + } + +} +@mixin scut-hanging-indent ( + $indent: 1em +) { + + // padding-left creates the indent, + // while text-indent pulls the first line + // back to the edge. + + padding-left: $indent; + text-indent: -$indent; + +} + +%scut-hanging-indent { + @include scut-hanging-indent; +} +@mixin scut-indented-ps ( + $indent: 1.5em, + $no-first-indent: true +) { + + p { + margin: 0; + text-indent: $indent; + } + + @if $no-first-indent { + p:first-of-type { + text-indent: 0; + } + } + +} + +%scut-indented-ps { + @include scut-indented-ps; +} +@mixin scut-key-val ( + $divider: ":", + $pad: 0.25em, + $indent: 1em, + $spacing: 0, + $pad-left: 0 +) { + + & > dt { + clear: both; + float: left; + &:after { + content: $divider; + margin-right: $pad; + @if $pad-left != 0 { + margin-left: $pad-left; + } + } + } + + & > dd { + margin-left: $indent; + @if $spacing != 0 { + margin-bottom: $spacing; + } + } + +} + +%scut-key-val { + @include scut-key-val; +} +@mixin scut-link-bb ( + $color: inherit, + $style: solid, + $width: 1px +) { + + text-decoration: none; + + border-bottom-width: $width; + border-bottom-style: $style; + @if $color != inherit { + border-bottom-color: $color; + } + +} + +%scut-link-bb { + @include scut-link-bb; +} +@mixin scut-reverse-italics ( + $elements: false +) { + + $element-list: em, cite, i; + @each $el in $elements { + $element-list: append($element-list, unquote($el), comma) + } + + font-style: italic; + #{$element-list} { + font-style: normal; + } + +} + +%scut-reverse-italics { + @include scut-reverse-italics; +} +@mixin scut-side-lined ( + $height: 1px, + $space: 0.5em, + $color: inherit, + $style: solid, + $v-adjust: false, + $double: false +) { + + display: block; + overflow: hidden; + text-align: center; + + &:before, + &:after { + content: ""; + display: inline-block; + vertical-align: middle; + position: relative; + width: 50%; + + border-top-style: $style; + border-top-width: $height; + + @if $color != inherit { + border-top-color: $color; + } + + @if $v-adjust != false { + bottom: $v-adjust; + } + + @if $double != false { + height: $double; + border-bottom-style: $style; + border-bottom-width: $height; + @if $color != inherit { + border-bottom-color: $color; + } + } + } + + &:before { + right: $space; + margin-left: -50%; + } + &:after { + left: $space; + margin-right: -50%; + } + +} + +%scut-side-lined { + @include scut-side-lined; +} +@mixin scut-truncate { + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + +} + +%scut-truncate { + @include scut-truncate; +} \ No newline at end of file diff --git a/src/sass/views/_contacts.scss b/src/sass/views/_contacts.scss index a4bd33b..98c8d05 100644 --- a/src/sass/views/_contacts.scss +++ b/src/sass/views/_contacts.scss @@ -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; + } } - + } \ No newline at end of file diff --git a/src/sass/views/_dialog.scss b/src/sass/views/_dialog.scss index 0ad95a3..da28db2 100644 --- a/src/sass/views/_dialog.scss +++ b/src/sass/views/_dialog.scss @@ -3,7 +3,6 @@ @include respond-to(desktop) { max-width: 350px; - top: 30%; } p { diff --git a/src/sass/views/_mail-list.scss b/src/sass/views/_mail-list.scss index 107f966..77b1ad4 100755 --- a/src/sass/views/_mail-list.scss +++ b/src/sass/views/_mail-list.scss @@ -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); - } } \ No newline at end of file diff --git a/src/sass/views/_read.scss b/src/sass/views/_read.scss index 4ef3e09..60a29c8 100644 --- a/src/sass/views/_read.scss +++ b/src/sass/views/_read.scss @@ -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; - } } \ No newline at end of file diff --git a/src/sass/views/_write.scss b/src/sass/views/_write.scss index c09feff..32a615c 100644 --- a/src/sass/views/_write.scss +++ b/src/sass/views/_write.scss @@ -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; diff --git a/src/tpl/add-account.html b/src/tpl/add-account.html index 837115e..534a131 100644 --- a/src/tpl/add-account.html +++ b/src/tpl/add-account.html @@ -25,11 +25,6 @@ - - -
    diff --git a/src/tpl/contacts.html b/src/tpl/contacts.html index 0e69e2b..aaf7781 100644 --- a/src/tpl/contacts.html +++ b/src/tpl/contacts.html @@ -16,30 +16,34 @@
    - - - - - - - - - - - - - - -
    Key IDEmailCreatedSize
    {{key._id.slice(8)}}{{key.userId}}{{key.created | date:'mediumDate'}}{{key.bitSize}} bit
    -
    +
    + + + + + + + + + + + + + + +
    Key IDEmailCreatedSize
    {{key._id.slice(8)}}{{key.userId}}{{key.created | date:'mediumDate'}}{{key.bitSize}} bit
    +
    + + +
    +
    Fingerprint
    +
    {{fingerprint}}
    +
    + - -
    -
    Fingerprint
    -
    {{fingerprint}}
    -
    + \ No newline at end of file diff --git a/src/tpl/desktop.html b/src/tpl/desktop.html index 2a659ec..88ec5d9 100644 --- a/src/tpl/desktop.html +++ b/src/tpl/desktop.html @@ -19,21 +19,22 @@ - @@ -62,21 +62,10 @@
    -
    - - - - - - - - - - - - - -
    Reply
    Reply All
    Forward
    -
    +
      +
    • +
    • +
    • +
    diff --git a/src/tpl/write.html b/src/tpl/write.html index 7a89620..747000b 100644 --- a/src/tpl/write.html +++ b/src/tpl/write.html @@ -6,20 +6,30 @@
    -
    +
    +
    + + +

    - To: + - +

    -

    - Cc: +

    + - +

    -
    +

    + + + + +

    +
    @@ -44,9 +54,9 @@
    -

    +

    -
    +

    -----BEGIN ENCRYPTED PREVIEW-----
    {{ciphertextPreview}}
    -----END ENCRYPTED PREVIEW-----

    diff --git a/test/new-unit/account-ctrl-test.js b/test/new-unit/account-ctrl-test.js index d31d021..f9b74f0 100644 --- a/test/new-unit/account-ctrl-test.js +++ b/test/new-unit/account-ctrl-test.js @@ -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(); diff --git a/test/new-unit/contacts-ctrl-test.js b/test/new-unit/contacts-ctrl-test.js index 2973581..b4989b3 100644 --- a/test/new-unit/contacts-ctrl-test.js +++ b/test/new-unit/contacts-ctrl-test.js @@ -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; }); }); diff --git a/test/new-unit/login-existing-ctrl-test.js b/test/new-unit/login-existing-ctrl-test.js index 29fea16..96542e1 100644 --- a/test/new-unit/login-existing-ctrl-test.js +++ b/test/new-unit/login-existing-ctrl-test.js @@ -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(); }); }); }); diff --git a/test/new-unit/login-initial-ctrl-test.js b/test/new-unit/login-initial-ctrl-test.js index a364f61..21a20ff 100644 --- a/test/new-unit/login-initial-ctrl-test.js +++ b/test/new-unit/login-initial-ctrl-test.js @@ -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(); }); }); diff --git a/test/new-unit/login-new-device-ctrl-test.js b/test/new-unit/login-new-device-ctrl-test.js index 81985dd..d9ce2ca 100644 --- a/test/new-unit/login-new-device-ctrl-test.js +++ b/test/new-unit/login-new-device-ctrl-test.js @@ -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; diff --git a/test/new-unit/navigation-ctrl-test.js b/test/new-unit/navigation-ctrl-test.js index 6c6b1fd..010a25e 100644 --- a/test/new-unit/navigation-ctrl-test.js +++ b/test/new-unit/navigation-ctrl-test.js @@ -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; }); }); diff --git a/test/new-unit/write-ctrl-test.js b/test/new-unit/write-ctrl-test.js index 3690de6..79d5d5a 100644 --- a/test/new-unit/write-ctrl-test.js +++ b/test/new-unit/write-ctrl-test.js @@ -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; }); });