diff --git a/src/js/app.js b/src/js/app.js index 18f12f1..8f5b30a 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -62,7 +62,10 @@ requirejs([ 'contacts', 'login-new-device', 'popover' - ]); + ], function($rootScopeProvider) { + // increase digest iteration limit for large recursive ng-includes in reader + $rootScopeProvider.digestTtl(100); + }); // set router paths app.config(function($routeProvider) { diff --git a/src/js/controller/mail-list.js b/src/js/controller/mail-list.js index d1dd7fd..3000861 100644 --- a/src/js/controller/mail-list.js +++ b/src/js/controller/mail-list.js @@ -385,7 +385,17 @@ define(function(require) { this.html = html; this.sentDate = new Date('Thu Sep 19 2013 20:41:23 GMT+0200 (CEST)'); this.subject = 'Getting started'; // Subject line - this.body = 'Here are a few pointers to help you get started with Whiteout Mail.\n\nhttp://www.example.com\n\n# Write encrypted message\n- You can compose a message by clicking on the compose button on the upper right (keyboard shortcut is "n" for a new message or "r" to reply).\n- When typing the recipient\'s email address, secure recipients are marked with a blue label and insecure recipients are red.\n- When sending an email to insecure recipients, the default behavior for Whiteout Mail is to invite them to the service and only send the message content in an encrypted form, once they have joined.\n\n# Advanced features\n- To verify a recipient\'s PGP key, you can hover over the blue label containing their email address and their key fingerprint will be displayed.\n- To view your own key fingerprint, open the account view in the navigation bar on the left. You can compare these with your correspondants over a second channel such as a phonecall.\n\nWe hope this helped you to get started with Whiteout Mail.\n\nYour Whiteout Networks team'; // plaintext body + this.body = 'And a good day to you too sir. \n' + + '\n' + + 'Thursday, Apr 24, 2014 3:33 PM safewithme.testuser@gmail.com wrote:\n' + + '> adsfadfasdfasdfasfdasdfasdfas\n' + + '\n' + + 'http://example.com\n' + + '\n' + + '> Tuesday, Mar 25, 2014 4:19 PM gianniarcore@gmail.com wrote:\n' + + '>> from 0.7.0.1\n' + + '>>\n' + + '>> God speed!'; // plaintext body this.encrypted = true; this.decrypted = true; }; diff --git a/src/js/controller/read.js b/src/js/controller/read.js index edc8a46..30353bf 100644 --- a/src/js/controller/read.js +++ b/src/js/controller/read.js @@ -11,7 +11,7 @@ define(function(require) { // Controller // - var ReadCtrl = function($scope) { + var ReadCtrl = function($scope, $timeout) { emailDao = appController._emailDao; invitationDao = appController._invitationDao; @@ -66,6 +66,21 @@ define(function(require) { mail.to.forEach(checkPublicKey); // display recipient security status Array.isArray(mail.cc) && mail.cc.forEach(checkPublicKey); + + $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 + }); + }); }); function checkPublicKey(user) { @@ -156,6 +171,114 @@ define(function(require) { }); }; + $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; + }; }; // diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 055e225..553ff78 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -127,7 +127,7 @@ define(function(require) { // only display non html mails in reply part if (!re.html) { - body += re.body.trim().split('\n').join('\n> '); + body += re.body.trim().split('\n').join('\n> ').replace(/ >/g, '>'); $scope.body = body; } } diff --git a/src/sass/views/_read.scss b/src/sass/views/_read.scss index 60a29c8..0a59444 100644 --- a/src/sass/views/_read.scss +++ b/src/sass/views/_read.scss @@ -117,6 +117,32 @@ line-height: 1em; } } + + .prev-message { + $prev-message-base-color: desaturate($color-blue, 50%); + $prev-message-hue-rotate: 40deg; + + border-left: 2px solid $prev-message-base-color; + color: $prev-message-base-color; + padding-left: 0.5em; + + & > .prev-message { + border-left-color: adjust-hue($prev-message-base-color, 1 * $prev-message-hue-rotate); + color: adjust-hue($prev-message-base-color, 1 * $prev-message-hue-rotate); + & > .prev-message { + border-left-color: adjust-hue($prev-message-base-color, 2 * $prev-message-hue-rotate); + color: adjust-hue($prev-message-base-color, 2 * $prev-message-hue-rotate); + & > .prev-message { + border-left-color: adjust-hue($prev-message-base-color, 3 * $prev-message-hue-rotate); + color: adjust-hue($prev-message-base-color, 3 * $prev-message-hue-rotate); + .prev-message { + border-left-color: adjust-hue($prev-message-base-color, 4 * $prev-message-hue-rotate); + color: adjust-hue($prev-message-base-color, 4 * $prev-message-hue-rotate); + } + } + } + } + } } iframe { diff --git a/src/tpl/read.html b/src/tpl/read.html index ced1afc..c1bd3e2 100644 --- a/src/tpl/read.html +++ b/src/tpl/read.html @@ -42,11 +42,14 @@
- -
- -
-
+ + +
+
+
diff --git a/src/tpl/text-node.html b/src/tpl/text-node.html new file mode 100644 index 0000000..cc8dbe1 --- /dev/null +++ b/src/tpl/text-node.html @@ -0,0 +1,16 @@ + +
+ +
+
+ + +
+
\ No newline at end of file diff --git a/test/new-unit/read-ctrl-test.js b/test/new-unit/read-ctrl-test.js index 82214dd..9875371 100644 --- a/test/new-unit/read-ctrl-test.js +++ b/test/new-unit/read-ctrl-test.js @@ -166,5 +166,28 @@ define(function(require) { }); }); }); + + describe('parseConversation', function() { + it('should work', function() { + var body = 'foo\n' + + '\n' + + '> bar\n' + + '>\n' + + '> foofoo\n' + + '>> foofoobar\n' + + '\ncomment\n' + + '>> barbar'; + + var nodes = scope.parseConversation({ + body: body + }); + expect(nodes).to.exist; + + var expected = '{"children":["foo\\n",{"children":["bar\\n\\nfoofoo",{"children":["foofoobar"]}]},"\\ncomment",{"children":[{"children":["barbar"]}]}]}'; + var json = JSON.stringify(nodes); + expect(json).to.equal(expected); + }); + }); + }); }); \ No newline at end of file