summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md45
-rw-r--r--doc/maintenance.md97
-rwxr-xr-xinstall-pixelated.sh2
-rwxr-xr-xservice/go2
-rw-r--r--service/pixelated/adapter/model/mail.py2
-rw-r--r--service/pixelated/assets/welcome.mail19
-rw-r--r--service/pixelated/config/__init__.py3
-rw-r--r--service/pixelated/config/app_factory.py3
-rw-r--r--service/pixelated/config/args.py22
-rw-r--r--service/pixelated/config/leap_cert.py6
-rw-r--r--service/pixelated/config/register.py10
-rw-r--r--service/pixelated/maintenance.py169
-rw-r--r--service/pixelated/resources/root_resource.py2
-rw-r--r--service/pixelated/resources/sync_info_resource.py46
-rw-r--r--service/pixelated/support/error_handler.py27
-rw-r--r--service/pixelated/support/ext_sync.py23
-rw-r--r--service/setup.py3
-rw-r--r--service/test/functional/features/forward_trash_archive.feature2
-rw-r--r--service/test/functional/features/steps/tag_list.py4
-rw-r--r--service/test/unit/config/test_register.py20
-rw-r--r--service/test/unit/fixtures/mailset/mbox0000000012
-rw-r--r--service/test/unit/fixtures/mailset/mbox0000000112
-rw-r--r--service/test/unit/maintenance/__init__.py15
-rw-r--r--service/test/unit/maintenance/test_commands.py92
-rw-r--r--service/test/unit/resources/test_sync_info_controller.py53
-rw-r--r--web-ui/app/index.html5
-rw-r--r--web-ui/app/js/helpers/view_helper.js23
-rw-r--r--web-ui/app/js/mail_list/ui/mail_item_factory.js1
-rw-r--r--web-ui/app/js/mail_list/ui/mail_items/draft_item.js8
-rw-r--r--web-ui/app/js/mail_list/ui/mail_items/generic_mail_item.js17
-rw-r--r--web-ui/app/js/mail_list/ui/mail_items/mail_item.js48
-rw-r--r--web-ui/app/js/mail_list/ui/mail_items/sent_item.js11
-rw-r--r--web-ui/app/js/mail_list/ui/mail_syncing_progress_bar.js75
-rw-r--r--web-ui/app/js/mail_view/ui/mail_view.js3
-rw-r--r--web-ui/app/js/mixins/with_mail_edit_base.js2
-rw-r--r--web-ui/app/js/page/default.js3
-rw-r--r--web-ui/app/js/tags/ui/tag_list.js1
-rw-r--r--web-ui/app/locales/en-us/translation.json10
-rw-r--r--web-ui/app/locales/en/translation.json10
-rw-r--r--web-ui/app/scss/_colors.scss4
-rw-r--r--web-ui/app/scss/_compose.scss8
-rw-r--r--web-ui/app/scss/_mixins.scss18
-rw-r--r--web-ui/app/scss/_read.scss7
-rw-r--r--web-ui/app/scss/main.scss11
-rw-r--r--web-ui/app/scss/styles.scss3
-rw-r--r--web-ui/app/templates/compose/compose_box.hbs3
-rw-r--r--web-ui/app/templates/mails/draft.hbs4
-rw-r--r--web-ui/app/templates/mails/full_view.hbs13
-rw-r--r--web-ui/app/templates/mails/sent.hbs4
-rw-r--r--web-ui/app/templates/mails/single.hbs4
-rw-r--r--web-ui/app/templates/mails/trash.hbs2
-rw-r--r--web-ui/test/spec/helpers/view_helper.spec.js30
-rw-r--r--web-ui/test/spec/mail_list/ui/mail_items/draft_item.spec.js32
-rw-r--r--web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js2
-rw-r--r--web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js8
-rw-r--r--web-ui/test/spec/mail_view/ui/send_button.spec.js16
-rw-r--r--web-ui/test/spec/mixins/with_mail_edit_base.spec.js4
-rw-r--r--web-ui/test/spec/tags/ui/tag_list.spec.js7
-rw-r--r--web-ui/test/test_data.js35
59 files changed, 753 insertions, 370 deletions
diff --git a/README.md b/README.md
index 4af4dc9b..edf926dd 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,29 @@
Pixelated User Agent
====================
-[![Build Status](https://snap-ci.com/pixelated-project/pixelated-user-agent/branch/master/build_image)](https://snap-ci.com/pixelated-project/pixelated-user-agent/branch/master)
-The Pixelated User Agent is the mail client of the Pixelated ecosystem, it is composed of two parts, a web interface written in javascript and an API written in python that glues that interface with the Pixelated or LEAP Provider.
+[![Build Status](https://snap-ci.com/pixelated-project/pixelated-user-agent/branch/master/build_image)](https://snap-ci.com/pixelated-project/pixelated-user-agent/branch/master) [
+![Coverage Status](https://coveralls.io/repos/pixelated-project/pixelated-user-agent/badge.svg?branch=master)](https://coveralls.io/r/pixelated-project/pixelated-user-agent?branch=master)
+
+The Pixelated User Agent is the mail client of the Pixelated ecosystem. It is composed of two parts, a web interface written in JavaScript ([FlightJS](https://flightjs.github.io/)) and a Python API that interacts with a LEAP Provider, the e-mail platform that Pixelated is built on.
**Pixelated is still in early development state!**
![High level architecture User Agent](https://pixelated-project.org/assets/images/pixelated-user-agent.png)
-## Getting started
-### Registering with a provider
+## Getting started
+
+### Registering with a LEAP provider
* You can create a developer account at our [Dev Provider](https://dev.pixelated-project.org/).
- * There are some other LEAP providers on the [Bitmask page](https://bitmask.net), but they don't support email currently.
- * If you want to run your own provider, see [pixelated-platform](https://github.com/pixelated-project/pixelated-platform).
+ * If you want to run your own LEAP provider, see [pixelated-platform](https://github.com/pixelated-project/pixelated-platform).
### Requirements
- * vagrant
- * virtualbox
+
+ * [virtualbox](https://www.virtualbox.org/wiki/Downloads) - Virtualbox is a virtual machine provider platform. It will be used by Vagrant to create a virtual machine ready to run the Pixelated User Agent.
+ * [vagrant](https://www.vagrantup.com/downloads.html) - 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.
+
+### Instructions
Clone the repository:
@@ -27,25 +32,27 @@ Clone the repository:
From the project root folder, set up the vagrant machine:
- vagrant up source
+ vagrant up
-You can log into the machine and view project root folder with:
+After this is done, you can log into the machine and view project root folder with:
vagrant ssh
-
- Then you need to run the setup:
+ cd /vagrant
+
+Then run the setup:
- cd service
+ cd /vagrant/service
./go setup
-
-From here on you can run the tests for the UI by going to the **web-ui** folder or for the API by going to the **service** folder:
+
+From here on you can run the tests for the UI by going to the **web-ui** folder or for the REST API by going to the **service** folder:
cd /vagrant/web-ui
./go test
-
+
cd /vagrant/service
./go test
-Running the user agent:
+
+To run the user agent:
```
$ pixelated-user-agent --host 0.0.0.0 -lc /vagrant/service/pixelated/certificates/dev.pixelated-project.org.ca.crt
@@ -58,11 +65,11 @@ username
*******************
```
-As soon as the agent starts you will be asked for username, password and the [provider you registered with](https://github.com/pixelated-project/pixelated-user-agent/blob/master/README.md#registering-with-a-provider).
+As soon as the user agent starts you will be asked for username, password and the [provider you registered with](https://github.com/pixelated-project/pixelated-user-agent/blob/master/README.md#registering-with-a-provider).
Now you can see it running on [http://localhost:3333](http://localhost:3333)
-##Debian package
+## Debian package
For people that just want to try the user agent, we have debian packages available in our [repository](http://packages.pixelated-project.org/debian/). To use it you have to add it to your sources list:
diff --git a/doc/maintenance.md b/doc/maintenance.md
new file mode 100644
index 00000000..38c18bf8
--- /dev/null
+++ b/doc/maintenance.md
@@ -0,0 +1,97 @@
+Pixelated User Agent Maintenance
+================================
+
+## Overview
+
+The command line tool pixelated-maintenace allows you to run some common tasks, mostly related to soledad.
+
+```
+usage: pixelated-maintenance [-h] [--debug] [--dispatcher file]
+ [--dispatcher-stdin] [-c <configfile>]
+ [--home HOME] [-lc <leap-provider.crt>]
+ [-lf <leap provider certificate fingerprint>]
+ {reset,load-mails,dump-soledad,sync} ...
+
+pixelated maintenance
+
+positional arguments:
+ {reset,load-mails,dump-soledad,sync}
+ commands
+ reset reset account command
+ load-mails load mails into account
+ dump-soledad dump the soledad database
+ sync sync the soledad database
+
+optional arguments:
+ -h, --help show this help message and exit
+ --debug DEBUG mode.
+ --dispatcher file run in organization mode, the credentials will be read
+ from specified file
+ --dispatcher-stdin run in organization mode, the credentials will be read
+ from stdin
+ -c <configfile>, --config <configfile>
+ use specified file for credentials (for test purposes
+ only)
+ --home HOME The folder where the user agent stores its data.
+ Defaults to ~/.leap
+ -lc <leap-provider.crt>, --leap-provider-cert <leap-provider.crt>
+ use specified file for LEAP provider cert authority
+ certificate (url https://<LEAP-provider-
+ domain>/ca.crt)
+ -lf <leap provider certificate fingerprint>, --leap-provider-cert-fingerprint <leap provider certificate fingerprint>
+ use specified fingerprint to validate connection with
+ LEAP provider
+```
+
+The commands you can run are:
+
+* reset - Use this to remove all mails from your account. Existing encryption keys like your GnuPG key is not affected
+* sync - Sync your soledad database
+* load-mails - Loads existing mails into your account
+* dump-soledad- Get a soledad database dump. Mostly for debugging use cases
+
+Like with other such tools, to get detailed help for a single command, call it with the --help option.
+
+```
+$ pixelated-maintenace load-mails --help
+usage: pixelated-maintenance load-mails [-h] file [file ...]
+
+positional arguments:
+ file file(s) with mail data
+
+ optional arguments:
+ -h, --help show this help message and exit
+```
+
+## How to load mails into an account:
+
+With the load-mails command you are able to import existing mails into your account. The mails have to be in the **mbox** format, i.e. the need a 'From' line in the first line:
+```
+From someone@somedomain.tld
+Subject: This is a testmail
+To: else@somedomain.tld
+X-TW-Pixelated-Tags: nite, macro, trash
+
+This is a test mail
+```
+
+*Preparation*
+
+Steps you might want to consider before importing mails into an account:
+
+* You have started the pixelated-user-agent at least once for this account
+* No pixelated-user-agent is currently running
+
+To import this mail into your soledad database, put it into an empty folder. Let's assume its called just 'example_mails'.
+
+```
+$ pixelated-maintenace load-mails /path/to/example_mails
+```
+
+---
+## Troubleshooting
+
+### load-mails fails with soledad sync errors
+
+This happens sometimes, kill the pixelated-maintenance process and start it again, but this time with the *sync* command. **Don't run the load-mails again as you will end up with double the mails**.
+
diff --git a/install-pixelated.sh b/install-pixelated.sh
index 55fcc172..3e21f251 100755
--- a/install-pixelated.sh
+++ b/install-pixelated.sh
@@ -44,12 +44,14 @@ done
shift $((OPTIND-1))
function check_installed() {
+ set +e
which $1
if [ $? -ne 0 ]; then
echo "## You must have ${1} installed and in the PATH to run Pixelated-User-Agent"
echo "## exiting..."
exit 1
fi
+ set -e
}
function install_node_modules_at_custom_location() {
diff --git a/service/go b/service/go
index f6a836dc..e7864bdc 100755
--- a/service/go
+++ b/service/go
@@ -125,6 +125,8 @@ elif [ "$1" == 'coverage_unit' ]; then
elif [ "$1" == 'coverage_integration' ]; then
runCoverageIntegration "${@:2}"
elif [ "$1" == 'coverage_all' ]; then
+ set -e
+ runPep8
runCoverageUnitAndIntegration "${@:2}"
elif [ "$1" == 'start' ]; then
/usr/bin/env pixelated-user-agent "${@:2}"
diff --git a/service/pixelated/adapter/model/mail.py b/service/pixelated/adapter/model/mail.py
index 8e7b745c..b0b56fec 100644
--- a/service/pixelated/adapter/model/mail.py
+++ b/service/pixelated/adapter/model/mail.py
@@ -341,7 +341,7 @@ class PixelatedMail(Mail):
date = received.split(";")[-1].strip()
else:
# we can't get a date for this mail, so lets just use now
- logger.warning('Encountered a mail with missing date and received header fields. Subject %s' % self.hdoc.content.get('subject', None))
+ logger.warning('Encountered a mail with missing date and received header fields. ID %s' % self.fdoc.content.get('uid', None))
date = pixelated.support.date.iso_now()
return dateparser.parse(date).isoformat()
except (ValueError, TypeError) as e:
diff --git a/service/pixelated/assets/welcome.mail b/service/pixelated/assets/welcome.mail
index 346bfaf6..e85694f1 100644
--- a/service/pixelated/assets/welcome.mail
+++ b/service/pixelated/assets/welcome.mail
@@ -19,10 +19,12 @@ To compose a message look for the big blue button on the top left. You can add t
A bit more about Pixelated
Pixelated is an open source project licensed under AGPL 3.0. It is composed of 3 main parts, the User Agent (what you are using right now), the Dispatcher (what allows you to log in with different accounts to the same instance) and the Platform (which provides the email service you will use to send and receive messages - the server behind the @ sign on your new mail address). You can learn more by visiting https://pixelated-project.org/.
-About this message and understanding examples of message status
-This message was not encrypted, in other words, it could have been read by others at some point during transmission.
-However you can check the authenticity of this message, because the message has a certified sender.
-Whether it is green, it means that the text you are reading has not changed on the way until delivered to you.
+About this message and encryption status
+This message was not encrypted, in other words, it could have been read by others at some point during transmission, like any other email client.
+To send encrypted messages you have to have the public keys of the recipients, in Pixelated you have 2 options:
+send an email to another Pixelated account: public keys are exchanged by default.
+send an email to another email provider: you should had exchanged public keys with the recipients previously.
+
Enjoy your secure messaging!
@@ -60,10 +62,11 @@ s). You can learn more by visiting <a src=3D"https://pixelated-project.org/=
">https://pixelated-project.org/</a>.
</p>
<p>
-<b>About this message and understanding examples of message status</b><br>
-This message was not encrypted, in other words, it could have been read by others at some point during transmission.
-However you can check the authenticity of this message, because the message has a certified sender.
-Whether it is green, it means that the text you are reading has not changed on the way until delivered to you.
+<b>About this message and encryption status</b><br>
+This message was not encrypted, in other words, it could have been read by others at some point during transmission like any other email client.
+To send encrypted messages you have to have the public Keys of the recipients, in Pixelated you have 2 options:
+<p>- sending an email to another Pixelated account: public keys are exchanged by default.
+<br>- sending an email to another email provider: you should had exchanged public keys with the recipients previously.
</p>
<p>
Enjoy your secure messaging!
diff --git a/service/pixelated/config/__init__.py b/service/pixelated/config/__init__.py
index af264c77..f59d684b 100644
--- a/service/pixelated/config/__init__.py
+++ b/service/pixelated/config/__init__.py
@@ -36,8 +36,10 @@ import pixelated.support.ext_protobuf
import pixelated.support.ext_sqlcipher
import pixelated.support.ext_esmtp_sender_factory
import pixelated.support.ext_fetch
+import pixelated.support.ext_sync
import pixelated.support.ext_keymanager_fetch_key
import pixelated.support.ext_requests_urllib3
+from pixelated.support.error_handler import error_handler
def initialize():
@@ -74,6 +76,7 @@ def initialize():
d = deferToThread(init_soledad)
d.addCallback(stop_loading_app)
+ d.addErrback(error_handler)
reactor.callWhenRunning(load_app)
reactor.run()
diff --git a/service/pixelated/config/app_factory.py b/service/pixelated/config/app_factory.py
index 5dcf60cb..51f76741 100644
--- a/service/pixelated/config/app_factory.py
+++ b/service/pixelated/config/app_factory.py
@@ -53,9 +53,8 @@ def init_index_and_remove_dupes(querier, search_engine, mail_service):
return wrapper
-def update_info_sync_and_index_partial(sync_info_controller, search_engine, mail_service):
+def update_index_partial(search_engine, mail_service):
def wrapper(soledad_sync_status):
- sync_info_controller.set_sync_info(soledad_sync_status)
search_engine.index_mails(mails=mail_service.all_mails())
return wrapper
diff --git a/service/pixelated/config/args.py b/service/pixelated/config/args.py
index d3284fab..a5d19369 100644
--- a/service/pixelated/config/args.py
+++ b/service/pixelated/config/args.py
@@ -20,18 +20,24 @@ from pixelated.bitmask_libraries.config import DEFAULT_LEAP_HOME
def parse():
parser = argparse.ArgumentParser(description='Pixelated user agent.')
- parser.add_argument('--debug', action='store_true', help='DEBUG mode.')
- parser.add_argument('--dispatcher', help='run in organization mode, the credentials will be read from specified file', metavar='file')
- parser.add_argument('--dispatcher-stdin', help='run in organization mode, the credentials will be read from stdin', default=False, action='store_true', dest='dispatcher_stdin')
+
+ parser_add_default_arguments(parser)
+
parser.add_argument('--host', default='127.0.0.1', help='the host to run the user agent on')
parser.add_argument('--port', type=int, default=3333, help='the port to run the user agent on')
- parser.add_argument('--home', help='The folder where the user agent stores its data. Defaults to ~/.leap', default=DEFAULT_LEAP_HOME)
- parser.add_argument('-c', '--config', metavar='<configfile>', default=None, help='use specified file for credentials (for test purposes only)')
parser.add_argument('-sk', '--sslkey', metavar='<server.key>', default=None, help='use specified file as web server\'s SSL key (when using the user-agent together with the pixelated-dispatcher)')
parser.add_argument('-sc', '--sslcert', metavar='<server.crt>', default=None, help='use specified file as web server\'s SSL certificate (when using the user-agent together with the pixelated-dispatcher)')
- parser.add_argument('-lc', '--leap-cert', metavar='<leap.crt>', default=None, help='use specified file for LEAP cert authority certificate (url https://<provider-domain>/ca.crt)')
- parser.add_argument('--leap-cert-fingerprint', metavar='<leap certificate fingerprint>', default=None, help='use specified fingerprint to validate connection with leap provider', dest='leap_cert_fingerprint')
parser.add_argument('--register', metavar=('provider', 'username'),
- nargs=2, help='register a new username on the desired provider')
+ nargs=2, help='register a new username on the desired LEAP provider')
args = parser.parse_args()
return args
+
+
+def parser_add_default_arguments(parser):
+ parser.add_argument('--debug', action='store_true', help='DEBUG mode.')
+ parser.add_argument('--dispatcher', help='run in organization mode, the credentials will be read from specified file', metavar='file')
+ parser.add_argument('--dispatcher-stdin', help='run in organization mode, the credentials will be read from stdin', default=False, action='store_true', dest='dispatcher_stdin')
+ parser.add_argument('-c', '--config', metavar='<configfile>', default=None, help='use specified file for credentials (for test purposes only)')
+ parser.add_argument('--home', help='The folder where the user agent stores its data. Defaults to ~/.leap', default=DEFAULT_LEAP_HOME)
+ parser.add_argument('-lc', '--leap-provider-cert', metavar='<leap-provider.crt>', default=None, help='use specified file for LEAP provider cert authority certificate (url https://<LEAP-provider-domain>/ca.crt)')
+ parser.add_argument('-lf', '--leap-provider-cert-fingerprint', metavar='<leap provider certificate fingerprint>', default=None, help='use specified fingerprint to validate connection with LEAP provider', dest='leap_provider_cert_fingerprint')
diff --git a/service/pixelated/config/leap_cert.py b/service/pixelated/config/leap_cert.py
index 22f73720..568f76d9 100644
--- a/service/pixelated/config/leap_cert.py
+++ b/service/pixelated/config/leap_cert.py
@@ -18,9 +18,9 @@ import pixelated.bitmask_libraries.certs as certs
def init_leap_cert(args):
- if args.leap_cert_fingerprint is None:
- certs.LEAP_CERT = args.leap_cert or True
+ if args.leap_provider_cert_fingerprint is None:
+ certs.LEAP_CERT = args.leap_provider_cert or True
certs.LEAP_FINGERPRINT = None
else:
- certs.LEAP_FINGERPRINT = args.leap_cert_fingerprint
+ certs.LEAP_FINGERPRINT = args.leap_provider_cert_fingerprint
certs.LEAP_CERT = False
diff --git a/service/pixelated/config/register.py b/service/pixelated/config/register.py
index 3f93aa8d..d54b10ff 100644
--- a/service/pixelated/config/register.py
+++ b/service/pixelated/config/register.py
@@ -13,6 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+import re
from pixelated.bitmask_libraries.leap_srp import LeapAuthException
from pixelated.bitmask_libraries.register import register_new_user
@@ -20,6 +21,15 @@ from pixelated.bitmask_libraries.register import register_new_user
def register(username, server_name):
try:
+ validate_username(username)
register_new_user(username, server_name)
except LeapAuthException:
print('User already exists')
+ except ValueError:
+ print('Only lowercase letters, digits, . - and _ allowed.')
+
+
+def validate_username(username):
+ accepted_characters = '^[a-z0-9\-\_\.]*$'
+ if not re.match(accepted_characters, username):
+ raise ValueError
diff --git a/service/pixelated/maintenance.py b/service/pixelated/maintenance.py
new file mode 100644
index 00000000..8c20e097
--- /dev/null
+++ b/service/pixelated/maintenance.py
@@ -0,0 +1,169 @@
+#
+# 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/>.
+
+import os
+from functools import partial
+import sys
+import json
+import argparse
+import email
+
+from os.path import join
+from mailbox import mboxMessage
+from pixelated.config.app import App
+from pixelated.config import app_factory
+from pixelated.config.args import parser_add_default_arguments
+from pixelated.config.config_ua import config_user_agent
+from pixelated.config.dispatcher import config_dispatcher
+from pixelated.config.events_server import init_events_server
+from pixelated.config.loading_page import loading
+from pixelated.config.register import register
+from pixelated.config.logging_setup import init_logging
+from pixelated.config.leap_cert import init_leap_cert
+from pixelated.config.soledad import init_soledad_and_user_key
+from twisted.internet import reactor, defer
+from twisted.internet.threads import deferToThread
+
+from leap.mail.imap.memorystore import MemoryStore
+from leap.mail.imap.soledadstore import SoledadStore
+from leap.common.events import register, unregister, events_pb2 as proto
+
+# monkey patching some specifics
+import pixelated.support.ext_protobuf
+import pixelated.support.ext_sqlcipher
+import pixelated.support.ext_esmtp_sender_factory
+import pixelated.support.ext_fetch
+import pixelated.support.ext_keymanager_fetch_key
+import pixelated.support.ext_requests_urllib3
+
+
+def delete_all_mails(args):
+ leap_session, soledad = args
+ generation, docs = soledad.get_all_docs()
+
+ for doc in docs:
+ if doc.content.get('type', None) in ['head', 'cnt', 'flags']:
+ soledad.delete_doc(doc)
+
+ return args
+
+
+@defer.inlineCallbacks
+def load_mails(args, mail_paths):
+ leap_session, soledad = args
+ account = leap_session.account
+
+ for path in mail_paths:
+ print 'Loading mails from %s' % path
+ for root, dirs, files in os.walk(path):
+ mbx = account.getMailbox('INBOX')
+ for f in files:
+ with open(join(root, f), 'r') as fp:
+ m = email.message_from_file(fp)
+ flags = ("\\RECENT",)
+ r = yield mbx.addMessage(m.as_string(), flags=flags, notify_on_disk=False)
+ print 'Added message %s' % m.get('subject')
+ print m.as_string()
+
+ defer.returnValue(args)
+ return
+
+
+def dump_soledad(args):
+ leap_session, soledad = args
+
+ generation, docs = soledad.get_all_docs()
+
+ for doc in docs:
+ print doc
+ print '\n'
+
+ return args
+
+
+def initialize():
+ parser = argparse.ArgumentParser(description='pixelated maintenance')
+ parser_add_default_arguments(parser)
+ subparsers = parser.add_subparsers(help='commands', dest='command')
+ subparsers.add_parser('reset', help='reset account command')
+ mails_parser = subparsers.add_parser('load-mails', help='load mails into account')
+ mails_parser.add_argument('file', nargs='+', help='file(s) with mail data')
+
+ subparsers.add_parser('dump-soledad', help='dump the soledad database')
+ subparsers.add_parser('sync', help='sync the soledad database')
+
+ args = parser.parse_args()
+ app = App()
+
+ init_logging(args)
+ init_leap_cert(args)
+
+ if args.dispatcher or args.dispatcher_stdin:
+ config_dispatcher(app, args)
+ else:
+ config_user_agent(app, args)
+
+ init_events_server()
+
+ def execute_command():
+
+ def init_soledad():
+ return init_soledad_and_user_key(app, args.home)
+
+ def get_soledad_handle(leap_session):
+ soledad = leap_session.soledad_session.soledad
+
+ return leap_session, soledad
+
+ def soledad_sync(args):
+ leap_session, soledad = args
+
+ soledad.sync()
+
+ return args
+
+ d = deferToThread(init_soledad)
+ d.addCallback(get_soledad_handle)
+ d.addCallback(soledad_sync)
+ if args.command == 'reset':
+ d.addCallback(delete_all_mails)
+ elif args.command == 'load-mails':
+ d.addCallback(load_mails, args.file)
+ elif args.command == 'dump-soledad':
+ d.addCallback(dump_soledad)
+ elif args.command == 'sync':
+ # nothing to do here, sync is already part of the chain
+ pass
+ else:
+ print 'Unsupported command: %s' % args.command
+ d.addCallback(soledad_sync)
+ d.addCallback(shutdown)
+ d.addErrback(shutdown_on_error)
+
+ reactor.callWhenRunning(execute_command)
+ reactor.run()
+
+
+def shutdown(args):
+ reactor.stop()
+
+
+def shutdown_on_error(error):
+ print error
+ reactor.stop()
+
+if __name__ == 'main':
+ initialize()
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
index b45bd00f..4c0c47ac 100644
--- a/service/pixelated/resources/root_resource.py
+++ b/service/pixelated/resources/root_resource.py
@@ -4,7 +4,6 @@ from pixelated.resources.contacts_resource import ContactsResource
from pixelated.resources.features_resource import FeaturesResource
from pixelated.resources.mail_resource import MailResource
from pixelated.resources.mails_resource import MailsResource
-from pixelated.resources.sync_info_resource import SyncInfoResource
from pixelated.resources.tags_resource import TagsResource
from pixelated.resources.keys_resource import KeysResource
from twisted.web.resource import Resource
@@ -28,7 +27,6 @@ class RootResource(Resource):
self.putChild('attachment', AttachmentsResource(querier))
self.putChild('contacts', ContactsResource(search_engine))
self.putChild('features', FeaturesResource())
- self.putChild('sync_info', SyncInfoResource())
self.putChild('tags', TagsResource(search_engine))
self.putChild('mails', MailsResource(mail_service, draft_service))
self.putChild('mail', MailResource(mail_service))
diff --git a/service/pixelated/resources/sync_info_resource.py b/service/pixelated/resources/sync_info_resource.py
deleted file mode 100644
index 791c5add..00000000
--- a/service/pixelated/resources/sync_info_resource.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# 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 respond_json
-from twisted.web.resource import Resource
-
-
-class SyncInfoResource(Resource):
-
- isLeaf = True
-
- def __init__(self):
- Resource.__init__(self)
- self.current = 0
- self.total = 0
-
- def _get_progress(self):
- if self.total == 0:
- return 0
- return self.current / float(self.total)
-
- def set_sync_info(self, soledad_sync_status):
- self.current, self.total = [int(x) for x in soledad_sync_status.content.split('/')]
-
- def render_GET(self, request):
- _sync_info = {
- 'is_syncing': self.current != self.total,
- 'count': {
- 'current': self.current,
- 'total': self.total,
- 'progress': self._get_progress()
- }
- }
- return respond_json(_sync_info, request)
diff --git a/service/pixelated/support/error_handler.py b/service/pixelated/support/error_handler.py
new file mode 100644
index 00000000..1a0e1a11
--- /dev/null
+++ b/service/pixelated/support/error_handler.py
@@ -0,0 +1,27 @@
+#
+# 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 requests.exceptions import SSLError
+
+
+def error_handler(excp):
+ if excp.type is SSLError:
+ print """
+ SSL Error: Please check your certificates or read our wiki for further info:
+ https://github.com/pixelated-project/pixelated-user-agent/wiki/Configuring-and-using-SSL-Certificates-for-LEAP-provider
+ Error reference: %s
+ """ % excp.getErrorMessage()
+ else:
+ raise excp
diff --git a/service/pixelated/support/ext_sync.py b/service/pixelated/support/ext_sync.py
new file mode 100644
index 00000000..d35eed3e
--- /dev/null
+++ b/service/pixelated/support/ext_sync.py
@@ -0,0 +1,23 @@
+import leap.soledad.client as client
+import logging
+import urlparse
+from leap.soledad.client.events import (
+ SOLEDAD_DONE_DATA_SYNC,
+ signal
+)
+
+
+def patched_sync(self, defer_decryption=True):
+ if self._db:
+ try:
+ local_gen = self._db.sync(
+ urlparse.urljoin(self.server_url, 'user-%s' % self._uuid),
+ creds=self._creds, autocreate=False,
+ defer_decryption=defer_decryption)
+ signal(SOLEDAD_DONE_DATA_SYNC, self._uuid)
+ return local_gen
+ except Exception as e:
+ client.logger.error("Soledad exception when syncing: %s - %s" % (e.__class__.__name__, e.message))
+
+
+client.Soledad.sync = patched_sync
diff --git a/service/setup.py b/service/setup.py
index ed6be9c0..973cac47 100644
--- a/service/setup.py
+++ b/service/setup.py
@@ -70,7 +70,8 @@ setup(name='pixelated-user-agent',
],
entry_points={
'console_scripts': [
- 'pixelated-user-agent = pixelated.config:initialize'
+ 'pixelated-user-agent = pixelated.config:initialize',
+ 'pixelated-maintenance = pixelated.maintenance:initialize'
]
},
include_package_data=True
diff --git a/service/test/functional/features/forward_trash_archive.feature b/service/test/functional/features/forward_trash_archive.feature
index 6e959c32..1d373b88 100644
--- a/service/test/functional/features/forward_trash_archive.feature
+++ b/service/test/functional/features/forward_trash_archive.feature
@@ -31,4 +31,4 @@ Feature: forward and deletion
When I choose to trash
# Then I see that mail under the 'trash' tag
When I select the tag 'trash'
- And I open the first mail in the mail list \ No newline at end of file
+ And I open the first mail in the mail list
diff --git a/service/test/functional/features/steps/tag_list.py b/service/test/functional/features/steps/tag_list.py
index b3e09c22..443c5173 100644
--- a/service/test/functional/features/steps/tag_list.py
+++ b/service/test/functional/features/steps/tag_list.py
@@ -21,13 +21,13 @@ def click_first_element_with_class(context, classname):
elements[0].click()
-def is_side_nax_expanded(context):
+def is_side_nav_expanded(context):
e = context.browser.find_elements_by_class_name('content')[0].get_attribute('class').count(u'move-right') == 1
return e
def expand_side_nav(context):
- if is_side_nax_expanded(context):
+ if is_side_nav_expanded(context):
return
toggle = context.browser.find_elements_by_class_name('side-nav-toggle')[0]
diff --git a/service/test/unit/config/test_register.py b/service/test/unit/config/test_register.py
new file mode 100644
index 00000000..7db2b000
--- /dev/null
+++ b/service/test/unit/config/test_register.py
@@ -0,0 +1,20 @@
+import unittest
+
+from pixelated.config.register import validate_username
+
+
+class TestRegister(unittest.TestCase):
+
+ def test_username_raises_error_when_it_contains_uppercase_letters(self):
+ with self.assertRaises(ValueError):
+ validate_username('INVALIDUSERNAME')
+
+ def test_username_raises_error_when_it_contains_special_characters(self):
+ with self.assertRaises(ValueError):
+ validate_username('invalid@username')
+
+ def test_username_pass_when_valid(self):
+ try:
+ validate_username('a.valid_username-123')
+ except:
+ self.fail('Valid username should not raise an exception')
diff --git a/service/test/unit/fixtures/mailset/mbox00000000 b/service/test/unit/fixtures/mailset/mbox00000000
new file mode 100644
index 00000000..3d01c203
--- /dev/null
+++ b/service/test/unit/fixtures/mailset/mbox00000000
@@ -0,0 +1,12 @@
+From darby.senger@zemlak.biz
+Subject: Itaque consequatur repellendus provident sunt quia.
+To: carmel@murazikortiz.name
+X-TW-Pixelated-Tags: nite, macro, trash
+Date: Tue, 21 Apr 2015 08:43:27 +0000 (UTC)
+
+Dignissimos ducimus veritatis. Est tenetur consequatur quia occaecati. Vel sit sit voluptas.
+
+Earum distinctio eos. Accusantium qui sint ut quia assumenda. Facere dignissimos inventore autem sit amet. Pariatur voluptatem sint est.
+
+Ut recusandae praesentium aspernatur. Exercitationem amet placeat deserunt quae consequatur eum. Unde doloremque suscipit quia.
+
diff --git a/service/test/unit/fixtures/mailset/mbox00000001 b/service/test/unit/fixtures/mailset/mbox00000001
new file mode 100644
index 00000000..fc76bba2
--- /dev/null
+++ b/service/test/unit/fixtures/mailset/mbox00000001
@@ -0,0 +1,12 @@
+From madeline.littel@sanfordruel.com
+Subject: Error illum dignissimos autem eos aspernatur.
+To: phyllis@stiedemann.net
+X-TW-Pixelated-Tags: instadaily, inspiration
+Date: Tue, 21 Apr 2015 08:43:27 +0000 (UTC)
+
+Et inventore placeat aut. Sint eveniet labore perferendis nulla. Maiores rerum sunt perferendis. Voluptate iure hic et ut blanditiis ad veritatis. Labore occaecati rerum.
+
+Sit fugiat aliquam voluptates ipsum non. Dolor quo sapiente. Itaque sed odit velit. Qui et aspernatur et fugiat voluptas eum est. Et expedita eos rerum nisi ut eum vero.
+
+Ab et est cumque. Qui nostrum perferendis. Labore est tempora porro est quia deleniti consequatur. Fugit quis ipsa.
+
diff --git a/service/test/unit/maintenance/__init__.py b/service/test/unit/maintenance/__init__.py
new file mode 100644
index 00000000..2756a319
--- /dev/null
+++ b/service/test/unit/maintenance/__init__.py
@@ -0,0 +1,15 @@
+#
+# 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/>.
diff --git a/service/test/unit/maintenance/test_commands.py b/service/test/unit/maintenance/test_commands.py
new file mode 100644
index 00000000..f23655d8
--- /dev/null
+++ b/service/test/unit/maintenance/test_commands.py
@@ -0,0 +1,92 @@
+#
+# 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/>.
+import unittest
+import email
+
+from pixelated.maintenance import delete_all_mails, load_mails
+from pixelated.bitmask_libraries.session import LeapSession
+from leap.mail.imap.account import SoledadBackedAccount
+from leap.soledad.client import Soledad
+from leap.soledad.common.document import SoledadDocument
+from mock import MagicMock, ANY
+from os.path import join, dirname
+
+
+class TestCommands(unittest.TestCase):
+
+ def setUp(self):
+ self.leap_session = MagicMock(spec=LeapSession)
+ self.soledad = MagicMock(spec=Soledad)
+ self.account = MagicMock(spec=SoledadBackedAccount)
+ self.mailbox = MagicMock()
+ self.leap_session.account = self.account
+ self.account.getMailbox.return_value = self.mailbox
+
+ self.args = (self.leap_session, self.soledad)
+
+ def test_delete_all_mails_supports_empty_doclist(self):
+ self.soledad.get_all_docs.return_value = (1, [])
+
+ delete_all_mails(self.args)
+
+ self.assertFalse(self.soledad.delete_doc.called)
+
+ def test_delete_all_mails(self):
+ doc = MagicMock(spec=SoledadDocument)
+ doc.content = {'type': 'head'}
+ self.soledad.get_all_docs.return_value = (1, [doc])
+
+ delete_all_mails(self.args)
+
+ self.soledad.delete_doc.assert_called_once_with(doc)
+
+ def test_only_mail_documents_are_deleted(self):
+ docs = self._create_docs_of_type(['head', 'cnt', 'flags', 'mbx', 'foo', None])
+ self.soledad.get_all_docs.return_value = (1, docs)
+
+ delete_all_mails(self.args)
+
+ for doc in docs:
+ if doc.content['type'] in ['head', 'cnt', 'flags']:
+ self.soledad.delete_doc.assert_any_call(doc)
+ self.assertEqual(3, len(self.soledad.delete_doc.mock_calls))
+
+ def _create_docs_of_type(self, type_list):
+ return [self._create_doc_type(t) for t in type_list]
+
+ def _create_doc_type(self, doc_type):
+ doc = MagicMock(spec=SoledadDocument)
+ doc.content = {'type': doc_type}
+ return doc
+
+ def test_load_mails_empty_path_list(self):
+ load_mails(self.args, [])
+
+ self.assertFalse(self.mailbox.called)
+
+ def test_load_mails_adds_mails(self):
+ mail_root = join(dirname(__file__), '..', 'fixtures', 'mailset')
+
+ foo = load_mails(self.args, [mail_root])
+
+ self.assertTrue(self.mailbox.addMessage.called)
+ self.mailbox.addMessage.assert_any_call(self._mail_content(join(mail_root, 'mbox00000000')), flags=("\\RECENT",), notify_on_disk=False)
+ self.mailbox.addMessage.assert_any_call(self._mail_content(join(mail_root, 'mbox00000001')), flags=("\\RECENT",), notify_on_disk=False)
+
+ def _mail_content(self, mail_file):
+ with open(mail_file, 'r') as fp:
+ m = email.message_from_file(fp)
+ return m.as_string()
diff --git a/service/test/unit/resources/test_sync_info_controller.py b/service/test/unit/resources/test_sync_info_controller.py
deleted file mode 100644
index 1285237b..00000000
--- a/service/test/unit/resources/test_sync_info_controller.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# 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/>.
-import unittest
-import json
-
-from test.support.test_helper import request_mock
-from pixelated.resources.sync_info_resource import SyncInfoResource
-from mockito import mock
-
-
-class SyncInfoResourceTest(unittest.TestCase):
-
- def setUp(self):
- self.dummy_request = request_mock()
- self.controller = SyncInfoResource()
-
- def _set_count(self, current, total):
- soledad_sync_data = mock()
- soledad_sync_data.content = "%s/%s" % (current, total)
- self.controller.set_sync_info(soledad_sync_data)
-
- def get_sync_info(self):
- return json.loads(self.controller.render_GET(self.dummy_request))
-
- def test_is_not_syncing_if_total_is_equal_to_current(self):
- self._set_count(total=0, current=0)
-
- sync_info = self.get_sync_info()
-
- self.assertFalse(sync_info['is_syncing'])
-
- def test_is_syncing_if_total_is_not_equal_to_current_and_adds_count(self):
- self._set_count(total=10, current=5)
-
- sync_info = self.get_sync_info()
-
- self.assertTrue(sync_info['is_syncing'])
- self.assertEquals(5, sync_info['count']['current'])
- self.assertEquals(10, sync_info['count']['total'])
- self.assertEquals(0.5, sync_info['count']['progress'])
diff --git a/web-ui/app/index.html b/web-ui/app/index.html
index d6e03de0..cbdae267 100644
--- a/web-ui/app/index.html
+++ b/web-ui/app/index.html
@@ -20,7 +20,7 @@
<div class="off-canvas-wrap move-right menu" data-offcanvas>
<div class="inner-wrap">
<section id="left-pane" class="left-off-canvas-menu">
- <a class="left-off-canvas-logo" href="#">
+ <a class="left-off-canvas-logo side-nav-toggle" href="#">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="30.4 316.8 555.2 155.2" enable-background="new 30.4 316.8 555.2 155.2" xml:space="preserve">
<g>
@@ -77,14 +77,13 @@
</section>
<section id="middle-pane" class="small-9 medium-12 large-12 columns no-padding">
- <div id="mail-syncing-progress-bar" ></div>
<ul id="mail-list">
</ul>
</section>
</article>
<section id="right-pane" class="small-7 medium-7 large-7 columns">
- </section>
+ </section>
</div>
</div>
diff --git a/web-ui/app/js/helpers/view_helper.js b/web-ui/app/js/helpers/view_helper.js
index a682ae5e..8d841cc7 100644
--- a/web-ui/app/js/helpers/view_helper.js
+++ b/web-ui/app/js/helpers/view_helper.js
@@ -83,15 +83,6 @@ define(
return res;
}
- function getFormattedDate(date){
- var today = createTodayDate();
- if (date.getTime() > today.getTime()) {
- return fixedSizeNumber(date.getHours(), 2) + ':' + fixedSizeNumber(date.getMinutes(), 2);
- } else {
- return '' + date.getFullYear() + '-' + fixedSizeNumber(date.getMonth() + 1, 2) + '-' + fixedSizeNumber(date.getDate(), 2);
- }
- }
-
function createTodayDate() {
var today = new Date();
today.setHours(0);
@@ -120,11 +111,23 @@ define(
return '\n\n' + prependFrom(mail) + mail.textPlainBody.replace(/^/mg, '> ');
}
+ function formatDate(dateString) {
+ var date = new Date(dateString);
+ var today = createTodayDate();
+ if (date.getTime() > today.getTime()) {
+ return fixedSizeNumber(date.getHours(), 2) + ':' + fixedSizeNumber(date.getMinutes(), 2);
+ } else {
+ return '' + date.getFullYear() + '-' + fixedSizeNumber(date.getMonth() + 1, 2) + '-' + fixedSizeNumber(date.getDate(), 2);
+ }
+ }
+
+ Handlebars.registerHelper('formatDate', formatDate);
+ Handlebars.registerHelper('formatStatusClasses', formatStatusClasses);
+
return {
formatStatusClasses: formatStatusClasses,
formatMailBody: formatMailBody,
moveCaretToEndOfText: moveCaretToEndOfText,
- getFormattedDate: getFormattedDate,
quoteMail: quoteMail,
i18n: i18n
};
diff --git a/web-ui/app/js/mail_list/ui/mail_item_factory.js b/web-ui/app/js/mail_list/ui/mail_item_factory.js
index 3c815401..ddfa4c62 100644
--- a/web-ui/app/js/mail_list/ui/mail_item_factory.js
+++ b/web-ui/app/js/mail_list/ui/mail_item_factory.js
@@ -40,6 +40,7 @@ define(
var mailItemContainer = $('<li>', { id: 'mail-' + mail.ident});
nodeToAttachTo.append(mailItemContainer);
+ mail.currentTag = currentTag;
var mailToCreate = MAIL_ITEM_TYPE[mail.mailbox] || GenericMailItem;
mailToCreate.attachTo(mailItemContainer, {
mail: mail,
diff --git a/web-ui/app/js/mail_list/ui/mail_items/draft_item.js b/web-ui/app/js/mail_list/ui/mail_items/draft_item.js
index fda6c3f8..57fbafd5 100644
--- a/web-ui/app/js/mail_list/ui/mail_items/draft_item.js
+++ b/web-ui/app/js/mail_list/ui/mail_items/draft_item.js
@@ -33,16 +33,14 @@ define(
if (this.isOpeningOnANewTab(ev)) {
return;
}
- this.trigger(document, events.dispatchers.rightPane.openDraft, { ident: this.attr.ident });
- this.trigger(document, events.ui.mail.updateSelected, { ident: this.attr.ident });
- this.trigger(document, events.router.pushState, { mailIdent: this.attr.ident });
+ this.trigger(document, events.dispatchers.rightPane.openDraft, { ident: this.attr.mail.ident });
+ this.trigger(document, events.ui.mail.updateSelected, { ident: this.attr.mail.ident });
+ this.trigger(document, events.router.pushState, { mailIdent: this.attr.mail.ident });
ev.preventDefault(); // don't let the hashchange trigger a popstate
};
this.after('initialize', function () {
- this.initializeAttributes();
this.render();
- this.attachListeners();
if (this.attr.isChecked) {
this.checkCheckbox();
diff --git a/web-ui/app/js/mail_list/ui/mail_items/generic_mail_item.js b/web-ui/app/js/mail_list/ui/mail_items/generic_mail_item.js
index b700eeeb..939f7e1b 100644
--- a/web-ui/app/js/mail_list/ui/mail_items/generic_mail_item.js
+++ b/web-ui/app/js/mail_list/ui/mail_items/generic_mail_item.js
@@ -38,14 +38,14 @@ define(
updateMailStatusToRead.call(this);
return;
}
- this.trigger(document, events.ui.mail.open, { ident: this.attr.ident });
- this.trigger(document, events.router.pushState, { mailIdent: this.attr.ident });
+ this.trigger(document, events.ui.mail.open, { ident: this.attr.mail.ident });
+ this.trigger(document, events.router.pushState, { mailIdent: this.attr.mail.ident });
ev.preventDefault(); // don't let the hashchange trigger a popstate
};
function updateMailStatusToRead() {
if (!_.contains(this.attr.mail.status, this.status.READ)) {
- var mail_read_data = { ident: this.attr.ident, tags: this.attr.tags, mailbox: this.attr.mailbox };
+ var mail_read_data = { ident: this.attr.mail.ident, tags: this.attr.mail.tags, mailbox: this.attr.mail.mailbox };
this.trigger(document, events.mail.read, mail_read_data);
this.attr.mail.status.push(this.status.READ);
this.$node.addClass(viewHelpers.formatStatusClasses(this.attr.mail.status));
@@ -53,16 +53,16 @@ define(
}
this.openMail = function (ev, data) {
- if (data.ident !== this.attr.ident) {
+ if (data.ident !== this.attr.mail.ident) {
return;
}
updateMailStatusToRead.call(this);
- this.trigger(document, events.ui.mail.updateSelected, { ident: this.attr.ident });
+ this.trigger(document, events.ui.mail.updateSelected, { ident: this.attr.mail.ident });
};
this.updateTags = function(ev, data) {
- if(data.ident === this.attr.ident){
+ if(data.ident === this.attr.mail.ident){
this.attr.tags = data.tags;
if(!_.contains(this.attr.tags, this.attr.tag)) {
this.teardown();
@@ -73,16 +73,13 @@ define(
};
this.deleteMail = function(ev, data) {
- if(data.mail.ident === this.attr.ident){
+ if(data.mail.ident === this.attr.mail.ident){
this.teardown();
}
};
this.after('initialize', function () {
- this.initializeAttributes();
- this.attr.tagsForListView = _.without(this.attr.tags, this.attr.tag);
this.render();
- this.attachListeners();
if (this.attr.isChecked) {
this.checkCheckbox();
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 bcd0444b..266db926 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
@@ -1,6 +1,6 @@
/*
* 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
@@ -26,14 +26,10 @@ define(
function mailItem() {
this.updateSelected = function (ev, data) {
- if(data.ident === this.attr.ident) { this.doSelect(); }
+ if (data.ident === this.attr.mail.ident) { this.doSelect(); }
else { this.doUnselect(); }
};
- this.formattedDate = function (date) {
- return viewHelper.getFormattedDate(new Date(date));
- };
-
this.isOpeningOnANewTab = function (ev) {
return ev.metaKey || ev.ctrlKey || ev.which === 2;
};
@@ -46,9 +42,12 @@ define(
this.$node.removeClass('selected');
};
- this.triggerMailChecked = function (ev, data) {
- var eventToTrigger = ev.target.checked ? events.ui.mail.checked : events.ui.mail.unchecked;
- this.trigger(document, eventToTrigger, { mail: this.attr.mail});
+ this.doMailChecked = function (ev) {
+ if (ev.target.checked) {
+ this.checkCheckbox();
+ } else {
+ this.uncheckCheckbox();
+ }
};
this.checkboxElement = function () {
@@ -57,42 +56,31 @@ define(
this.checkCheckbox = function () {
this.checkboxElement().prop('checked', true);
- this.triggerMailChecked({'target': {'checked': true}});
+ this.trigger(document, events.ui.mail.checked, { mail: this.attr.mail});
};
this.uncheckCheckbox = function () {
this.checkboxElement().prop('checked', false);
- this.triggerMailChecked({'target': {'checked': false}});
+ this.trigger(document, events.ui.mail.unchecked, { mail: this.attr.mail});
};
this.render = function () {
- this.attr.tagsForListView = _.without(this.attr.tags, this.attr.tag);
- var mailItemHtml = templates.mails[this.attr.templateType](this.attr);
+ 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(this.attr.statuses);
- if(this.attr.selected) { this.doSelect(); }
+ this.$node.addClass(viewHelper.formatStatusClasses(this.attr.mail.status));
+ if (this.attr.selected) { this.doSelect(); }
this.on(this.$node.find('a'), 'click', this.triggerOpenMail);
};
- this.initializeAttributes = function () {
- var mail = this.attr.mail;
- this.attr.ident = mail.ident;
- this.attr.header = mail.header;
- this.attr.ident = mail.ident;
- this.attr.statuses = viewHelper.formatStatusClasses(mail.status);
- this.attr.tags = mail.tags;
- this.attr.attachments = mail.attachments;
- this.attr.mailbox = mail.mailbox;
- this.attr.header.formattedDate = this.formattedDate(mail.header.date);
- };
-
- this.attachListeners = function () {
- this.on(this.$node.find('input[type=checkbox]'), 'change', this.triggerMailChecked);
+ this.after('initialize', function () {
+ this.on(this.$node.find('input[type=checkbox]'), 'change', this.doMailChecked);
this.on(document, events.ui.mails.cleanSelected, this.doUnselect);
this.on(document, events.ui.tag.select, this.doUnselect);
+ this.on(document, events.ui.tag.select, this.uncheckCheckbox);
this.on(document, events.ui.mails.uncheckAll, this.uncheckCheckbox);
this.on(document, events.ui.mails.checkAll, this.checkCheckbox);
- };
+ });
}
return mailItem;
diff --git a/web-ui/app/js/mail_list/ui/mail_items/sent_item.js b/web-ui/app/js/mail_list/ui/mail_items/sent_item.js
index 3cfa25bd..9e511068 100644
--- a/web-ui/app/js/mail_list/ui/mail_items/sent_item.js
+++ b/web-ui/app/js/mail_list/ui/mail_items/sent_item.js
@@ -32,23 +32,20 @@ define(
if (this.isOpeningOnANewTab(ev)) {
return;
}
- this.trigger(document, events.ui.mail.open, { ident: this.attr.ident });
- this.trigger(document, events.router.pushState, { mailIdent: this.attr.ident });
+ this.trigger(document, events.ui.mail.open, { ident: this.attr.mail.ident });
+ this.trigger(document, events.router.pushState, { mailIdent: this.attr.mail.ident });
ev.preventDefault(); // don't let the hashchange trigger a popstate
};
this.openMail = function (ev, data) {
- if (data.ident !== this.attr.ident) {
+ if (data.ident !== this.attr.mail.ident) {
return;
}
- this.trigger(document, events.ui.mail.updateSelected, { ident: this.attr.ident });
+ this.trigger(document, events.ui.mail.updateSelected, { ident: this.attr.mail.ident });
};
this.after('initialize', function () {
- this.initializeAttributes();
- this.attr.tagsForListView = _.without(this.attr.tags, this.attr.tag);
this.render();
- this.attachListeners();
if (this.attr.isChecked) {
this.checkCheckbox();
diff --git a/web-ui/app/js/mail_list/ui/mail_syncing_progress_bar.js b/web-ui/app/js/mail_list/ui/mail_syncing_progress_bar.js
deleted file mode 100644
index 8d4eebb5..00000000
--- a/web-ui/app/js/mail_list/ui/mail_syncing_progress_bar.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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/>.
- */
-
-define(
- [
- 'flight/lib/component'
- ],
-
- function (defineComponent) {
- 'use strict';
-
- return defineComponent(mailSyncingProgressbar);
-
- function mailSyncingProgressbar() {
- this.updateProgressBar = function (count) {
- this.attr.syncingMails = true;
-
- this.$node.css({
- 'width': (count.progress * 100).toFixed(2) + '%',
- 'transition': '1000ms linear',
- 'background-color': '#FF7902',
- 'height': '3px'
- });
-
- };
-
- this.resetProgressBar = function () {
- this.$node.removeAttr('style');
- this.attr.syncingMails = false;
- clearInterval(this.attr.updateIntervalId);
- };
-
- this.doUpdate = function () {
- $.getJSON('/sync_info')
- .success(function (data) {
- if (data.is_syncing) {
- this.updateProgressBar(data.count);
- } else {
- this.resetProgressBar();
- }
- }.bind(this))
- .error(function () {
- clearInterval(this.attr.poolIntervalId);
- this.resetProgressBar();
- }.bind(this));
- };
-
- this.checkForMailSyncing = function () {
- if (this.attr.syncingMails) {
- return;
- }
- this.attr.updateIntervalId = setInterval(this.doUpdate.bind(this), 1000);
- };
-
- this.after('initialize', function () {
- this.checkForMailSyncing();
- this.attr.poolIntervalId = setInterval(this.checkForMailSyncing.bind(this), 20000);
- });
- }
- }
-);
diff --git a/web-ui/app/js/mail_view/ui/mail_view.js b/web-ui/app/js/mail_view/ui/mail_view.js
index 578dcbb9..71a67e5a 100644
--- a/web-ui/app/js/mail_view/ui/mail_view.js
+++ b/web-ui/app/js/mail_view/ui/mail_view.js
@@ -47,9 +47,6 @@ define(
this.displayMail = function (event, data) {
this.attr.mail = data.mail;
- var date = new Date(data.mail.header.date);
- data.mail.header.formattedDate = viewHelpers.getFormattedDate(date);
-
var signed, encrypted;
data.mail.security_casing = data.mail.security_casing || {};
diff --git a/web-ui/app/js/mixins/with_mail_edit_base.js b/web-ui/app/js/mixins/with_mail_edit_base.js
index 5efb8967..848fe026 100644
--- a/web-ui/app/js/mixins/with_mail_edit_base.js
+++ b/web-ui/app/js/mixins/with_mail_edit_base.js
@@ -74,7 +74,7 @@ define(
if (!_.isUndefined(recipients) && !_.isEmpty(recipients)) {
var recipientsUpdatedData = {
newRecipients: recipients,
- name: recipientsType
+ recipientsName: recipientsType
};
this.trigger(document, events.ui.recipients.updated, recipientsUpdatedData);
}
diff --git a/web-ui/app/js/page/default.js b/web-ui/app/js/page/default.js
index 16918d90..1571202e 100644
--- a/web-ui/app/js/page/default.js
+++ b/web-ui/app/js/page/default.js
@@ -20,7 +20,6 @@ define(
'mail_list_actions/ui/mail_list_actions',
'user_alerts/ui/user_alerts',
'mail_list/ui/mail_list',
- 'mail_list/ui/mail_syncing_progress_bar',
'mail_view/ui/no_message_selected_pane',
'mail_view/ui/mail_view',
'mail_view/ui/mail_actions',
@@ -51,7 +50,6 @@ define(
mailListActions,
userAlerts,
mailList,
- mailSyncingProgressBar,
noMessageSelectedPane,
mailView,
mailViewActions,
@@ -84,7 +82,6 @@ define(
userAlerts.attachTo('#user-alerts');
mailList.attachTo('#mail-list');
- mailSyncingProgressBar.attachTo('#mail-syncing-progress-bar');
mailListActions.attachTo('#list-actions');
searchTrigger.attachTo('#search-trigger');
diff --git a/web-ui/app/js/tags/ui/tag_list.js b/web-ui/app/js/tags/ui/tag_list.js
index d6f6f76c..a2172c6d 100644
--- a/web-ui/app/js/tags/ui/tag_list.js
+++ b/web-ui/app/js/tags/ui/tag_list.js
@@ -98,7 +98,6 @@ define(
this.after('initialize', function() {
this.on(document, events.tags.received, this.displayTags);
this.on(document, events.ui.tag.select, this.updateCurrentTag);
- this.on(document, events.ui.tag.select, events.ui.mails.uncheckAll);
this.renderTagListTemplate();
});
}
diff --git a/web-ui/app/locales/en-us/translation.json b/web-ui/app/locales/en-us/translation.json
index b6fd6f0a..330e38b6 100644
--- a/web-ui/app/locales/en-us/translation.json
+++ b/web-ui/app/locales/en-us/translation.json
@@ -47,11 +47,11 @@
"encrypted encryption-valid": "Encrypted",
"not-encrypted": "Not Encrypted",
"signed": "Certified sender",
- "signed signature-revoked": "Sender could not be securely identified.",
- "signed signature-expired": "Sender could not be securely identified.",
- "signed signature-not-trusted": "Sender and/or message cannot be trusted.",
- "signed signature-unknown": "Sender and/or message cannot be trusted.",
- "not-signed": "Sender could not be securely identified.",
+ "signed signature-revoked": "Sender could not be securely identifiedw",
+ "signed signature-expired": "Sender could not be securely identified",
+ "signed signature-not-trusted": "Sender and/or message cannot be trusted",
+ "signed signature-unknown": "Sender and/or message cannot be trusted",
+ "not-signed": "Uncertified sender",
"send-button": "Send",
"sending-mail": "Sending...",
"draft-button": "Save Draft",
diff --git a/web-ui/app/locales/en/translation.json b/web-ui/app/locales/en/translation.json
index b6fd6f0a..a360cc02 100644
--- a/web-ui/app/locales/en/translation.json
+++ b/web-ui/app/locales/en/translation.json
@@ -47,11 +47,11 @@
"encrypted encryption-valid": "Encrypted",
"not-encrypted": "Not Encrypted",
"signed": "Certified sender",
- "signed signature-revoked": "Sender could not be securely identified.",
- "signed signature-expired": "Sender could not be securely identified.",
- "signed signature-not-trusted": "Sender and/or message cannot be trusted.",
- "signed signature-unknown": "Sender and/or message cannot be trusted.",
- "not-signed": "Sender could not be securely identified.",
+ "signed signature-revoked": "Sender could not be securely identified",
+ "signed signature-expired": "Sender could not be securely identified",
+ "signed signature-not-trusted": "Sender and/or message cannot be trusted",
+ "signed signature-unknown": "Sender and/or message cannot be trusted",
+ "not-signed": "Uncertified sender",
"send-button": "Send",
"sending-mail": "Sending...",
"draft-button": "Save Draft",
diff --git a/web-ui/app/scss/_colors.scss b/web-ui/app/scss/_colors.scss
index 4a1eba33..320a8d1a 100644
--- a/web-ui/app/scss/_colors.scss
+++ b/web-ui/app/scss/_colors.scss
@@ -34,5 +34,5 @@ $error: #D93C38;
$attention: #F6A41C;
$success: #50BA5B;
-$will_be_encrypted: #41cd60;
-$wont_be_encrypted: #F6A40A;
+$will_be_encrypted: $success;
+$wont_be_encrypted: $attention;
diff --git a/web-ui/app/scss/_compose.scss b/web-ui/app/scss/_compose.scss
index c026b866..acff745d 100644
--- a/web-ui/app/scss/_compose.scss
+++ b/web-ui/app/scss/_compose.scss
@@ -24,7 +24,8 @@
// COMPOSE PANE
#compose-box, #draft-box, #reply-box {
- margin: 0 0 50px 10px;
+ margin: 5px 0 50px 30px;
+ padding: 0;
.input-container {
border-bottom: 1px solid #DDD;
padding: 1px;
@@ -44,7 +45,6 @@
&#subject {
font-size: 1.6875rem;
line-height: 1.4;
- margin-top: 26px;
}
}
textarea {
@@ -85,6 +85,10 @@
}
}
+ button.close-mail-button {
+ margin: 1px;
+ }
+
@include recipients;
}
diff --git a/web-ui/app/scss/_mixins.scss b/web-ui/app/scss/_mixins.scss
index 2ce4999a..e1f06425 100644
--- a/web-ui/app/scss/_mixins.scss
+++ b/web-ui/app/scss/_mixins.scss
@@ -133,7 +133,7 @@
input {
display: inline;
font-size: 1em;
- padding: 2px 5px;
+ padding: 1px 5px;
width: 120px;
margin: 0;
}
@@ -202,11 +202,27 @@
&.selected {
border: 1px solid #666666;
}
+
+ &:before {
+ font-family: FontAwesome;
+ padding-right: 4px;
+
+ }
&.encrypted {
border-bottom-color: $will_be_encrypted;
+
+ &:before {
+ color: $will_be_encrypted;
+ content: "\f023 ";
+ }
}
&.not-encrypted {
border-bottom-color: $wont_be_encrypted;
+
+ &:before {
+ color: $wont_be_encrypted;
+ content: "\f13e ";
+ }
}
background-color: #F5F5F5;
border: 1px solid #D9D9D9;
diff --git a/web-ui/app/scss/_read.scss b/web-ui/app/scss/_read.scss
index 7235df72..d621f672 100644
--- a/web-ui/app/scss/_read.scss
+++ b/web-ui/app/scss/_read.scss
@@ -32,6 +32,13 @@
height: 27px;
margin-right: 3px;
}
+
+ .full-view-header {
+ display:inline-block;
+ padding-top: 5px;
+ width:95%;
+ flex-shrink:1;
+ }
}
h3 {
margin-bottom: 0;
diff --git a/web-ui/app/scss/main.scss b/web-ui/app/scss/main.scss
index f4ce2c4b..ed9b90c4 100644
--- a/web-ui/app/scss/main.scss
+++ b/web-ui/app/scss/main.scss
@@ -26,17 +26,6 @@ header#main {
padding: 0;
}
-.tip-msg {
- padding: 10px;
- margin: 8px 20px -25px 20px;
- background: $warning;
- color: darken($warning, 50%);
- font-size: 0.9em;
- i {
- margin-right: 5px;
- }
-}
-
.text-right {
text-align: right;
}
diff --git a/web-ui/app/scss/styles.scss b/web-ui/app/scss/styles.scss
index 6dd82c4e..4f2a56ee 100644
--- a/web-ui/app/scss/styles.scss
+++ b/web-ui/app/scss/styles.scss
@@ -344,7 +344,8 @@ section {
visibility: hidden;
opacity: 0;
transition-duration: 500ms;
- height: 220px;
+ height: 100%;
+ max-height: 220px;
overflow: auto;
background-color: lighten($navigation_background,1);
diff --git a/web-ui/app/templates/compose/compose_box.hbs b/web-ui/app/templates/compose/compose_box.hbs
index 300f8049..d5501e69 100644
--- a/web-ui/app/templates/compose/compose_box.hbs
+++ b/web-ui/app/templates/compose/compose_box.hbs
@@ -1,9 +1,6 @@
<button class="close-mail-button">
<i class="fa fa-times"></i>
</button>
-<div class="tip-msg">
- <i class="fa fa-lightbulb-o"></i>{{t "Don't worry about recipients right now, you'll be able to add them just before sending." }}
-</div>
<input type="text" id="subject" value="{{subject}}" placeholder="{{t 'Subject'}}" tabindex="1"/>
<textarea id="text-box" placeholder="{{t 'Body'}}" tabindex="2">{{body}}</textarea>
diff --git a/web-ui/app/templates/mails/draft.hbs b/web-ui/app/templates/mails/draft.hbs
index 87862f34..c3d2fa5b 100644
--- a/web-ui/app/templates/mails/draft.hbs
+++ b/web-ui/app/templates/mails/draft.hbs
@@ -2,8 +2,8 @@
<input type="checkbox"/>
</span>
<span>
- <a href="/#/{{ tag }}/mail/{{ ident }}">
- <span class="sent-date">{{ header.formattedDate }}</span>
+ <a href="/#/{{ currentTag }}/mail/{{ ident }}">
+ <span class="sent-date">{{ formatDate header.date }}</span>
<div class="from">
{{t 'to:'}}
diff --git a/web-ui/app/templates/mails/full_view.hbs b/web-ui/app/templates/mails/full_view.hbs
index 50ac2776..77994860 100644
--- a/web-ui/app/templates/mails/full_view.hbs
+++ b/web-ui/app/templates/mails/full_view.hbs
@@ -8,9 +8,9 @@
</button>
- <div style="display:inline-block;padding-top: 5px;width:95%;flex-shrink:1" >
+ <div class="full-view-header">
- <div class="column large-10 no-padding security-status">
+ <div class="column large-12 no-padding security-status">
{{#if signatureStatus}}
<span class="{{signatureStatus}}">
{{t signatureStatus }}
@@ -22,10 +22,7 @@
</span>
{{/if}}
</div>
- <div class="column large-2 no-padding text-right">
- <span class="received-date">{{ header.formattedDate }}</span>
- </div>
- <div class="recipients column large-12 no-padding">
+ <div class="recipients column large-10 no-padding">
<span class="from">
{{#if header.from }}
{{ header.from }}
@@ -36,7 +33,9 @@
<i class="fa fa-long-arrow-right"></i>
{{{formatRecipients header}}}
</div>
-
+ <div class="recipients column large-2 text-right">
+ <span class="received-date">{{ formatDate header.date }}</span>
+ </div>
<div>
<h3 class="subjectArea column large-10 no-padding">
<span class="subject">{{ header.subject }}</span>
diff --git a/web-ui/app/templates/mails/sent.hbs b/web-ui/app/templates/mails/sent.hbs
index e4b49b37..a0712124 100644
--- a/web-ui/app/templates/mails/sent.hbs
+++ b/web-ui/app/templates/mails/sent.hbs
@@ -2,8 +2,8 @@
<input type="checkbox"/>
</span>
<span>
- <a href="/#/{{ tag }}/mail/{{ ident }}">
- <span class="sent-date">{{ header.formattedDate }}</span>
+ <a href="/#/{{ currentTag }}/mail/{{ ident }}">
+ <span class="sent-date">{{ formatDate header.date }}</span>
<div class="from">
{{t 'to:'}}
diff --git a/web-ui/app/templates/mails/single.hbs b/web-ui/app/templates/mails/single.hbs
index 90023713..47d600fb 100644
--- a/web-ui/app/templates/mails/single.hbs
+++ b/web-ui/app/templates/mails/single.hbs
@@ -2,8 +2,8 @@
<input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
</span>
<span>
- <a href="/#/{{ tag }}/mail/{{ ident }}">
- <span class="received-date">{{ header.formattedDate }}
+ <a href="/#/{{ currentTag }}/mail/{{ ident }}">
+ <span class="received-date">{{ formatDate header.date }}
{{#if attachments}}
<div class="attachment-indicator">
<i class="fa fa-paperclip"></i>
diff --git a/web-ui/app/templates/mails/trash.hbs b/web-ui/app/templates/mails/trash.hbs
index a74c9606..4475aeb0 100644
--- a/web-ui/app/templates/mails/trash.hbs
+++ b/web-ui/app/templates/mails/trash.hbs
@@ -2,7 +2,7 @@
<input type="checkbox" {{#if isChecked }}checked="true"{{/if}} />
</span>
<span>
- <a href="/#/{{ tag }}/mail/{{ ident }}">
+ <a href="/#/{{ currentTag }}/mail/{{ ident }}">
<span class="received-date">{{ header.formattedDate }}
{{#if attachments}}
<div class="attachment-indicator">
diff --git a/web-ui/test/spec/helpers/view_helper.spec.js b/web-ui/test/spec/helpers/view_helper.spec.js
index 888c6cda..655ba181 100644
--- a/web-ui/test/spec/helpers/view_helper.spec.js
+++ b/web-ui/test/spec/helpers/view_helper.spec.js
@@ -9,41 +9,45 @@ define(['helpers/view_helper'], function (viewHelper) {
describe('quote email', function() {
it('should add > to body text', function() {
- testData.rawMail.mail.textPlainBody = 'First Line\nSecond Line';
+ testData.parsedMail.simpleTextPlain.textPlainBody = 'First Line\nSecond Line';
- var quotedMail = viewHelper.quoteMail(testData.rawMail.mail);
+ var quotedMail = viewHelper.quoteMail(testData.parsedMail.simpleTextPlain);
expect(quotedMail).toContain('> First Line\n> Second Line');
});
it('should add the mail sender information', function() {
- testData.rawMail.mail.textPlainBody = 'First Line\nSecond Line';
+ testData.parsedMail.simpleTextPlain.textPlainBody = 'First Line\nSecond Line';
- var quotedMail = viewHelper.quoteMail(testData.rawMail.mail);
+ var quotedMail = viewHelper.quoteMail(testData.parsedMail.simpleTextPlain);
- expect(quotedMail).toContain('On Wed Jun 04 2014 17:41:13 GMT+0000 (UTC), <laurel@hamill.info> wrote');
+ expect(quotedMail).toContain('<laurel@hamill.info>');
});
});
- describe('getFormmattedDate', function() {
+ describe('formatDate', function() {
+ var template;
+ beforeEach(function () {
+ template = Handlebars.compile('{{formatDate date}}');
+ });
+
it('formats correctly a Date for today', function() {
var d = new Date();
- var dtest = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 14, 2, 36);
-
- var res = viewHelper.getFormattedDate(dtest);
+ var mailDate = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 14, 2, 36);
- expect(res).toEqual('14:02');
+ var result = template({ date: mailDate.toISOString() });
+ expect(result).toEqual('14:02');
});
it('formats correctly a Date for a specific day', function() {
- var dtest = new Date(2013, 2, 13, 7, 56, 1);
+ var mailDate = new Date(2013, 2, 13, 7, 56, 1);
- var res = viewHelper.getFormattedDate(dtest);
+ var result = template({ date: mailDate.toISOString() });
// This expectation is weird for the month - JS Dates have date numbers be zero-indexed, thus the discrepancy
// Specifically, the 2 in the constructor DOES match the 3 in the expectation below.
- expect(res).toEqual('2013-03-13');
+ expect(result).toEqual('2013-03-13');
});
});
diff --git a/web-ui/test/spec/mail_list/ui/mail_items/draft_item.spec.js b/web-ui/test/spec/mail_list/ui/mail_items/draft_item.spec.js
new file mode 100644
index 00000000..fbe285c6
--- /dev/null
+++ b/web-ui/test/spec/mail_list/ui/mail_items/draft_item.spec.js
@@ -0,0 +1,32 @@
+describeComponent('mail_list/ui/mail_items/draft_item', function () {
+ 'use strict';
+
+ var mail;
+
+ beforeEach(function () {
+ mail = Pixelated.testData().parsedMail.draft;
+
+ this.setupComponent('<li></li>', {
+ mail: mail,
+ selected: false,
+ templateType: 'single'
+ });
+ });
+
+ it('should de-select the item if a new mail is composed', function () {
+ this.component.$node.addClass('selected');
+
+ $(document).trigger(Pixelated.events.ui.composeBox.newMessage);
+
+ expect(this.component.$node).not.toHaveClass('selected');
+ });
+
+ it('should trigger the openDraft event when clicked', function () {
+ var openDraftEvent = spyOnEvent(document, Pixelated.events.dispatchers.rightPane.openDraft);
+
+ this.$node.find('a').click();
+
+ expect(openDraftEvent).toHaveBeenTriggeredOnAndWith(document, { ident: 'B2432' });
+ });
+});
+
diff --git a/web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js b/web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js
index 88735302..87d38595 100644
--- a/web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js
+++ b/web-ui/test/spec/mail_list/ui/mail_items/generic_mail_item.spec.js
@@ -7,11 +7,11 @@ describeComponent('mail_list/ui/mail_items/generic_mail_item', function () {
mail = Pixelated.testData().parsedMail.simpleTextPlain;
mail.tags = [];
mail.mailbox = 'inbox';
+ mail.currentTag = 'inbox';
this.setupComponent('<li></li>', {
mail: mail,
selected: false,
- tag: 'inbox',
templateType: 'single'
});
});
diff --git a/web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js b/web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js
index 058040c9..03f95e54 100644
--- a/web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js
+++ b/web-ui/test/spec/mail_list/ui/mail_items/mail_item.spec.js
@@ -20,6 +20,14 @@ describeMixin('mail_list/ui/mail_items/mail_item', function () {
checkbox = this.component.$node.find('input[type=checkbox]');
});
+ it('unchecks itself when another tag is selected', function () {
+ this.component.checkCheckbox();
+ this.component.trigger(document, Pixelated.events.ui.tag.select, { tag: 'amazing'});
+
+ expect(mailUncheckedEvent).toHaveBeenTriggeredOn(document);
+ expect(checkbox.prop('checked')).toBe(false);
+ });
+
it('checkCheckbox checks it and triggers events.ui.mail.checked', function () {
this.component.checkCheckbox();
diff --git a/web-ui/test/spec/mail_view/ui/send_button.spec.js b/web-ui/test/spec/mail_view/ui/send_button.spec.js
index 17de1531..351b4a08 100644
--- a/web-ui/test/spec/mail_view/ui/send_button.spec.js
+++ b/web-ui/test/spec/mail_view/ui/send_button.spec.js
@@ -25,6 +25,14 @@ describeComponent('mail_view/ui/send_button', function () {
expect(this.$node).not.toBeDisabled();
});
+
+ it('gets enabled if recipients:updated also with invalid email', function () {
+ $(document).trigger(Pixelated.events.ui.recipients.inputFieldHasCharacters, { name: 'to' });
+ $(document).trigger(Pixelated.events.ui.recipients.updated, { newRecipients: ['InvalidEmail']});
+
+ expect(this.$node).not.toBeDisabled();
+ expect(this.$node.text()).toBe('Send');
+ });
});
describe('multiple events', function () {
@@ -63,14 +71,6 @@ describeComponent('mail_view/ui/send_button', function () {
expect(this.$node).toBeDisabled();
});
-
- it('gets disabled if recipients:updated with invalid email', function () {
- $(document).trigger(Pixelated.events.ui.recipients.inputFieldHasCharacters, { name: 'to' });
- $(document).trigger(Pixelated.events.ui.recipients.updated, { newRecipients: ['InvalidEmail']});
-
- expect(this.$node).not.toBeDisabled();
- expect(this.$node.text()).toBe('Send');
- });
});
describe('on click', function () {
diff --git a/web-ui/test/spec/mixins/with_mail_edit_base.spec.js b/web-ui/test/spec/mixins/with_mail_edit_base.spec.js
index 8f495399..940b4b5e 100644
--- a/web-ui/test/spec/mixins/with_mail_edit_base.spec.js
+++ b/web-ui/test/spec/mixins/with_mail_edit_base.spec.js
@@ -18,8 +18,8 @@ describeMixin('mixins/with_mail_edit_base', function () {
recipients: { to: ['foobar@mail.com'], cc: [] }
});
- expect(recipientsUpdatedEvent).toHaveBeenTriggeredOnAndWith(document, { newRecipients: ['foobar@mail.com'], name: 'to'});
- expect(recipientsUpdatedEvent).not.toHaveBeenTriggeredOnAndWith(document, { newRecipients: [], name: 'cc'});
+ expect(recipientsUpdatedEvent).toHaveBeenTriggeredOnAndWith(document, { newRecipients: ['foobar@mail.com'], recipientsName: 'to'});
+ expect(recipientsUpdatedEvent).not.toHaveBeenTriggeredOnAndWith(document, { newRecipients: [], recipientsName: 'cc'});
});
});
diff --git a/web-ui/test/spec/tags/ui/tag_list.spec.js b/web-ui/test/spec/tags/ui/tag_list.spec.js
index e84c68aa..f92f72af 100644
--- a/web-ui/test/spec/tags/ui/tag_list.spec.js
+++ b/web-ui/test/spec/tags/ui/tag_list.spec.js
@@ -62,13 +62,6 @@ describeComponent('tags/ui/tag_list', function () {
expect(this.component.attr.currentTag).toEqual('amazing');
});
- it('should uncheck all emails when a new tag is selected', function () {
- var uncheckAllEvent = spyOnEvent(document, Pixelated.events.ui.mails.uncheckAll);
- $(document).trigger(Pixelated.events.ui.tag.select, { tag: 'amazing'});
-
- expect(uncheckAllEvent).toHaveBeenTriggeredOn(document);
- });
-
it('resets the tag lists when loading tags', function () {
var tagList = [tag('tag1', 1, false), tag('tag2', 2, true), tag('tag3', 3, true)];
$(document).trigger(Pixelated.events.tags.received, {tags: tagList});
diff --git a/web-ui/test/test_data.js b/web-ui/test/test_data.js
index f09260c9..446fd7c6 100644
--- a/web-ui/test/test_data.js
+++ b/web-ui/test/test_data.js
@@ -170,6 +170,38 @@ define(function() {
}
};
+ var draftMail = {
+ status: [],
+ header: {'from': 'jed_waelchi@cummerata.info',
+ cc: [],
+ bcc: [],
+ to: [],
+ date: '2015-04-09T18:30:18.998999-03:00',
+ subject: 'bla'},
+ ident: 'B2432',
+ replying: {'single': 'jed_waelchi@cummerata.info',
+ all: {
+ 'to-field': ['jed_waelchi@cummerata.info'],
+ 'cc-field': []
+ }
+ },
+ attachments: [],
+ textPlainBody: 'bla',
+ tags: [],
+ htmlBody: null,
+ mailbox: 'drafts',
+ security_casing: {'locks': [],
+ imprints: [{'state': 'no_signature_information'}]
+ },
+ isSentMail: function() { return false; },
+ isDraftMail: function() { return false; },
+ replyToAddress: function() { return { to: ['jed_waelchi@cummerata.info'], cc: [] }; },
+ replyToAllAddress: function() { return { to: ['jed_waelchi@cummerata.info'], cc: [] }; },
+ isMailMultipartAlternative: function () { return false; },
+ availableBodyPartsContentType: function () { return []; },
+ getMailPartByContentType: function () { return; }
+ };
+
var testData = {
rawMail: {
mail: rawMail,
@@ -182,7 +214,8 @@ define(function() {
},
parsedMail: {
simpleTextPlain: simpleTextPlainMail,
- html: htmlNoEncodingMail
+ html: htmlNoEncodingMail,
+ draft: draftMail
}
};