diff --git a/src/js/app-controller.js b/src/js/app-controller.js index 2649e67..161584b 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -207,9 +207,11 @@ define(function(require) { pubkeyDao = new PublicKeyDAO(restDao); lawnchairDao = new LawnchairDAO(); keychain = new KeychainDAO(lawnchairDao, pubkeyDao); + self._keychain = keychain; imapClient = new ImapClient(imapOptions); smtpClient = new SmtpClient(smtpOptions); pgp = new PGP(); + self._crypto = pgp; userStorage = new DeviceStorageDAO(lawnchairDao); self._emailDao = new EmailDAO(keychain, imapClient, smtpClient, pgp, userStorage); diff --git a/src/js/app.js b/src/js/app.js index 3785a26..6ef0d86 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -4,6 +4,7 @@ window.name = 'NG_DEFER_BOOTSTRAP!'; require([ 'angular', 'js/controller/dialog', + 'js/controller/popover', 'js/controller/account', 'js/controller/login', 'js/controller/login-initial', @@ -16,14 +17,14 @@ require([ 'cryptoLib/util', 'angularRoute', 'angularTouch' -], function(angular, DialogCtrl, AccountCtrl, LoginCtrl, LoginInitialCtrl, LoginNewDeviceCtrl, LoginExistingCtrl, MailListCtrl, ReadCtrl, WriteCtrl, NavigationCtrl, util) { +], function(angular, DialogCtrl, PopoverCtrl, AccountCtrl, LoginCtrl, LoginInitialCtrl, LoginNewDeviceCtrl, LoginExistingCtrl, MailListCtrl, ReadCtrl, WriteCtrl, NavigationCtrl, util) { 'use strict'; // reset window.name window.name = util.UUID(); // init main angular module including dependencies - var app = angular.module('mail', ['ngRoute', 'ngTouch', 'navigation', 'mail-list', 'write', 'read', 'login-new-device']); + var app = angular.module('mail', ['ngRoute', 'ngTouch', 'navigation', 'mail-list', 'write', 'read', 'login-new-device', 'popover']); // set router paths app.config(function($routeProvider) { @@ -58,6 +59,7 @@ require([ app.controller('MailListCtrl', MailListCtrl); app.controller('AccountCtrl', AccountCtrl); app.controller('DialogCtrl', DialogCtrl); + app.controller('PopoverCtrl', PopoverCtrl); // manually bootstrap angular due to require.js angular.element().ready(function() { diff --git a/src/js/controller/login-existing.js b/src/js/controller/login-existing.js index de91124..cc54d61 100644 --- a/src/js/controller/login-existing.js +++ b/src/js/controller/login-existing.js @@ -43,7 +43,9 @@ define(function(require) { function onUnlock(err) { if (err) { - handleError(err); + $scope.incorrect = true; + $scope.buttonEnabled = true; + $scope.$apply(); return; } diff --git a/src/js/controller/popover.js b/src/js/controller/popover.js new file mode 100644 index 0000000..28d635a --- /dev/null +++ b/src/js/controller/popover.js @@ -0,0 +1,49 @@ +define(function(require) { + 'use strict'; + + var angular = require('angular'); + + // + // Controller + // + + var PopoverCtrl = function($scope) { + $scope.state.popover = {}; + }; + + // + // Directives + // + + var ngModule = angular.module('popover', []); + ngModule.directive('popover', function($parse) { + return function(scope, elm, attrs) { + var popover = angular.element(document.querySelector('.popover')); + + elm.on('mouseover', function() { + var model = $parse(attrs.popover); + scope.$watch(model, function(value) { + // set popover title and content + scope.state.popover.title = attrs.popoverTitle; + scope.state.popover.content = value; + + // set popover position + var top = elm[0].offsetTop; + var left = elm[0].offsetLeft; + var width = elm[0].offsetWidth; + var height = elm[0].offsetHeight; + + popover[0].style.top = (top + height / 2 - popover[0].offsetHeight / 2) + 'px'; + popover[0].style.left = (left + width) + 'px'; + popover[0].style.opacity = '1'; + }); + }); + + elm.on('mouseout', function() { + popover[0].style.opacity = '0'; + }); + }; + }); + + return PopoverCtrl; +}); \ No newline at end of file diff --git a/src/js/controller/read.js b/src/js/controller/read.js index 63715d4..07853aa 100644 --- a/src/js/controller/read.js +++ b/src/js/controller/read.js @@ -1,13 +1,18 @@ define(function(require) { 'use strict'; - var angular = require('angular'); + var appController = require('js/app-controller'), + angular = require('angular'), + crypto, keychain; // // Controller // var ReadCtrl = function($scope) { + crypto = appController._crypto; + keychain = appController._keychain; + $scope.state.read = { open: false, toggle: function(to) { @@ -18,6 +23,16 @@ define(function(require) { $scope.lineEmpty = function(line) { return line.replace(/>/g, '').trim().length === 0; }; + + $scope.getFingerprint = function(address) { + keychain.getReceiverPublicKey(address, function(err, pubkey) { + var fpr = crypto.getFingerprint(pubkey.publicKey); + var formatted = fpr.slice(0, 4) + ' ' + fpr.slice(4, 8) + ' ' + fpr.slice(8, 12) + ' ' + fpr.slice(12, 16) + ' ' + fpr.slice(16, 20) + ' ' + fpr.slice(20, 24) + ' ' + fpr.slice(24, 28) + ' ' + fpr.slice(28, 32) + ' ' + fpr.slice(32, 36) + ' ' + fpr.slice(36); + + $scope.fingerprint = formatted; + $scope.$apply(); + }); + }; }; // diff --git a/src/js/crypto/pgp.js b/src/js/crypto/pgp.js index 656f87c..af20886 100644 --- a/src/js/crypto/pgp.js +++ b/src/js/crypto/pgp.js @@ -46,9 +46,15 @@ define(function(require) { /** * Show a user's fingerprint */ - PGP.prototype.getFingerprint = function() { + PGP.prototype.getFingerprint = function(publicKeyArmored) { var publicKey, privateKey; + if (publicKeyArmored) { + // parse the optional public key parameter + publicKey = openpgp.read_publicKey(publicKeyArmored)[0]; + return util.hexstrdump(publicKey.getFingerprint()).toUpperCase(); + } + privateKey = openpgp.keyring.exportPrivateKey(0); if (privateKey && privateKey.keyId) { publicKey = openpgp.keyring.getPublicKeysForKeyId(privateKey.keyId)[0]; diff --git a/src/sass/all.scss b/src/sass/all.scss index b5fcae2..1b2eb0a 100755 --- a/src/sass/all.scss +++ b/src/sass/all.scss @@ -17,6 +17,7 @@ @import "components/nav"; @import "components/mail-list"; @import "components/layout"; +@import "components/popover"; // Views @import "views/shared"; diff --git a/src/sass/components/_popover.scss b/src/sass/components/_popover.scss new file mode 100644 index 0000000..eb53e51 --- /dev/null +++ b/src/sass/components/_popover.scss @@ -0,0 +1,125 @@ +/* + * Copyright 2013 Twitter, Inc under the Apache 2.0 license. + * From Bootstrap popover https://github.com/twbs/bootstrap + */ +.popover { + position: absolute; + top: -9999px; + left: -9999px; + display: block; + z-index: 1010; + max-width: 276px; + padding: 1px; + text-align: left; + background-color: #ffffff; + background-clip: padding-box; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + white-space: normal; + opacity: 0; + transition: opacity 0.4s; + + &.top { + margin-top: -10px; + } + &.right { + margin-left: 10px; + } + &.bottom { + margin-top: 10px; + } + &.left { + margin-left: -10px; + } + .popover-title { + margin: 0; + padding: 8px 14px; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; + } + .popover-content { + padding: 9px 14px; + } + .arrow, + .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + } + .arrow { + border-width: 11px; + } + .arrow:after { + border-width: 10px; + content: ""; + } + &.top .arrow { + left: 50%; + margin-left: -11px; + border-bottom-width: 0; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + bottom: -11px; + } + &.top .arrow:after { + content: " "; + bottom: 1px; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #ffffff; + } + &.right .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-left-width: 0; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); + } + &.right .arrow:after { + content: " "; + left: 1px; + bottom: -10px; + border-left-width: 0; + border-right-color: #ffffff; + } + &.bottom .arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); + top: -11px; + } + &.bottom .arrow:after { + content: " "; + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #ffffff; + } + &.left .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); + } + &.left .arrow:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: #ffffff; + bottom: -10px; + } +} \ No newline at end of file diff --git a/src/tpl/desktop.html b/src/tpl/desktop.html index 2ab6f22..566c9a0 100644 --- a/src/tpl/desktop.html +++ b/src/tpl/desktop.html @@ -27,4 +27,13 @@
\ No newline at end of file + + + +{{state.mailList.selected.subject || 'No subject'}}
{{state.mailList.selected.sentDate | date:'EEEE, MMM d, yyyy h:mm a'}}
- From: {{f.name || f.address}} + From: {{f.name || f.address}}
- To: {{t.address}} + To: {{t.address}}
- CC: {{t.address}} + CC: {{t.address}}