summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md41
-rwxr-xr-x_trial_temp/_trial_marker0
-rw-r--r--service/pixelated/adapter/listeners/mailbox_indexer_listener.py9
-rw-r--r--service/pixelated/bitmask_libraries/session.py13
-rw-r--r--service/pixelated/resources/__init__.py16
-rw-r--r--service/pixelated/resources/login_resource.py1
-rw-r--r--service/pixelated/resources/logout_resource.py22
-rw-r--r--service/pixelated/resources/mail_resource.py6
-rw-r--r--service/pixelated/resources/tags_resource.py2
-rw-r--r--service/pixelated/resources/user_settings_resource.py19
-rw-r--r--service/test/functional/features/steps/common.py2
-rw-r--r--service/test/functional/features/steps/mail_list.py10
-rw-r--r--service/test/functional/features/steps/mail_view.py10
-rw-r--r--service/test/functional/features/steps/tag_list.py2
-rw-r--r--service/test/integration/test_incoming_mail.py4
-rw-r--r--service/test/support/integration/app_test_client.py11
-rw-r--r--service/test/unit/adapter/test_mailbox_indexer_listener.py6
-rw-r--r--service/test/unit/bitmask_libraries/test_session.py6
-rw-r--r--service/test/unit/resources/test_logout_resources.py23
-rw-r--r--service/test/unit/resources/test_tags_resource.py36
-rw-r--r--service/test/unit/resources/test_user_settings_resource.py64
-rw-r--r--web-ui/app/js/helpers/view_helper.js7
-rw-r--r--web-ui/app/js/mail_list/ui/mail_items/mail_item.js1
-rw-r--r--web-ui/app/scss/_security.scss48
-rw-r--r--web-ui/app/scss/_styles.scss110
-rw-r--r--web-ui/app/scss/style.scss3
-rw-r--r--web-ui/app/scss/views/_mail-list.scss96
-rw-r--r--web-ui/app/templates/mails/draft.hbs66
-rw-r--r--web-ui/app/templates/mails/sent.hbs65
-rw-r--r--web-ui/app/templates/mails/single.hbs49
-rw-r--r--web-ui/app/templates/mails/trash.hbs52
-rw-r--r--web-ui/app/templates/page/user_settings_box.hbs3
-rw-r--r--web-ui/app/templates/page/version.hbs3
-rwxr-xr-xweb-ui/config/add_git_version.sh2
-rw-r--r--web-ui/test/custom_matchers.js8
-rw-r--r--web-ui/test/spec/helpers/view_helper.spec.js6
37 files changed, 509 insertions, 314 deletions
diff --git a/.gitignore b/.gitignore
index 5197d853..0c347bf8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,4 +33,5 @@ __pycache__/
credentials.ini
pixelated.cfg
service/_trial_temp/
+_trial_temp
web-ui/coverage
diff --git a/README.md b/README.md
index 27ecbcd0..f4e2a29c 100644
--- a/README.md
+++ b/README.md
@@ -12,14 +12,24 @@ Here's a [podcast](https://soundcloud.com/thoughtworks/pixelated-why-secure-comm
![High level architecture User Agent](https://raw.githubusercontent.com/pixelated/website/master/assets/images/pixelated-user-agent.png)
+## Try it!
+
+If you are curious, have a look at the pixelated web client, it as simple as clicking *[here!](https://try.pixelated-project.org:8080/)*
+
## Getting started
You are most welcome to contribute to the pixelated user agent code base. Please have a look at the [contributions how to](https://github.com/pixelated/pixelated-user-agent/blob/master/CONTRIBUTING.md).
+## Installing Pixelated
+
+You like the idea and you want to run it locally, then before you have to install the following packages:
-1) Install [Vagrant](https://www.vagrantup.com/downloads.html) and a vagrant [compatible provider](https://www.vagrantup.com/docs/providers/), e.g. [Virtual Box](https://www.virtualbox.org/wiki/Downloads). Vagrant is a tool that automates the setup of a virtual machine with the development environment in your computer. Inside the virtual machine's filesystem, this repository will be automatically mounted in the `/vagrant` folder.
+* [Vagrant](https://www.vagrantup.com/downloads.html), Vagrant is a tool that automates the setup of a virtual machine with the development environment
+* A vagrant [compatible provider](https://www.vagrantup.com/docs/providers/), e.g. [Virtual Box](https://www.virtualbox.org/wiki/Downloads).
-2) Clone the repo and start the virtual machine (downloads 600MB):
+### Option 1: Pixelated User Agent without LEAP provider
+
+1) Clone the repo and start the virtual machine (downloads 600MB, you may want get a coffee or tea in the meantime):
```
$ git clone https://github.com/pixelated/pixelated-user-agent.git
@@ -27,15 +37,15 @@ $ cd pixelated-user-agent
$ vagrant up
```
-3) Log into the VM:
+2) Log into the VM:
```
$ vagrant ssh
```
-4) Register with a LEAP provider. You can create a developer account at our [Dev Provider](https://dev.pixelated-project.org/). Please contact us at team@pixelated-project.org for an invite code.
+3) Register with a LEAP provider. You can create a developer account at our [Dev Provider](https://dev.pixelated-project.org/). Please contact us at team@pixelated-project.org for an invite code.
-5) Run the user agent:
+4) Run the user agent:
Please note: If you don't have an account on any provider, go directly to step 6b).
@@ -52,11 +62,11 @@ Type your password:
******** (the one you created in previous step)
```
-6a) Connect to the provider using your credentials, as shown in step 5 above. If the user agent starts up successfully, you will not see any other output.
+5a) Connect to the provider using your credentials, as shown in step 5 above. If the user agent starts up successfully, you will not see any other output.
**Note**: For more convenience during development, you can also create a config file with your credentials (see **Further Notes**).
-6b) If you don't have a `dev.pixelated-project.org` account or just want to connect to our `try.pixelated-project.org` environment, we have some sample configurations for you.
+5b) If you don't have a `dev.pixelated-project.org` account or just want to connect to our `try.pixelated-project.org` environment, we have some sample configurations for you.
Please navigate to the project root in your vagrant box with: `$ cd /vagrant`
@@ -65,9 +75,9 @@ Then you can connect to `try.pixelated-project.org` ...
* as Alice via: `$ pixelated-user-agent --host 0.0.0.0 -c try.alice.ini`
* as Bob via: `$ pixelated-user-agent --host 0.0.0.0 -c try.bob.ini`
-7) Go to [localhost:3333](http://localhost:3333/). You should see a loading screen for a few seconds, then your inbox. If it sticks on the loading screen, check your terminal for errors, then [get help](https://pixelated-project.org/faq/#contact-the-project).
+6) Go to [localhost:3333](http://localhost:3333/). You should see a loading screen for a few seconds, then your inbox. If it sticks on the loading screen, check your terminal for errors, then [get help](https://pixelated-project.org/faq/#contact-the-project).
-8) If you like console output, you can also run the tests to see if everything went according to plan.
+7) If you like console output, you can also run the tests to see if everything went according to plan.
```bash
(user-agent-venv)vagrant@jessie:~$ cd /vagrant
@@ -97,8 +107,19 @@ To run the functional tests:
(user-agent-venv)vagrant@jessie:/vagrant/service$ cd ..
```
-9) You're all set! We've prepared [a couple of issues labeled "Volunteer Task"](https://github.com/pixelated/pixelated-user-agent/labels/Volunteer%20task) that are a good place to dive into the project. Happy Hacking!
+7) You're all set! We've prepared [a couple of issues labeled "Volunteer Task"](https://github.com/pixelated/pixelated-user-agent/labels/Volunteer%20task) that are a good place to dive into the project. Happy Hacking!
+
+## Option 2: Pixelated User Agent + Leap Platform
+
+You can install the Pixelated User Agent and the Leap Platform at once, just by running the following command on your console (this may take a while, please be patient):
+
+```bash
+ curl https://raw.githubusercontent.com/pixelated/puppet-pixelated/master/vagrant_platform.sh | sh
+```
+
+ Once installed, you can create accounts by visiting the LEAP Webapp at [localhost:4443/signup](https://localhost:4443/signup) and see Pixelated in action at [localhost:8080](https://localhost:8080/).
+ NOTE: Be aware that you will not be able to send mails outside, but you can test sending mails internally from one user to another.
## Running tests inside your local IDE
diff --git a/_trial_temp/_trial_marker b/_trial_temp/_trial_marker
deleted file mode 100755
index e69de29b..00000000
--- a/_trial_temp/_trial_marker
+++ /dev/null
diff --git a/service/pixelated/adapter/listeners/mailbox_indexer_listener.py b/service/pixelated/adapter/listeners/mailbox_indexer_listener.py
index 74b4f5af..97a887f4 100644
--- a/service/pixelated/adapter/listeners/mailbox_indexer_listener.py
+++ b/service/pixelated/adapter/listeners/mailbox_indexer_listener.py
@@ -27,9 +27,8 @@ class MailboxIndexerListener(object):
@defer.inlineCallbacks
def listen(cls, account, mailbox_name, mail_store, search_engine):
listener = MailboxIndexerListener(mailbox_name, mail_store, search_engine)
- if listener not in (yield account.getMailbox(mailbox_name)).listeners:
- mbx = yield account.getMailbox(mailbox_name)
- mbx.addListener(listener)
+ mail_collection = yield account.get_collection_by_mailbox(mailbox_name)
+ mail_collection.addListener(listener)
defer.returnValue(listener)
@@ -39,7 +38,7 @@ class MailboxIndexerListener(object):
self.search_engine = search_engine
@defer.inlineCallbacks
- def newMessages(self, exists, recent):
+ def notify_new(self):
try:
indexed_idents = set(self.search_engine.search('tag:' + self.mailbox_name.lower(), all_mails=True))
soledad_idents = yield self.mail_store.get_mailbox_mail_ids(self.mailbox_name)
@@ -63,6 +62,6 @@ class MailboxIndexerListener(object):
@defer.inlineCallbacks
def listen_all_mailboxes(account, search_engine, mail_store):
- mailboxes = yield account.account.list_all_mailbox_names()
+ mailboxes = yield account.list_all_mailbox_names()
for mailbox_name in mailboxes:
yield MailboxIndexerListener.listen(account, mailbox_name, mail_store, search_engine)
diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py
index ae3eb992..d575a9c6 100644
--- a/service/pixelated/bitmask_libraries/session.py
+++ b/service/pixelated/bitmask_libraries/session.py
@@ -24,7 +24,7 @@ from twisted.internet import reactor, defer
from pixelated.bitmask_libraries.certs import LeapCertificate
from pixelated.adapter.mailstore import LeapMailStore
from leap.mail.incoming.service import IncomingMail
-from leap.mail.imap.account import IMAPAccount
+from leap.mail.mail import Account
from leap.auth import SRPAuth
from .nicknym import NickNym
from .smtp import LeapSMTPConfig
@@ -73,7 +73,7 @@ class LeapSession(object):
@defer.inlineCallbacks
def after_first_sync(self):
yield self.nicknym.generate_openpgp_key()
- self.account = self._create_account(self.account_email, self.soledad)
+ yield self._create_account(self.soledad)
self.incoming_mail_fetcher = yield self._create_incoming_mail_fetcher(
self.nicknym,
self.soledad,
@@ -81,8 +81,9 @@ class LeapSession(object):
self.account_email())
reactor.callFromThread(self.incoming_mail_fetcher.startService)
- def _create_account(self, user_mail, soledad):
- return IMAPAccount(user_mail, soledad, defer.Deferred())
+ def _create_account(self, soledad):
+ self.account = Account(soledad)
+ return self.account.deferred_initialization
def _set_fresh_account(self, event, email_address):
log.debug('Key for email %s has been generated' % email_address)
@@ -115,10 +116,10 @@ class LeapSession(object):
@defer.inlineCallbacks
def _create_incoming_mail_fetcher(self, nicknym, soledad, account, user_mail):
- inbox = yield account.callWhenReady(lambda _: account.getMailbox('INBOX'))
+ inbox = yield account.callWhenReady(lambda _: account.get_collection_by_mailbox('INBOX'))
defer.returnValue(IncomingMail(nicknym.keymanager,
soledad,
- inbox.collection,
+ inbox,
user_mail))
def stop_background_jobs(self):
diff --git a/service/pixelated/resources/__init__.py b/service/pixelated/resources/__init__.py
index 469c8bc8..77425cc5 100644
--- a/service/pixelated/resources/__init__.py
+++ b/service/pixelated/resources/__init__.py
@@ -15,6 +15,7 @@
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import json
+import logging
from twisted.web.http import UNAUTHORIZED
from twisted.web.resource import Resource
@@ -23,6 +24,9 @@ from twisted.web.resource import Resource
from pixelated.resources.session import IPixelatedSession
from pixelated.support import log_time
+from twisted.web.http import INTERNAL_SERVER_ERROR
+log = logging.getLogger(__name__)
+
class SetEncoder(json.JSONEncoder):
def default(self, obj):
@@ -47,7 +51,17 @@ def respond_json_deferred(entity, request, status_code=200):
request.finish()
-class BaseResource(Resource):
+class GenericDeferredErrorHandler(object):
+
+ @classmethod
+ def generic_error_handling(cls, e, request):
+ log.error(e)
+ request.setResponseCode(INTERNAL_SERVER_ERROR)
+ request.write('Something went wrong!')
+ request.finish()
+
+
+class BaseResource(Resource, GenericDeferredErrorHandler):
def __init__(self, services_factory):
Resource.__init__(self)
diff --git a/service/pixelated/resources/login_resource.py b/service/pixelated/resources/login_resource.py
index aca266cf..13a78fbc 100644
--- a/service/pixelated/resources/login_resource.py
+++ b/service/pixelated/resources/login_resource.py
@@ -141,6 +141,7 @@ class LoginResource(BaseResource):
d = self._handle_login(request)
d.addCallbacks(render_response, render_error)
+ d.addErrback(self.generic_error_handling, request)
return NOT_DONE_YET
diff --git a/service/pixelated/resources/logout_resource.py b/service/pixelated/resources/logout_resource.py
index 01092b05..c22815ce 100644
--- a/service/pixelated/resources/logout_resource.py
+++ b/service/pixelated/resources/logout_resource.py
@@ -1,9 +1,24 @@
-from twisted.web.server import NOT_DONE_YET
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
-from pixelated.resources import BaseResource
-from twisted.web import util
from twisted.internet import defer
+from twisted.web import util
+from twisted.web.server import NOT_DONE_YET
+from pixelated.resources import BaseResource
from pixelated.resources.login_resource import LoginResource
@@ -25,5 +40,6 @@ class LogoutResource(BaseResource):
d = self._execute_logout(request)
d.addCallback(_redirect_to_login)
+ d.addErrback(self.generic_error_handling, request)
return NOT_DONE_YET
diff --git a/service/pixelated/resources/mail_resource.py b/service/pixelated/resources/mail_resource.py
index 37fceb9b..6f9ec828 100644
--- a/service/pixelated/resources/mail_resource.py
+++ b/service/pixelated/resources/mail_resource.py
@@ -4,7 +4,7 @@ from twisted.python.log import err
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
-from pixelated.resources import respond_json_deferred, BaseResource
+from pixelated.resources import respond_json_deferred, BaseResource, GenericDeferredErrorHandler
from pixelated.support import replier
@@ -30,7 +30,7 @@ class MailTags(Resource):
return NOT_DONE_YET
-class Mail(Resource):
+class Mail(Resource, GenericDeferredErrorHandler):
def __init__(self, mail_id, mail_service):
Resource.__init__(self)
@@ -51,6 +51,8 @@ class Mail(Resource):
d = self._mail_service.mail(self._mail_id)
d.addCallback(lambda mail: populate_reply(mail))
d.addCallback(lambda mail_dict: respond_json_deferred(mail_dict, request))
+ d.addErrback(self.generic_error_handling, request)
+
return NOT_DONE_YET
def render_DELETE(self, request):
diff --git a/service/pixelated/resources/tags_resource.py b/service/pixelated/resources/tags_resource.py
index 6d4b7335..26da9dae 100644
--- a/service/pixelated/resources/tags_resource.py
+++ b/service/pixelated/resources/tags_resource.py
@@ -16,7 +16,6 @@
from pixelated.resources import respond_json_deferred, BaseResource
from twisted.internet.threads import deferToThread
-from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
@@ -34,5 +33,6 @@ class TagsResource(BaseResource):
d = deferToThread(lambda: _search_engine.tags(query=query, skip_default_tags=skip_default_tags))
d.addCallback(lambda tags: respond_json_deferred(tags, request))
+ d.addErrback(self.generic_error_handling, request)
return NOT_DONE_YET
diff --git a/service/pixelated/resources/user_settings_resource.py b/service/pixelated/resources/user_settings_resource.py
index 5b3f0051..38997b30 100644
--- a/service/pixelated/resources/user_settings_resource.py
+++ b/service/pixelated/resources/user_settings_resource.py
@@ -14,7 +14,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
-from pixelated.resources import respond_json, BaseResource
+from pixelated.resources import respond_json_deferred, BaseResource
+from twisted.web import server
+
+FINGERPRINT_NOT_FOUND = 'Fingerprint not found'
class UserSettingsResource(BaseResource):
@@ -25,4 +28,16 @@ class UserSettingsResource(BaseResource):
def render_GET(self, request):
_account_email = self.mail_service(request).account_email
- return respond_json({'account_email': _account_email}, request)
+
+ def finish_request(key):
+ _fingerprint = key.fingerprint
+ respond_json_deferred({'account_email': _account_email, 'fingerprint': _fingerprint}, request)
+
+ def key_not_found(_):
+ respond_json_deferred({'account_email': _account_email, 'fingerprint': FINGERPRINT_NOT_FOUND}, request)
+
+ d = self.keymanager(request).fetch_key(_account_email)
+ d.addCallback(finish_request)
+ d.addErrback(key_not_found)
+
+ return server.NOT_DONE_YET
diff --git a/service/test/functional/features/steps/common.py b/service/test/functional/features/steps/common.py
index 21794eb7..ccad842c 100644
--- a/service/test/functional/features/steps/common.py
+++ b/service/test/functional/features/steps/common.py
@@ -168,7 +168,7 @@ def click_button(context, title, element='button'):
def mail_list_with_subject_exists(context, subject):
- return find_element_by_xpath(context, "//*[@class='subject' and contains(.,'%s')]" % subject)
+ return find_element_by_xpath(context, "//*[@class='mail-list-entry__item-subject' and contains(.,'%s')]" % subject)
def mail_subject(context):
diff --git a/service/test/functional/features/steps/mail_list.py b/service/test/functional/features/steps/mail_list.py
index d19de6cd..82faa7af 100644
--- a/service/test/functional/features/steps/mail_list.py
+++ b/service/test/functional/features/steps/mail_list.py
@@ -32,7 +32,7 @@ def open_current_mail(context):
def get_first_email(context):
- return wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '#mail-list li span a'))[0]
+ return wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '.mail-list-entry__item'))[0]
@then('I see that mail under the \'{tag}\' tag')
@@ -78,13 +78,13 @@ def impl(context):
@given('I have mails')
@then(u'I have mails')
def impl(context):
- emails = wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '#mail-list li span a'))
+ emails = wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '.mail-list-entry__item'))
assert len(emails) > 0
@when('I mark the first unread email as read')
def impl(context):
- emails = wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '#mail-list li'))
+ emails = wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '.mail-list-entry'))
for email in emails:
if 'status-read' not in email.get_attribute('class'):
@@ -98,7 +98,7 @@ def impl(context):
@when('I delete the email')
def impl(context):
def last_email():
- return wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, '#mail-list li'))
+ return wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, '.mail-list-entry'))
mail = last_email()
context.current_mail_id = mail.get_attribute('id')
mail.find_element_by_tag_name('input').click()
@@ -112,7 +112,7 @@ def _wait_for_mail_list_to_be_empty(context):
def mail_list_is_empty(_):
with ImplicitWait(context, timeout=0.1):
try:
- return 0 == len(context.browser.find_elements_by_css_selector('#mail-list li'))
+ return 0 == len(context.browser.find_elements_by_css_selector('.mail-list-entry'))
except TimeoutException:
return False
diff --git a/service/test/functional/features/steps/mail_view.py b/service/test/functional/features/steps/mail_view.py
index 565031b5..2db6dfc5 100644
--- a/service/test/functional/features/steps/mail_view.py
+++ b/service/test/functional/features/steps/mail_view.py
@@ -58,21 +58,13 @@ def impl(context):
click_button(context, 'Send')
-# NOT BEING USED
-@then('I see if the mail has html content')
-def impl(context):
- e = find_element_by_css_selector(context, '#mail-view .bodyArea')
- h2 = e.find_element_by_css_selector("h2[style*='color: #3f4944']")
- assert 'cborim' in h2.text
-
-
@when('I try to delete the first mail')
def impl(context):
context.execute_steps(u"When I open the first mail in the mail list")
find_element_by_css_selector(context, '#mail-view #view-more-actions').click()
context.browser.execute_script("$('#delete-button-top').click();")
- e = find_element_by_css_selector(context, '#user-alerts')
+ e = find_element_by_css_selector(context, '.message-panel__growl--success')
assert 'Your message was moved to trash!' == e.text
diff --git a/service/test/functional/features/steps/tag_list.py b/service/test/functional/features/steps/tag_list.py
index a3315835..8550a886 100644
--- a/service/test/functional/features/steps/tag_list.py
+++ b/service/test/functional/features/steps/tag_list.py
@@ -49,7 +49,7 @@ def impl(context, tag):
e = find_element_by_id(context, 'tag-%s' % tag)
e.click()
- wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, "#mail-list li span a[href*='%s']" % tag), timeout=20)
+ wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, ".mail-list-entry__item[href*='%s']" % tag), timeout=20)
success = True
except TimeoutException:
pass
diff --git a/service/test/integration/test_incoming_mail.py b/service/test/integration/test_incoming_mail.py
index 682ca118..e413d6c1 100644
--- a/service/test/integration/test_incoming_mail.py
+++ b/service/test/integration/test_incoming_mail.py
@@ -25,12 +25,12 @@ class IncomingMailTest(SoledadTestBase):
@defer.inlineCallbacks
def test_message_collection(self):
# given
- mbx = yield self.account.getMailbox('INBOX')
+ mail_collection = yield self.account.get_collection_by_mailbox('INBOX')
input_mail = MailBuilder().build_input_mail()
# when
yield MailboxIndexerListener.listen(self.account, 'INBOX', self.mail_store, self.search_engine)
- yield mbx.addMessage(input_mail.raw, [], notify_just_mdoc=False)
+ yield mail_collection.add_msg(input_mail.raw)
# then
yield self.wait_in_reactor() # event handlers are called async, wait for it
diff --git a/service/test/support/integration/app_test_client.py b/service/test/support/integration/app_test_client.py
index f3ec5d25..95facec0 100644
--- a/service/test/support/integration/app_test_client.py
+++ b/service/test/support/integration/app_test_client.py
@@ -25,7 +25,7 @@ import uuid
import random
-from leap.mail.imap.account import IMAPAccount
+from leap.mail.mail import Account
from leap.soledad.client import Soledad
from mock import Mock
from twisted.internet import reactor, defer
@@ -77,7 +77,7 @@ class AppTestAccount(object):
self.mail_store = SearchableMailStore(LeapMailStore(self.soledad), self.search_engine)
self.attachment_store = LeapAttachmentStore(self.soledad)
- yield self._initialize_imap_account()
+ yield self._initialize_account()
self.draft_service = DraftService(self.mail_store)
self.leap_session = mock()
@@ -110,10 +110,9 @@ class AppTestAccount(object):
soledad_test_folder = os.path.join(self._leap_home, self._uuid)
shutil.rmtree(soledad_test_folder)
- def _initialize_imap_account(self):
- account_ready_cb = defer.Deferred()
- self.account = IMAPAccount(self._user_id, self.soledad, account_ready_cb)
- return account_ready_cb
+ def _initialize_account(self):
+ self.account = Account(self.soledad)
+ return self.account.deferred_initialization
def _create_mail_service(self, mail_sender, mail_store, search_engine, attachment_store):
return MailService(mail_sender, mail_store, search_engine, self._mail_address, attachment_store)
diff --git a/service/test/unit/adapter/test_mailbox_indexer_listener.py b/service/test/unit/adapter/test_mailbox_indexer_listener.py
index c88ba035..c101aa1a 100644
--- a/service/test/unit/adapter/test_mailbox_indexer_listener.py
+++ b/service/test/unit/adapter/test_mailbox_indexer_listener.py
@@ -31,7 +31,7 @@ class MailboxListenerTest(unittest.TestCase):
def test_add_itself_to_mailbox_listeners(self):
self.account.mailboxes = ['INBOX']
mailbox = mock()
- when(self.account).getMailbox('INBOX').thenReturn(mailbox)
+ when(self.account).get_collection_by_mailbox('INBOX').thenReturn(mailbox)
mailbox.listeners = set()
when(mailbox).addListener = lambda x: mailbox.listeners.add(x)
@@ -49,7 +49,7 @@ class MailboxListenerTest(unittest.TestCase):
listener = MailboxIndexerListener('INBOX', self.mail_store, search_engine)
when(self.mail_store).get_mailbox_mail_ids('INBOX').thenReturn({'ident1', 'ident2', 'missing_ident'})
when(self.mail_store).get_mails({'missing_ident'}, include_body=True).thenReturn([mail])
- listener.newMessages(10, 5)
+ listener.notify_new()
verify(self.mail_store, times=1).get_mails({'missing_ident'}, include_body=True)
verify(search_engine).index_mails([mail])
@@ -59,6 +59,6 @@ class MailboxListenerTest(unittest.TestCase):
when(logger).error(ANY()).thenReturn(None)
listener = MailboxIndexerListener('INBOX', self.mail_store, mock())
- yield listener.newMessages(1, 1)
+ yield listener.notify_new()
verify(logger).error(ANY())
diff --git a/service/test/unit/bitmask_libraries/test_session.py b/service/test/unit/bitmask_libraries/test_session.py
index aad2cac2..84f9f023 100644
--- a/service/test/unit/bitmask_libraries/test_session.py
+++ b/service/test/unit/bitmask_libraries/test_session.py
@@ -29,7 +29,7 @@ class SessionTest(AbstractLeapTest):
self.smtp_mock = MagicMock()
@patch('pixelated.bitmask_libraries.session.register')
- @patch('pixelated.bitmask_libraries.session.IMAPAccount')
+ @patch('pixelated.bitmask_libraries.session.Account')
@defer.inlineCallbacks
def test_background_jobs_are_started_during_initial_sync(self, *unused):
mailFetcherMock = MagicMock()
@@ -41,7 +41,7 @@ class SessionTest(AbstractLeapTest):
@patch('pixelated.bitmask_libraries.session.register')
@patch('pixelated.bitmask_libraries.session.unregister')
- @patch('pixelated.bitmask_libraries.session.IMAPAccount')
+ @patch('pixelated.bitmask_libraries.session.Account')
@defer.inlineCallbacks
def test_that_close_stops_background_jobs(self, *unused):
with patch('pixelated.bitmask_libraries.session.reactor.callFromThread', new=_execute_func) as _:
@@ -153,7 +153,7 @@ class SessionTest(AbstractLeapTest):
@patch('pixelated.bitmask_libraries.session.register')
@patch('pixelated.bitmask_libraries.session.unregister')
- @patch('pixelated.bitmask_libraries.session.IMAPAccount')
+ @patch('pixelated.bitmask_libraries.session.Account')
@defer.inlineCallbacks
def test_session_initial_sync_only_triggered_once(self, *unused):
mailFetcherMock = MagicMock()
diff --git a/service/test/unit/resources/test_logout_resources.py b/service/test/unit/resources/test_logout_resources.py
index 312d2ba4..49866057 100644
--- a/service/test/unit/resources/test_logout_resources.py
+++ b/service/test/unit/resources/test_logout_resources.py
@@ -1,12 +1,16 @@
+import logging
+
from mock import patch, MagicMock
-from twisted.trial import unittest
from twisted.internet import defer
+from twisted.trial import unittest
from twisted.web.error import UnsupportedMethod
from twisted.web.test.requesthelper import DummyRequest
from pixelated.resources.logout_resource import LogoutResource
from test.unit.resources import DummySite
+logging.getLogger('pixelated.resources').addHandler(logging.NullHandler())
+
class TestLogoutResource(unittest.TestCase):
def setUp(self):
@@ -40,3 +44,20 @@ class TestLogoutResource(unittest.TestCase):
request.method = 'GET'
self.assertRaises(UnsupportedMethod, self.web.get, request)
+
+ def test_errback_is_called(self):
+ request = DummyRequest(['/logout'])
+ request.method = 'POST'
+
+ session = self.resource.get_session(request)
+ exception = Exception('')
+ session.expire = MagicMock(side_effect=exception)
+
+ d = self.web.get(request)
+
+ def assert_500_when_exception_is_thrown(_):
+ self.assertEqual(500, request.responseCode)
+ self.assertEqual('Something went wrong!', request.written[0])
+
+ d.addCallback(assert_500_when_exception_is_thrown)
+ return d
diff --git a/service/test/unit/resources/test_tags_resource.py b/service/test/unit/resources/test_tags_resource.py
new file mode 100644
index 00000000..684e95f2
--- /dev/null
+++ b/service/test/unit/resources/test_tags_resource.py
@@ -0,0 +1,36 @@
+import logging
+
+from mock import MagicMock
+from twisted.trial import unittest
+from twisted.web.test.requesthelper import DummyRequest
+from pixelated.resources.tags_resource import TagsResource
+from test.unit.resources import DummySite
+
+logging.getLogger('pixelated.resources').addHandler(logging.NullHandler())
+
+
+class TestTagsResource(unittest.TestCase):
+ def setUp(self):
+ self.services_factory = MagicMock()
+ self.resource = TagsResource(self.services_factory)
+
+ def test_errback_is_called(self):
+ exception = Exception('')
+ mock_search_engine = MagicMock()
+ mock_search_engine.tags = MagicMock(side_effect=exception)
+ mock_service = MagicMock()
+ mock_service.search_engine = mock_search_engine
+ self.services_factory.services.return_value = mock_service
+ self.web = DummySite(self.resource)
+
+ request = DummyRequest(['/tags'])
+ request.method = 'GET'
+
+ d = self.web.get(request)
+
+ def assert_500_when_exception_is_thrown(_):
+ self.assertEqual(500, request.responseCode)
+ self.assertEqual('Something went wrong!', request.written[0])
+
+ d.addCallback(assert_500_when_exception_is_thrown)
+ return d
diff --git a/service/test/unit/resources/test_user_settings_resource.py b/service/test/unit/resources/test_user_settings_resource.py
new file mode 100644
index 00000000..d69f4895
--- /dev/null
+++ b/service/test/unit/resources/test_user_settings_resource.py
@@ -0,0 +1,64 @@
+import unittest
+import json
+import ast
+
+from pixelated.application import UserAgentMode
+from pixelated.resources.user_settings_resource import UserSettingsResource, FINGERPRINT_NOT_FOUND
+from mockito import mock, when, any
+from test.unit.resources import DummySite
+from twisted.web.test.requesthelper import DummyRequest
+from leap.keymanager import OpenPGPKey
+from twisted.internet import defer
+from twisted.python.failure import Failure
+
+MAIL_ADDRESS = 'some@key'
+FINGERPRINT = '4-8-12-13-23-42'
+
+
+class TestUserSettingsResource(unittest.TestCase):
+
+ def setUp(self):
+ self.services = mock()
+ self.mail_service = mock()
+ self.mail_service.account_email = MAIL_ADDRESS
+ self.keymanager = mock()
+ self.services_factory = mock()
+ self.services_factory.mode = UserAgentMode(is_single_user=True)
+ self.services.mail_service = self.mail_service
+ self.services.keymanager = self.keymanager
+ self.services_factory._services_by_user = {'someuserid': self.keymanager}
+ self.resource = UserSettingsResource(self.services_factory)
+ when(self.services_factory).services(any()).thenReturn(self.services)
+ self.web = DummySite(self.resource)
+
+ def test_fingerprint_given(self):
+ key = OpenPGPKey(MAIL_ADDRESS)
+ key.fingerprint = FINGERPRINT
+ request = DummyRequest(['/user-settings'])
+ when(self.keymanager).fetch_key(MAIL_ADDRESS).thenReturn(defer.succeed(key))
+
+ d = self.web.get(request)
+
+ def assert_response(_):
+ response = json.loads(request.written[0])
+ self.assertEqual(FINGERPRINT, response['fingerprint'])
+ self.assertEqual(MAIL_ADDRESS, response['account_email'])
+
+ d.addCallback(assert_response)
+ return d
+
+ def test_fingerprint_missing(self):
+ key = OpenPGPKey(MAIL_ADDRESS)
+ key.fingerprint = FINGERPRINT
+ request = DummyRequest(['/user-settings'])
+ when(self.keymanager).fetch_key(MAIL_ADDRESS).thenReturn(defer.fail(Failure))
+
+ d = self.web.get(request)
+
+ def assert_response(_):
+ response = json.loads(request.written[0])
+ self.assertEqual(FINGERPRINT_NOT_FOUND, response['fingerprint'])
+ self.assertEqual(MAIL_ADDRESS, response['account_email'])
+
+ d.addCallback(assert_response)
+ return d
diff --git a/web-ui/app/js/helpers/view_helper.js b/web-ui/app/js/helpers/view_helper.js
index e8d517a5..7e07df75 100644
--- a/web-ui/app/js/helpers/view_helper.js
+++ b/web-ui/app/js/helpers/view_helper.js
@@ -98,14 +98,21 @@ define(
}
+ function formatFingerPrint(fingerprint) {
+ fingerprint = fingerprint || '';
+ return fingerprint.replace(/(.{4})/g, '$1 ').trim();
+ }
+
Handlebars.registerHelper('formatDate', formatDate);
Handlebars.registerHelper('formatSize', formatSize);
Handlebars.registerHelper('formatStatusClasses', formatStatusClasses);
+ Handlebars.registerHelper('formatFingerPrint', formatFingerPrint);
return {
formatStatusClasses: formatStatusClasses,
formatSize: formatSize,
formatMailBody: formatMailBody,
+ formatFingerPrint: formatFingerPrint,
moveCaretToEndOfText: moveCaretToEndOfText,
quoteMail: quoteMail,
i18n: i18n
diff --git a/web-ui/app/js/mail_list/ui/mail_items/mail_item.js b/web-ui/app/js/mail_list/ui/mail_items/mail_item.js
index c075b0b5..be664289 100644
--- a/web-ui/app/js/mail_list/ui/mail_items/mail_item.js
+++ b/web-ui/app/js/mail_list/ui/mail_items/mail_item.js
@@ -68,6 +68,7 @@ define(
this.attr.mail.tagsForListView = _.without(this.attr.mail.tags, this.attr.tag);
var mailItemHtml = templates.mails[this.attr.templateType](this.attr.mail);
this.$node.html(mailItemHtml);
+ this.$node.addClass("mail-list-entry");
this.$node.addClass(viewHelper.formatStatusClasses(this.attr.mail.status));
if (this.attr.selected) { this.doSelect(); }
this.on(this.$node.find('a'), 'click', this.triggerOpenMail);
diff --git a/web-ui/app/scss/_security.scss b/web-ui/app/scss/_security.scss
deleted file mode 100644
index ff36cb3e..00000000
--- a/web-ui/app/scss/_security.scss
+++ /dev/null
@@ -1,48 +0,0 @@
-.security-status {
- margin: 0 0 5px;
- clear: both;
- span {
- display: inline-block;
- padding: 2px 6px;
- white-space: nowrap;
- background: $success;
- color: $white;
- border-radius: 12px;
- &:before {
- font-family: FontAwesome;
- }
- &.encrypted {
- &:before {
- content: "\f023";
- }
- &.encryption-error {
- background: $attention;
- &:before {
- content: "\f023 \f057";
- }
- }
- }
- &.signed {
- &:before {
- content: "\f00c";
- }
- &.signature-not-trusted {
- background: $error;
- &:before {
- content: "\f05e";
- }
- }
- }
- &[class^=not-], &.signature-expired, &.signature-revoked {
- background: $attention;
- &:before {
- content: "\f05e"
- }
- }
- &.not-encrypted {
- &:before {
- content: "\f13e ";
- }
- }
- }
-}
diff --git a/web-ui/app/scss/_styles.scss b/web-ui/app/scss/_styles.scss
index b4ffbd75..aacc5dd5 100644
--- a/web-ui/app/scss/_styles.scss
+++ b/web-ui/app/scss/_styles.scss
@@ -32,6 +32,11 @@
bottom: 33px
}
+ header {
+ border-bottom: 1px solid white;
+ margin-bottom: 10px;
+ }
+
#user-settings-close {
float: right;
}
@@ -42,6 +47,14 @@
line-height: 1.2em;
}
+ h2 {
+ font-size: 1.1em;
+ color: white;
+ line-height: 1.1em;
+ display: inline;
+ margin-left: 5px;
+ }
+
i.fa-user {
margin-right: 10px;
float: left;
@@ -54,7 +67,7 @@
}
p {
- font-size: 1.2em;
+ font-size: 1.1em;
color: $light_orange;
}
}
@@ -128,100 +141,6 @@
}
}
-@mixin email-list {
- ul#mail-list {
- clear: both;
- li {
- position: relative;
- padding: 8px 10px 10px 10px;
- background: $contrast;
- border-bottom: 1px solid white;
- cursor: pointer;
- font-weight: bold;
- transition: background-color 150ms ease-out;
- span {
- display: inline-block;
- vertical-align: top;
- &:last-child {
- width: 92%;
- }
- input[type=checkbox] {
- @include check-box;
- margin-right: 2px;
- }
- a {
- color: $dark_grey;
- display: block;
- height: 62px;
- margin-top: -8px;
- padding-top: 3px;
- width: 106%;
- }
- }
-
- .subject {
- display: block;
- width: 90%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- i.fa-pencil, i.fa-trash-o {
- color: $indicator_icon_color;
- }
- }
-
- .tags {
- @include tags;
-
- // HACK: overwrite default from ul li property
- &-tag {
- border-bottom: none;
- }
- }
-
- .received-date, .sent-date {
- position: absolute;
- right: 10px;
- font-size: 0.7em;
- }
-
- .attachment-indicator {
- margin: 2px 0 0 25px;
- font-size: initial;
-
- i.fa-paperclip {
- color: $indicator_icon_color;
- }
- }
- .from {
- white-space: nowrap;
- font-size: 0.8em;
- width: 80%;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- &.status-read {
- a {
- font-weight: normal;
- color: $attachment_text;
- }
- }
- &:hover {
- background: darken($contrast, 5%);
- }
- &.selected {
- background: $white;
- z-index: 3;
- a {
- color: $dark_grey;
- }
- }
- }
- }
-}
-
@mixin mail-count($bg_color) {
background: $bg_color;
color: $white;
@@ -510,7 +429,6 @@ section {
&#middle-pane {
background: $contrast;
- @include email-list;
}
&#right-pane {
diff --git a/web-ui/app/scss/style.scss b/web-ui/app/scss/style.scss
index 4dc79398..db9486fa 100644
--- a/web-ui/app/scss/style.scss
+++ b/web-ui/app/scss/style.scss
@@ -12,6 +12,8 @@
// mixins
@import "mixins/position-helpers";
@import "mixins/tags";
+
+// TODO
@import "mixins";
// templates
@@ -26,6 +28,7 @@
@import "views/security-labels";
@import "views/compose-view";
@import "views/compose-button";
+@import "views/mail-list";
// misc stuff
@import "others";
diff --git a/web-ui/app/scss/views/_mail-list.scss b/web-ui/app/scss/views/_mail-list.scss
new file mode 100644
index 00000000..417fabc2
--- /dev/null
+++ b/web-ui/app/scss/views/_mail-list.scss
@@ -0,0 +1,96 @@
+.mail-list-entry {
+ @include scut-clearfix;
+
+ padding: 8px 10px 10px 10px;
+ border-bottom: 1px solid white;
+ transition: background-color 150ms ease-out;
+ cursor: pointer;
+ font-weight: bold;
+ height: 80px;
+ position: relative;
+
+ // Workaround:
+ // Foundation is of the opinion that a 1.6 line height for all lists
+ // is a totally good idea. Please remove when Foundation is gone
+ line-height: normal;
+
+ &.status-read {
+ font-weight: normal;
+ color: $attachment_text;
+ }
+
+ &.selected {
+ background: $white;
+ z-index: 10; // overlay the box-shadow of the right page (z-index: 2)
+ }
+
+ &:hover {
+ background: darken($contrast, 5%);
+ }
+
+ &__checkbox {
+ margin-right: 5px;
+ display: block;
+ float: left;
+ min-height: 100%;
+
+ & > input[type=checkbox] {
+ @include check-box;
+ }
+ }
+
+ &__item {
+ display: block;
+ color: $dark_grey;
+ padding-left: 24px;
+
+ &-from {
+ white-space: nowrap;
+ font-size: 0.8em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+ }
+
+ &-date {
+ font-size: 0.7em;
+ float: right;
+ display: inline-block;
+ }
+
+ &-subject {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 85%;
+
+ &-icon {
+ color: $light_gray;
+ }
+ }
+
+ &-attachment {
+ width: 14px;
+ text-align: right;
+ display: inline-block;
+ float: right;
+ color: $light_gray;
+ }
+
+ &-tags {
+ @include tags;
+
+ // Workaround:
+ // Foundation is of the opinion that a 1.6 line height and a 0.6 rem margin-bottom
+ // for all lists is a totally good idea. Please remove when Foundation is gone
+ line-height: normal;
+ margin-bottom: 0;
+ }
+
+ &:hover, &:focus, &:active {
+ color: $dark_grey;
+ }
+ }
+}
+
diff --git a/web-ui/app/templates/mails/draft.hbs b/web-ui/app/templates/mails/draft.hbs
index 675f1481..d223c0a4 100644
--- a/web-ui/app/templates/mails/draft.hbs
+++ b/web-ui/app/templates/mails/draft.hbs
@@ -1,31 +1,41 @@
-<span>
- <input type="checkbox"/>
-</span>
-<span>
- <a href="/#/{{ currentTag }}/mail/{{ ident }}">
- <span class="sent-date">{{ formatDate header.date }}
- {{#if attachments}}
- <div class="attachment-indicator">
- <i class="fa fa-paperclip"></i>
- </div>
- {{/if}}
- </span>
+<div class="mail-list-entry__checkbox">
+ <input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
+</div>
+
+<a class="mail-list-entry__item" href="/#/{{ currentTag }}/mail/{{ ident }}">
+ <div>
+ <div class="mail-list-entry__item-from">
+ {{t 'to:'}}
+ {{#if header.to }}
+ {{ header.to }}
+ {{else}}
+ {{t 'no_recipient'}}
+ {{/if}}
+ </div> <!-- /.mail-list-entry__item-from -->
- <div class="from">
- {{t 'to:'}}
- {{#if header.to }}
- {{ header.to }}
- {{else}}
- {{t 'no_recipient'}}
- {{/if}}
+ <span class="mail-list-entry__item-date">{{ formatDate header.date }}</span> <!-- /.mail-list-entry__item-date -->
</div>
- <div class="subject">
- <i class="fa fa-pencil"></i>
- {{#if header.subject }}
- {{header.subject}}
- {{else}}
- {{t 'no_subject'}}
- {{/if}}
+ <div>
+ <div class="mail-list-entry__item-subject">
+ <i class="mail-list-entry__item-subject-icon fa fa-pencil"></i>
+ {{#if header.subject }}
+ {{header.subject}}
+ {{else}}
+ {{t 'no_subject'}}
+ {{/if}}
+ </div>
+
+ {{#if attachments}}
+ <div class="mail-list-entry__item-attachment"><i class="fa fa-paperclip"></i></div>
+ {{/if}}
</div>
- </a>
-</span>
+ <ul class="mail-list-entry__item-tags">
+ {{#each tagsForListView }}
+ <li class="mail-list-entry__item-tags-tag" data-tag="{{this}}">{{ this }}</li>
+ {{/each }}
+ </ul> <!-- /.mail-list-entry__item-tags -->
+</a>
+
+
+
+
diff --git a/web-ui/app/templates/mails/sent.hbs b/web-ui/app/templates/mails/sent.hbs
index 86b6e607..a637e3d2 100644
--- a/web-ui/app/templates/mails/sent.hbs
+++ b/web-ui/app/templates/mails/sent.hbs
@@ -1,35 +1,36 @@
-<span>
- <input type="checkbox"/>
-</span>
-<span>
- <a href="/#/{{ currentTag }}/mail/{{ ident }}">
- <span class="sent-date">{{ formatDate header.date }}
+<div class="mail-list-entry__checkbox">
+ <input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
+</div>
+<a class="mail-list-entry__item" href="/#/{{ currentTag }}/mail/{{ ident }}">
+ <div>
+ <div class="mail-list-entry__item-from">
+ {{t 'to:'}}
+ {{#if header.to }}
+ {{ header.to }}
+ {{else}}
+ {{t 'no_recipient'}}
+ {{/if}}
+ </div> <!-- /.mail-list-entry__item-from -->
+
+ <span class="mail-list-entry__item-date">{{ formatDate header.date }}</span> <!-- /.mail-list-entry__item-date -->
+ </div>
+ <div>
+ <div class="mail-list-entry__item-subject">
+ {{#if header.subject }}
+ {{header.subject}}
+ {{else}}
+ {{t 'no_subject'}}
+ {{/if}}
+ </div>
+
{{#if attachments}}
- <div class="attachment-indicator">
- <i class="fa fa-paperclip"></i>
- </div>
+ <div class="mail-list-entry__item-attachment"><i class="fa fa-paperclip"></i></div>
{{/if}}
- </span>
+ </div>
+ <ul class="mail-list-entry__item-tags">
+ {{#each tagsForListView }}
+ <li class="mail-list-entry__item-tags-tag" data-tag="{{this}}">{{ this }}</li>
+ {{/each }}
+ </ul> <!-- /.mail-list-entry__item-tags -->
+</a>
- <div class="from">
- {{t 'to:'}}
- {{#if header.to }}
- {{ header.to }}
- {{else}}
- {{t 'no_recipient'}}
- {{/if}}
- </div>
- <div class="subject">
- {{#if header.subject }}
- {{header.subject}}
- {{else}}
- {{t 'no_subject'}}
- {{/if}}
- </div>
- <ul class="tags">
- {{#each tagsForListView }}
- <li class="tags-tag" data-tag="{{this}}">{{ this }}</li>
- {{/each }}
- </ul>
- </a>
-</span>
diff --git a/web-ui/app/templates/mails/single.hbs b/web-ui/app/templates/mails/single.hbs
index 95f9adb7..aaede844 100644
--- a/web-ui/app/templates/mails/single.hbs
+++ b/web-ui/app/templates/mails/single.hbs
@@ -1,23 +1,28 @@
-<span>
- <input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
-</span>
-<span>
- <a href="/#/{{ currentTag }}/mail/{{ ident }}">
- <span class="received-date">{{ formatDate header.date }}
- {{#if attachments}}
- <div class="attachment-indicator">
- <i class="fa fa-paperclip"></i>
- </div>
- {{/if}}
- </span>
- <div class="from">{{#if header.from }}{{ header.from }}{{else}}{{t "you"}}{{/if}}</div>
- <div class="subject">
- {{ header.subject }}
+<div class="mail-list-entry__checkbox">
+ <input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
+</div>
+<a class="mail-list-entry__item" href="/#/{{ currentTag }}/mail/{{ ident }}">
+ <div>
+ <div class="mail-list-entry__item-from">
+ {{#if header.from }}
+ {{ header.from }}
+ {{else}}
+ {{t "you"}}
+ {{/if}}
+ </div> <!-- /.mail-list-entry__item-from -->
+
+ <span class="mail-list-entry__item-date">{{ formatDate header.date }}</span> <!-- /.mail-list-entry__item-date -->
</div>
- <ul class="tags">
- {{#each tagsForListView }}
- <li class="tags-tag" data-tag="{{this}}">{{ this }}</li>
- {{/each }}
- </ul>
- </a>
-</span>
+ <div>
+ <div class="mail-list-entry__item-subject">{{ header.subject }}</div>
+
+ {{#if attachments}}
+ <div class="mail-list-entry__item-attachment"><i class="fa fa-paperclip"></i></div>
+ {{/if}}
+ </div>
+ <ul class="mail-list-entry__item-tags">
+ {{#each tagsForListView }}
+ <li class="mail-list-entry__item-tags-tag" data-tag="{{this}}">{{ this }}</li>
+ {{/each }}
+ </ul> <!-- /.mail-list-entry__item-tags -->
+</a>
diff --git a/web-ui/app/templates/mails/trash.hbs b/web-ui/app/templates/mails/trash.hbs
index ea8fe07f..f8947b15 100644
--- a/web-ui/app/templates/mails/trash.hbs
+++ b/web-ui/app/templates/mails/trash.hbs
@@ -1,24 +1,32 @@
-<span>
- <input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
-</span>
-<span>
- <a href="/#/{{ currentTag }}/mail/{{ ident }}">
- <span class="received-date">{{ header.formattedDate }}
- {{#if attachments}}
- <div class="attachment-indicator">
- <i class="fa fa-paperclip"></i>
+<div class="mail-list-entry__checkbox">
+ <input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
+</div>
+<a class="mail-list-entry__item" href="/#/{{ currentTag }}/mail/{{ ident }}">
+ <div>
+ <div class="mail-list-entry__item-from">
+ {{#if header.from }}
+ {{ header.from }}
+ {{else}}
+ {{t "you"}}
+ {{/if}}
+ </div> <!-- /.mail-list-entry__item-from -->
+
+ <span class="mail-list-entry__item-date">{{ formatDate header.date }}</span> <!-- /.mail-list-entry__item-date -->
+ </div>
+ <div>
+ <div class="mail-list-entry__item-subject">
+ <i class="mail-list-entry__item-subject-icon fa fa-trash-o"></i>
+ {{ header.subject }}
</div>
- {{/if}}
- </span>
- <div class="from">{{#if header.from }}{{ header.from }}{{else}}{{t "you"}}{{/if}}</div>
- <div class="subject">
- <i class="fa fa-trash-o"></i>
- {{ header.subject }}
+
+ {{#if attachments}}
+ <div class="mail-list-entry__item-attachment"><i class="fa fa-paperclip"></i></div>
+ {{/if}}
</div>
- <ul class="tags">
- {{#each tagsForListView }}
- <li class="tags-tag" data-tag="{{this}}">{{ this }}</li>
- {{/each }}
- </ul>
- </a>
-</span>
+ <ul class="mail-list-entry__item-tags">
+ {{#each tagsForListView }}
+ <li class="mail-list-entry__item-tags-tag" data-tag="{{this}}">{{ this }}</li>
+ {{/each }}
+ </ul> <!-- /.mail-list-entry__item-tags -->
+</a>
+
diff --git a/web-ui/app/templates/page/user_settings_box.hbs b/web-ui/app/templates/page/user_settings_box.hbs
index eb227dcf..761bfc16 100644
--- a/web-ui/app/templates/page/user_settings_box.hbs
+++ b/web-ui/app/templates/page/user_settings_box.hbs
@@ -4,4 +4,7 @@
<h1>User Settings</h1>
<i class="shortcut-label"></i>
</header>
+<i class="fa fa-envelope-o"></i><h2>E-Mail address</h2>
<p>{{ account_email }}</p>
+<i class="fa fa-key"></i><h2>Public key fingerprint</h2>
+<p>{{ formatFingerPrint fingerprint }}</p>
diff --git a/web-ui/app/templates/page/version.hbs b/web-ui/app/templates/page/version.hbs
index 40804ff3..181d2151 100644
--- a/web-ui/app/templates/page/version.hbs
+++ b/web-ui/app/templates/page/version.hbs
@@ -1 +1,2 @@
-version: UNKNOWN_VERSION \ No newline at end of file
+version: UNKNOWN_VERSION <br/>
+COMMIT_DATE
diff --git a/web-ui/config/add_git_version.sh b/web-ui/config/add_git_version.sh
index 2732abed..b9cfba72 100755
--- a/web-ui/config/add_git_version.sh
+++ b/web-ui/config/add_git_version.sh
@@ -3,8 +3,10 @@
TEMPLATE_FILE="app/js/generated/hbs/templates.js"
COMMITISH=$(git rev-parse --short HEAD)
+COMMITDATE=$(git show -s --format=%cr)
perl -pi -e "s/UNKNOWN_VERSION/$COMMITISH/" $TEMPLATE_FILE
+perl -pi -e "s/COMMIT_DATE/$COMMITDATE/" $TEMPLATE_FILE
if [ ! -f "$TEMPLATE_FILE" ] ; then
echo "file $TEMPLATE_FILE not found" 1>&2
diff --git a/web-ui/test/custom_matchers.js b/web-ui/test/custom_matchers.js
index d68d21a8..3de7fb5b 100644
--- a/web-ui/test/custom_matchers.js
+++ b/web-ui/test/custom_matchers.js
@@ -5,10 +5,10 @@ define([], function() {
compare: function (mail, node) {
var result = {}, equals = {}, subject, tags, from, date, messages = [], notMessages = [];
- subject = node.find('#mail-' + mail.ident + ' .subject')[0];
- tags = _.map(node.find('#mail-' + mail.ident + ' .tags .tags-tag'), function (tag) { return tag.textContent; });
- date = node.find('#mail-' + mail.ident + ' .received-date');
- from = node.find('#mail-' + mail.ident + ' .from');
+ subject = node.find('#mail-' + mail.ident + ' .mail-list-entry__item-subject')[0];
+ tags = _.map(node.find('#mail-' + mail.ident + ' .mail-list-entry__item-tags .mail-list-entry__item-tags-tag'), function (tag) { return tag.textContent; });
+ date = node.find('#mail-' + mail.ident + ' .mail-list-entry__item-date');
+ from = node.find('#mail-' + mail.ident + ' .mail-list-entry__item-from');
if (subject && subject.textContent.trim() === mail.header.subject) {
equals.subject = true;
diff --git a/web-ui/test/spec/helpers/view_helper.spec.js b/web-ui/test/spec/helpers/view_helper.spec.js
index b2f597c2..19bef15f 100644
--- a/web-ui/test/spec/helpers/view_helper.spec.js
+++ b/web-ui/test/spec/helpers/view_helper.spec.js
@@ -106,5 +106,11 @@ define(['helpers/view_helper'], function (viewHelper) {
expect(window.setTimeout.calls.all()[0].args[1]).toEqual(1);
});
+
+ describe('fingerprint helper', function () {
+ it('should format fingerprint', function () {
+ expect(viewHelper.formatFingerPrint('12345678')).toEqual('1234 5678');
+ });
+ });
});
});