[WO-198] Style message replies with different colors

This commit is contained in:
Tankred Hase 2014-04-23 18:23:46 +02:00
parent f7fbec4823
commit 77876f5432
8 changed files with 213 additions and 9 deletions

View File

@ -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) {

View File

@ -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;
};

View File

@ -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;
};
};
//

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -42,11 +42,14 @@
<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)">
<div ng-switch-when="true">
<!-- Render lines of a text-email in divs for easier styling -->
<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><!--/.line-->
<!-- Render conversation as recursive text nodes -->
<div ng-include="'tpl/text-node.html'"
ng-if="node !== undefined"
ng-repeat="child in node.children track by $index"
onload="node = child">
</div>
</div><!--/ng-switch-when-->
<div class="working" ng-switch-default>
<span class="spinner"></span>

16
src/tpl/text-node.html Normal file
View File

@ -0,0 +1,16 @@
<!-- Render lines of a text-email in divs for easier styling -->
<div class="line"
ng-if="node.children === undefined"
ng-repeat="line in node.split('\n') track by $index"
ng-class="{'empty-line': lineEmpty(line)}">
<span ng-bind-html="line | createAnchors"></span>
<br>
</div><!--/.line-->
<!-- recursively render child node -->
<div class="prev-message"
ng-include="'tpl/text-node.html'"
ng-if="node.children !== undefined"
ng-repeat="child in node.children track by $index"
onload="node = child">
</div>

View File

@ -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);
});
});
});
});