2013-10-05 08:16:04 -04:00
|
|
|
define(function(require) {
|
|
|
|
'use strict';
|
|
|
|
|
2013-12-03 07:15:10 -05:00
|
|
|
var appController = require('js/app-controller'),
|
2014-01-16 09:37:08 -05:00
|
|
|
download = require('js/util/download'),
|
2013-12-03 07:15:10 -05:00
|
|
|
angular = require('angular'),
|
2014-02-27 09:23:33 -05:00
|
|
|
str = require('js/app-config').string,
|
|
|
|
emailDao, invitationDao, outbox, crypto, keychain;
|
2013-10-05 08:16:04 -04:00
|
|
|
|
|
|
|
//
|
|
|
|
// Controller
|
|
|
|
//
|
|
|
|
|
2014-04-23 12:23:46 -04:00
|
|
|
var ReadCtrl = function($scope, $timeout) {
|
2014-01-16 09:37:08 -05:00
|
|
|
|
|
|
|
emailDao = appController._emailDao;
|
2014-02-27 09:23:33 -05:00
|
|
|
invitationDao = appController._invitationDao;
|
|
|
|
outbox = appController._outboxBo;
|
2013-12-03 07:15:10 -05:00
|
|
|
crypto = appController._crypto;
|
|
|
|
keychain = appController._keychain;
|
|
|
|
|
2013-12-04 05:50:20 -05:00
|
|
|
// set default value so that the popover height is correct on init
|
2014-02-27 12:14:38 -05:00
|
|
|
$scope.keyId = 'No key found.';
|
2013-12-04 05:50:20 -05:00
|
|
|
|
2013-11-08 16:05:08 -05:00
|
|
|
$scope.state.read = {
|
|
|
|
open: false,
|
|
|
|
toggle: function(to) {
|
|
|
|
this.open = to;
|
|
|
|
}
|
|
|
|
};
|
2013-11-18 13:53:31 -05:00
|
|
|
|
|
|
|
$scope.lineEmpty = function(line) {
|
|
|
|
return line.replace(/>/g, '').trim().length === 0;
|
|
|
|
};
|
2013-12-03 07:15:10 -05:00
|
|
|
|
2014-02-20 13:20:24 -05:00
|
|
|
$scope.getKeyId = function(address) {
|
2014-02-27 12:14:38 -05:00
|
|
|
$scope.keyId = 'Searching...';
|
2013-12-03 07:15:10 -05:00
|
|
|
keychain.getReceiverPublicKey(address, function(err, pubkey) {
|
2013-12-04 10:35:53 -05:00
|
|
|
if (err) {
|
|
|
|
$scope.onError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-02-20 13:20:24 -05:00
|
|
|
if (!pubkey) {
|
2014-02-27 12:14:38 -05:00
|
|
|
$scope.keyId = 'User has no key. Click to invite.';
|
|
|
|
$scope.$apply();
|
2014-02-20 13:20:24 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-03 07:15:10 -05:00
|
|
|
var fpr = crypto.getFingerprint(pubkey.publicKey);
|
2014-02-20 13:20:24 -05:00
|
|
|
var formatted = fpr.slice(32);
|
2013-12-03 07:15:10 -05:00
|
|
|
|
2014-02-27 12:14:38 -05:00
|
|
|
$scope.keyId = 'PGP key: ' + formatted;
|
2013-12-03 07:15:10 -05:00
|
|
|
$scope.$apply();
|
|
|
|
});
|
|
|
|
};
|
2013-12-04 10:35:53 -05:00
|
|
|
|
2014-02-23 17:20:47 -05:00
|
|
|
$scope.$watch('state.mailList.selected', function(mail) {
|
2013-12-04 10:42:17 -05:00
|
|
|
if (!mail) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-04 10:35:53 -05:00
|
|
|
// display sender security status
|
|
|
|
mail.from.forEach(checkPublicKey);
|
|
|
|
// display recipient security status
|
|
|
|
mail.to.forEach(checkPublicKey);
|
2014-01-13 16:43:43 -05:00
|
|
|
// display recipient security status
|
|
|
|
Array.isArray(mail.cc) && mail.cc.forEach(checkPublicKey);
|
2014-04-23 12:23:46 -04:00
|
|
|
|
|
|
|
$scope.node = undefined;
|
|
|
|
});
|
|
|
|
$scope.$watch('state.mailList.selected.body', function(body) {
|
|
|
|
if (!body || (body && $scope.state.mailList.selected.decrypted === false)) {
|
|
|
|
$scope.node = undefined;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$timeout(function() {
|
|
|
|
// parse text nodes for rendering
|
|
|
|
$scope.node = $scope.parseConversation({
|
|
|
|
body: body
|
|
|
|
});
|
|
|
|
});
|
2013-12-04 10:35:53 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
function checkPublicKey(user) {
|
|
|
|
user.secure = undefined;
|
|
|
|
keychain.getReceiverPublicKey(user.address, function(err, pubkey) {
|
|
|
|
if (err) {
|
|
|
|
$scope.onError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pubkey && pubkey.publicKey) {
|
|
|
|
user.secure = true;
|
|
|
|
} else {
|
|
|
|
user.secure = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$scope.$apply();
|
|
|
|
});
|
|
|
|
}
|
2014-01-16 09:37:08 -05:00
|
|
|
|
|
|
|
$scope.download = function(attachment) {
|
|
|
|
// download file to disk if content is available
|
|
|
|
if (attachment.content) {
|
|
|
|
saveToDisk(attachment);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var folder = $scope.state.nav.currentFolder;
|
|
|
|
var email = $scope.state.mailList.selected;
|
|
|
|
|
|
|
|
emailDao.getAttachment({
|
|
|
|
path: folder.path,
|
|
|
|
uid: email.uid,
|
|
|
|
attachment: attachment
|
|
|
|
}, function(err) {
|
|
|
|
if (err) {
|
|
|
|
$scope.onError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
saveToDisk(attachment);
|
|
|
|
});
|
|
|
|
|
|
|
|
function saveToDisk(attachment) {
|
|
|
|
download.createDownload({
|
|
|
|
content: attachment.content,
|
|
|
|
filename: attachment.filename,
|
|
|
|
contentType: attachment.mimeType
|
|
|
|
}, $scope.onError);
|
|
|
|
}
|
|
|
|
};
|
2014-02-27 09:23:33 -05:00
|
|
|
|
2014-02-27 12:14:38 -05:00
|
|
|
$scope.invite = function(user) {
|
|
|
|
// only invite non-pgp users
|
|
|
|
if (user.secure) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$scope.keyId = 'Sending invitation...';
|
|
|
|
|
|
|
|
var sender = emailDao._account.emailAddress,
|
|
|
|
recipient = user.address;
|
|
|
|
|
2014-02-27 09:23:33 -05:00
|
|
|
invitationDao.invite({
|
2014-02-27 12:14:38 -05:00
|
|
|
recipient: recipient,
|
|
|
|
sender: sender
|
2014-02-27 09:23:33 -05:00
|
|
|
}, function(err) {
|
|
|
|
if (err) {
|
|
|
|
$scope.onError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var invitationMail = {
|
2014-02-27 12:14:38 -05:00
|
|
|
from: [{
|
|
|
|
address: sender
|
|
|
|
}],
|
|
|
|
to: [{
|
|
|
|
address: recipient
|
|
|
|
}],
|
|
|
|
cc: [],
|
|
|
|
bcc: [],
|
2014-02-27 09:23:33 -05:00
|
|
|
subject: str.invitationSubject,
|
|
|
|
body: str.invitationMessage
|
|
|
|
};
|
|
|
|
|
|
|
|
// send invitation mail
|
|
|
|
outbox.put(invitationMail, $scope.onError);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-04-23 12:23:46 -04:00
|
|
|
$scope.parseConversation = function(email) {
|
|
|
|
var nodes;
|
|
|
|
|
|
|
|
if (!email || !email.body) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseLines(body) {
|
|
|
|
var lines = [];
|
|
|
|
body.split('\n').forEach(parseLine);
|
|
|
|
|
|
|
|
function parseLine(line) {
|
|
|
|
var regex = /^>*/;
|
|
|
|
var result = regex.exec(line);
|
|
|
|
|
|
|
|
lines.push({
|
|
|
|
text: line.replace(regex, '').trim(),
|
|
|
|
level: (result && result.length > 0) ? result[0].length : 0
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildTextNodes(lines) {
|
|
|
|
var i, j, root, currentLevel, currentNode, levelDelta;
|
|
|
|
|
|
|
|
root = new Node();
|
|
|
|
currentLevel = 0;
|
|
|
|
currentNode = root;
|
|
|
|
|
|
|
|
// iterate over text lines
|
|
|
|
for (i = 0; i < lines.length; i++) {
|
|
|
|
levelDelta = lines[i].level - currentLevel;
|
|
|
|
|
|
|
|
if (levelDelta === 0) {
|
|
|
|
// we are at the desired node ... no traversal required
|
|
|
|
} else if (levelDelta > 0) {
|
|
|
|
// traverse to child node(s)
|
|
|
|
for (j = 0; j < levelDelta; j++) {
|
|
|
|
var newChild = new Node(currentNode);
|
|
|
|
// create new child node
|
|
|
|
currentNode.children.push(newChild);
|
|
|
|
// go to last child node
|
|
|
|
currentNode = newChild;
|
|
|
|
// increase current level by one
|
|
|
|
currentLevel++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// traverse to parent(s)
|
|
|
|
for (j = levelDelta; j < 0; j++) {
|
|
|
|
currentNode = currentNode.parent;
|
|
|
|
currentLevel--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add text to the current node
|
|
|
|
currentNode.addLine(lines[i].text);
|
|
|
|
}
|
|
|
|
|
|
|
|
return root;
|
|
|
|
}
|
|
|
|
|
|
|
|
function Node(parent) {
|
|
|
|
this.parent = parent;
|
|
|
|
this.children = [];
|
|
|
|
}
|
|
|
|
Node.prototype.addLine = function(lineText) {
|
|
|
|
var c, l;
|
|
|
|
|
|
|
|
c = this.children;
|
|
|
|
l = c.length;
|
|
|
|
|
|
|
|
// append text node to children if last child is not a text node
|
|
|
|
if (l < 1 || typeof c[l - 1] !== 'string') {
|
|
|
|
c[l] = '';
|
|
|
|
l = c.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
// append line to last child (add newline between lines)
|
|
|
|
c[l - 1] += lineText + '\n';
|
|
|
|
};
|
|
|
|
|
|
|
|
function removeParentReference(node) {
|
|
|
|
if (!node.children) {
|
|
|
|
// this is a text leaf ... terminate recursion
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove parent node to prevent infinite loop in JSON stringify
|
|
|
|
delete node.parent;
|
|
|
|
|
|
|
|
for (var i = 0; i < node.children.length; i++) {
|
|
|
|
if (typeof node.children[i] === 'string') {
|
|
|
|
// remove trailing newline in string
|
|
|
|
node.children[i] = node.children[i].replace(/\n$/, '');
|
|
|
|
} else {
|
|
|
|
// I used recursion ...
|
|
|
|
removeParentReference(node.children[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes = buildTextNodes(parseLines(email.body.replace(/ >/g, '>')));
|
|
|
|
removeParentReference(nodes);
|
|
|
|
|
|
|
|
return nodes;
|
|
|
|
};
|
2013-11-08 16:05:08 -05:00
|
|
|
};
|
2013-10-05 08:16:04 -04:00
|
|
|
|
|
|
|
//
|
|
|
|
// Directives
|
|
|
|
//
|
|
|
|
|
|
|
|
var ngModule = angular.module('read', []);
|
2014-03-14 14:10:51 -04:00
|
|
|
|
2014-04-02 13:47:50 -04:00
|
|
|
ngModule.directive('replySelection', function() {
|
|
|
|
return function(scope, elm) {
|
2014-04-29 11:09:02 -04:00
|
|
|
var popover, visible;
|
|
|
|
|
|
|
|
popover = angular.element(document.querySelector('.reply-selection'));
|
|
|
|
visible = false;
|
2014-04-02 13:47:50 -04:00
|
|
|
|
2014-04-29 11:09:02 -04:00
|
|
|
elm.on('touchstart click', appear);
|
|
|
|
elm.parent().parent().on('touchstart click', disappear);
|
|
|
|
popover.on('touchstart click', disappear);
|
|
|
|
|
|
|
|
function appear(e) {
|
2014-04-02 13:47:50 -04:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
visible = true;
|
|
|
|
|
|
|
|
// 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.transition = 'opacity 0.1s linear';
|
|
|
|
popover[0].style.top = (top + height) + 'px';
|
|
|
|
popover[0].style.left = (left + width / 2 - popover[0].offsetWidth / 2) + 'px';
|
|
|
|
popover[0].style.opacity = '1';
|
2014-04-29 11:09:02 -04:00
|
|
|
}
|
2014-04-02 13:47:50 -04:00
|
|
|
|
2014-04-29 11:09:02 -04:00
|
|
|
function disappear() {
|
2014-04-02 13:47:50 -04:00
|
|
|
if (!visible) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
popover[0].style.transition = 'opacity 0.25s linear, top 0.25s step-end, left 0.25s step-end';
|
|
|
|
popover[0].style.opacity = '0';
|
|
|
|
popover[0].style.top = '-9999px';
|
|
|
|
popover[0].style.left = '-9999px';
|
2014-04-29 11:09:02 -04:00
|
|
|
visible = false;
|
|
|
|
}
|
2014-04-02 13:47:50 -04:00
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2013-10-05 08:16:04 -04:00
|
|
|
ngModule.directive('frameLoad', function() {
|
|
|
|
return function(scope, elm) {
|
|
|
|
elm.bind('load', function() {
|
|
|
|
var frame = elm[0];
|
|
|
|
frame.height = frame.contentWindow.document.body.scrollHeight + 'px';
|
|
|
|
});
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2014-03-14 14:10:51 -04:00
|
|
|
ngModule.filter('createAnchors', function($sce) {
|
|
|
|
return function(str) {
|
|
|
|
// replace all urls with anchors
|
|
|
|
return $sce.trustAsHtml(str.replace(/(https?:\/\/[^\s]+)/g, function(url) {
|
|
|
|
return '<a href="' + url + '" target="_blank">' + url + '</a>';
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2013-10-05 08:16:04 -04:00
|
|
|
return ReadCtrl;
|
|
|
|
});
|