summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Vagrantfile1
-rw-r--r--service/pixelated/resources/root_resource.py2
-rw-r--r--service/pixelated/resources/sandbox_resource.py34
-rw-r--r--service/requirements.txt2
-rw-r--r--service/test/functional/features/steps/mail_view.py5
-rw-r--r--service/test/load/locustfile.py78
-rw-r--r--service/test/unit/resources/test_sandbox_resource.py38
-rw-r--r--web-ui/app/index.html6
-rw-r--r--web-ui/app/js/helpers/sanitizer.js32
-rw-r--r--web-ui/app/js/mail_list/ui/mail_list.js1
-rw-r--r--web-ui/app/js/mail_view/ui/mail_view.js57
-rw-r--r--web-ui/app/js/page/events.js2
-rw-r--r--web-ui/app/js/sandbox.js9
-rw-r--r--web-ui/app/js/search/results_highlighter.js29
-rw-r--r--web-ui/app/js/user_alerts/ui/user_alerts.js14
-rw-r--r--web-ui/app/sandbox.html17
-rw-r--r--web-ui/app/scss/_alerts.scss23
-rw-r--r--web-ui/app/scss/_mascot.scss47
-rw-r--r--web-ui/app/scss/_mixins.scss2
-rw-r--r--web-ui/app/scss/_others.scss7
-rw-r--r--web-ui/app/scss/_read.scss7
-rw-r--r--web-ui/app/scss/base/_colors.scss (renamed from web-ui/app/scss/_colors.scss)0
-rw-r--r--web-ui/app/scss/base/_fonts.scss (renamed from web-ui/app/scss/opensans.scss)0
-rw-r--r--web-ui/app/scss/base/_scaffolding.scss10
-rw-r--r--web-ui/app/scss/layout/_message-panel.scss9
-rw-r--r--web-ui/app/scss/main.scss37
-rw-r--r--web-ui/app/scss/mixins/_position-helpers.scss9
-rw-r--r--web-ui/app/scss/sandbox.scss20
-rw-r--r--web-ui/app/scss/style.scss35
-rw-r--r--web-ui/app/scss/templates/_no-content-placeholder.scss5
-rw-r--r--web-ui/app/scss/vendor/_foundation.scss (renamed from web-ui/app/scss/foundation.scss)0
-rw-r--r--web-ui/app/scss/vendor/_reset.scss (renamed from web-ui/app/scss/reset.scss)0
-rw-r--r--web-ui/app/scss/views/_message-panel.scss26
-rw-r--r--web-ui/app/scss/views/_no-mails-available.scss3
-rw-r--r--web-ui/app/scss/views/_no-message-selected.scss14
-rw-r--r--web-ui/app/templates/compose/no_mails_available.hbs6
-rw-r--r--web-ui/app/templates/compose/no_message_selected.hbs4
-rw-r--r--web-ui/app/templates/mails/full_view.hbs1
-rw-r--r--web-ui/app/templates/search/search_trigger.hbs2
-rw-r--r--web-ui/app/templates/user_alerts/message.hbs2
-rw-r--r--web-ui/bower.json3
-rw-r--r--web-ui/config/package.sh20
-rw-r--r--web-ui/package.json1
-rw-r--r--web-ui/test/spec/helpers/sanitizer.spec.js6
-rw-r--r--web-ui/test/spec/mail_view/ui/mail_view.spec.js6
-rw-r--r--web-ui/test/spec/mail_view/ui/no_mails_available_pane.spec.js6
-rw-r--r--web-ui/test/spec/search/results_highlighter.spec.js14
-rw-r--r--web-ui/test/spec/user_alerts/ui/user_alerts.spec.js6
48 files changed, 493 insertions, 165 deletions
diff --git a/Vagrantfile b/Vagrantfile
index ba925079..828382b9 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -30,5 +30,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.provider "virtualbox" do |v, override|
v.memory = 1024
override.vm.network :forwarded_port, guest: 3333, host: 3333 # do NOT add host_ip in this line. It is not necessary
+ override.vm.network :forwarded_port, guest: 8089, host: 8089
end
end
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
index 86435d89..109dc08e 100644
--- a/service/pixelated/resources/root_resource.py
+++ b/service/pixelated/resources/root_resource.py
@@ -20,6 +20,7 @@ from string import Template
from pixelated.resources import BaseResource, UnAuthorizedResource
from pixelated.resources.attachments_resource import AttachmentsResource
+from pixelated.resources.sandbox_resource import SandboxResource
from pixelated.resources.contacts_resource import ContactsResource
from pixelated.resources.features_resource import FeaturesResource
from pixelated.resources.feedback_resource import FeedbackResource
@@ -75,6 +76,7 @@ class RootResource(BaseResource):
return csrf_input and csrf_input == xsrf_token
def initialize(self, portal=None, disclaimer_banner=None):
+ self._child_resources.add('sandbox', SandboxResource(self._static_folder))
self._child_resources.add('assets', File(self._static_folder))
self._child_resources.add('keys', KeysResource(self._services_factory))
self._child_resources.add(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory))
diff --git a/service/pixelated/resources/sandbox_resource.py b/service/pixelated/resources/sandbox_resource.py
new file mode 100644
index 00000000..28e8c9be
--- /dev/null
+++ b/service/pixelated/resources/sandbox_resource.py
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2016 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 twisted.web.static import File
+
+
+class SandboxResource(File):
+ CSP_HEADER_VALUES = "sandbox allow-popups allow-scripts;" \
+ "default-src 'self';" \
+ "style-src *;" \
+ "script-src *;" \
+ "font-src *;" \
+ "img-src *;" \
+ "object-src 'none';" \
+ "connect-src 'none';"
+
+ def render_GET(self, request):
+ request.setHeader('Content-Security-Policy', self.CSP_HEADER_VALUES)
+ request.setHeader('X-Content-Security-Policy', self.CSP_HEADER_VALUES)
+ request.setHeader('X-Webkit-CSP', self.CSP_HEADER_VALUES)
+ return super(SandboxResource, self).render_GET(request)
diff --git a/service/requirements.txt b/service/requirements.txt
index 95a0f4f8..b74b7f94 100644
--- a/service/requirements.txt
+++ b/service/requirements.txt
@@ -4,7 +4,7 @@ https://launchpad.net/dirspec/stable-13-10/13.10/+download/dirspec-13.10.tar.gz
--allow-external dirspec --allow-unverified dirspec
https://launchpad.net/u1db/stable-13-10/13.10/+download/u1db-13.10.tar.bz2
pyasn1==0.1.8
-requests==2.0.0
+requests==2.9.1
srp==1.0.4
whoosh==2.5.7
pycryptopp
diff --git a/service/test/functional/features/steps/mail_view.py b/service/test/functional/features/steps/mail_view.py
index 82fc28af..c0e9e22b 100644
--- a/service/test/functional/features/steps/mail_view.py
+++ b/service/test/functional/features/steps/mail_view.py
@@ -25,8 +25,11 @@ def impl(context, subject):
@then('I see that the body reads \'{expected_body}\'')
def impl(context, expected_body):
- e = find_element_by_css_selector(context, '#mail-view .bodyArea')
+ find_element_by_css_selector(context, '#read-sandbox')
+ context.browser.switch_to_frame('read-sandbox')
+ e = find_element_by_css_selector(context, 'body')
assert e.text == expected_body
+ context.browser.switch_to_default_content()
@then('that email has the \'{tag}\' tag')
diff --git a/service/test/load/locustfile.py b/service/test/load/locustfile.py
index 68e39433..0c2ed518 100644
--- a/service/test/load/locustfile.py
+++ b/service/test/load/locustfile.py
@@ -1,6 +1,5 @@
import os
import json
-import time
from random import randint
from leap.auth import SRPAuth
@@ -9,9 +8,13 @@ from locust import HttpLocust, TaskSet, task
from pixelated.resources.login_resource import LoginResource
LEAP_PROVIDER = os.environ.get('LEAP_PROVIDER', 'dev.pixelated-project.org')
-LEAP_SERVER_HOST = os.environ.get('LEAP_SERVER_HOST', 'https://api.%s:4430' % LEAP_PROVIDER)
-LEAP_VERIFY_CERTIFICATE = os.environ.get('LEAP_VERIFY_CERTIFICATE', '~/.leap/ca.crt')
-MAX_NUMBER_USER = os.environ.get('MAX_NUMBER_USER', 10000)
+LEAP_SERVER_HOST = os.environ.get(
+ 'LEAP_SERVER_HOST',
+ 'https://api.%s:4430' % LEAP_PROVIDER)
+LEAP_VERIFY_CERTIFICATE = os.environ.get(
+ 'LEAP_VERIFY_CERTIFICATE',
+ '~/.leap/ca.crt')
+MAX_NUMBER_USER = os.environ.get('MAX_NUMBER_USER', 100)
INVITES_FILENAME = os.environ.get('INVITES_FILENAME', '/tmp/invite_codes.txt')
INVITES_ENABLED = os.environ.get('INVITES_ENABLED', 'true') == 'true'
@@ -23,53 +26,94 @@ def load_invite_from_number(number):
class UserBehavior(TaskSet):
+ def __init__(self, *args, **kwargs):
+ super(UserBehavior, self).__init__(*args, **kwargs)
+ self.cookies = {}
+
def on_start(self):
- """ on_start is called when a Locust start before any task is scheduled """
self.login()
def _get_or_create_user(self, number):
- srp_auth = SRPAuth(LEAP_SERVER_HOST, os.path.expanduser(LEAP_VERIFY_CERTIFICATE))
+ srp_auth = SRPAuth(
+ LEAP_SERVER_HOST,
+ os.path.expanduser(LEAP_VERIFY_CERTIFICATE))
username, password = ('loadtest%d' % number), ('password_%d' % number)
try:
srp_auth.authenticate(username, password)
except SRPAuthenticationError:
- invite_code = load_invite_from_number(number) if INVITES_ENABLED else None
+ invite_code = None
+ if INVITES_ENABLED:
+ invite_code = load_invite_from_number(number)
+
srp_auth.register(username, password, invite_code)
return username, password
def login(self):
number = randint(1, int(MAX_NUMBER_USER))
username, password = self._get_or_create_user(number)
- self.client.post("/%s" % LoginResource.BASE_URL, {"username": username, "password": password})
+ response = self.client.post(
+ "/%s" % LoginResource.BASE_URL,
+ {"username": username, "password": password},
+ verify=False)
+ self.cookies.update(response.cookies.get_dict())
+ resp = self.client.get("/", verify=False)
+ self.cookies.update(resp.cookies.get_dict())
self.username = username
- time.sleep(5)
@task(1)
def index(self):
- self.client.get("/")
+ self.client.get("/", verify=False)
@task(2)
def mail_box(self):
- self.client.get("/mails?q=tag:'inbox'&p=1&w=25")
+ self.client.get("/mails?q=tag:'inbox'&p=1&w=25", verify=False)
@task(3)
def send_mail(self):
- payload = {"tags": ["drafts"], "body": "some text lorem ipsum", "attachments": [], "ident": "",
- "header": {"to": ["%s@%s" % (self.username, LEAP_PROVIDER)], "cc": [], "bcc": [], "subject": "load testing"}}
- with self.client.post('/mails', json=payload, catch_response=True) as email_response:
+ payload = {
+ "tags": ["drafts"],
+ "body": "some text lorem ipsum",
+ "attachments": [],
+ "ident": "",
+ "header": {
+ "to": ["%s@%s" % (self.username, LEAP_PROVIDER)],
+ "cc": [],
+ "bcc": [],
+ "subject": "load testing"}}
+
+ self.cookies.update(
+ self.client.get("/", verify=False).cookies.get_dict())
+ print(self.cookies)
+ with self.client.post(
+ '/mails',
+ json=payload,
+ catch_response=True,
+ cookies=self.cookies,
+ headers={
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-XSRF-TOKEN': self.cookies['XSRF-TOKEN']}) as email_response:
if email_response.status_code == 201:
email_id = json.loads(email_response.content)['ident']
print email_id
self.delete_mail(email_id)
else:
- email_response.failure('Error: email not Sent, status code: %s' % email_response.status_code)
+ email_response.failure(
+ 'Error: email not Sent, status code: %s' % (
+ email_response.status_code))
def delete_mail(self, ident):
payload = {"idents": [ident]}
- self.client.post('/mails/delete', json=payload)
+ self.client.post(
+ '/mails/delete',
+ json=payload,
+ cookies=self.cookies,
+ verify=False,
+ headers={
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-XSRF-TOKEN': self.cookies['XSRF-TOKEN']})
class WebsiteUser(HttpLocust):
task_set = UserBehavior
- min_wait = 3000
+ min_wait = 5000
max_wait = 15000
diff --git a/service/test/unit/resources/test_sandbox_resource.py b/service/test/unit/resources/test_sandbox_resource.py
new file mode 100644
index 00000000..3db43e12
--- /dev/null
+++ b/service/test/unit/resources/test_sandbox_resource.py
@@ -0,0 +1,38 @@
+import os
+import unittest
+
+from twisted.internet import defer
+from twisted.web.test.requesthelper import DummyRequest
+
+from pixelated.resources.sandbox_resource import SandboxResource
+from test.unit.resources import DummySite
+
+
+class TestSandBoxResource(unittest.TestCase):
+ def setUp(self):
+ static_folder = os.path.dirname(os.path.abspath(__file__))
+ self.resource = SandboxResource(static_folder)
+ self.resource.isLeaf = True
+ self.web = DummySite(self.resource)
+
+ @defer.inlineCallbacks
+ def test_render_GET_should_set_sandbox_csp_header(self):
+ request = DummyRequest(['/sandbox'])
+ request.method = 'GET'
+ request.isSecure = lambda: True
+ request.redirect = lambda _: 'irrelevant'
+
+ expected_csp_headers = "sandbox allow-popups allow-scripts;" \
+ "default-src 'self';" \
+ "style-src *;" \
+ "script-src *;" \
+ "font-src *;" \
+ "img-src *;" \
+ "object-src 'none';" \
+ "connect-src 'none';"
+
+ yield self.web.get(request)
+
+ self.assertEquals(expected_csp_headers, request.outgoingHeaders.get('X-Content-Security-Policy'.lower()))
+ self.assertEquals(expected_csp_headers, request.outgoingHeaders.get('Content-Security-Policy'.lower()))
+ self.assertEquals(expected_csp_headers, request.outgoingHeaders.get('X-Webkit-CSP'.lower()))
diff --git a/web-ui/app/index.html b/web-ui/app/index.html
index 2d35662c..4d6f3037 100644
--- a/web-ui/app/index.html
+++ b/web-ui/app/index.html
@@ -9,8 +9,7 @@
<meta name="viewport" content="width=device-width">
<link href="assets/bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="assets/bower_components/jquery-file-upload/css/jquery.fileupload.css" rel="stylesheet" type="text/css">
-<link href="assets/css/opensans.css" rel="stylesheet" type="text/css">
-<link rel="stylesheet" href="assets/css/main.css">
+<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
@@ -56,7 +55,7 @@
</div>
<div class="off-canvas-wrap content" data-offcanvas>
- <header id="main" >
+ <header class="message-panel-container" >
<div id="user-alerts" class="message-panel"></div>
</header>
@@ -100,6 +99,7 @@
<script src="assets/bower_components/foundation/js/foundation/foundation.reveal.js" ></script>
<script src="assets/bower_components/foundation/js/foundation/foundation.offcanvas.js"></script>
<script src="assets/js/foundation/initialize_foundation.js"></script>
+<script src="assets/bower_components/iframe-resizer/js/iframeResizer.min.js"></script>
<script src="assets/bower_components/requirejs/require.js" data-main="assets/js/main.js"></script>
<!--usemin_end-->
diff --git a/web-ui/app/js/helpers/sanitizer.js b/web-ui/app/js/helpers/sanitizer.js
index eea1f0f7..443e8602 100644
--- a/web-ui/app/js/helpers/sanitizer.js
+++ b/web-ui/app/js/helpers/sanitizer.js
@@ -23,6 +23,16 @@ define(['DOMPurify', 'he'], function (DOMPurify, he) {
*/
var sanitizer = {};
+ sanitizer.whitelist = [{
+ // highlight tag open
+ pre: '&#x3C;&#x65;&#x6D;&#x20;&#x63;&#x6C;&#x61;&#x73;&#x73;&#x3D;&#x22;&#x73;&#x65;&#x61;&#x72;&#x63;&#x68;&#x2D;&#x68;&#x69;&#x67;&#x68;&#x6C;&#x69;&#x67;&#x68;&#x74;&#x22;&#x3E;',
+ post: '<em class="search-highlight">'
+ }, {
+ // highlight tag close
+ pre: '&#x3C;&#x2F;&#x65;&#x6D;&#x3E;',
+ post: '</em>'
+ }];
+
/**
* Adds html line breaks to a plaintext with line breaks (incl carriage return)
*
@@ -55,16 +65,24 @@ define(['DOMPurify', 'he'], function (DOMPurify, he) {
};
/**
- * Runs a given dirty body through he, thereby encoding everything
- * as HTML entities.
- *
- * @param {string} dirtyBody The unsanitized string
- * @return {string} Safe-to-display HTML string
- */
+ * Runs a given dirty body through he, thereby encoding everything
+ * as HTML entities.
+ *
+ * @param {string} dirtyBody The unsanitized string
+ * @return {string} Safe-to-display HTML string
+ */
sanitizer.purifyText = function (dirtyBody) {
- return he.encode(dirtyBody, {
+ var escapedBody = he.encode(dirtyBody, {
encodeEverything: true
});
+
+ this.whitelist.forEach(function(entry) {
+ while (escapedBody.indexOf(entry.pre) > -1) {
+ escapedBody = escapedBody.replace(entry.pre, entry.post);
+ }
+ });
+
+ return escapedBody;
};
/**
diff --git a/web-ui/app/js/mail_list/ui/mail_list.js b/web-ui/app/js/mail_list/ui/mail_list.js
index 18d36049..0f6c4fb5 100644
--- a/web-ui/app/js/mail_list/ui/mail_list.js
+++ b/web-ui/app/js/mail_list/ui/mail_list.js
@@ -81,7 +81,6 @@ define(
this.renderMails = function (mails) {
_.each(mails, this.appendMail, this);
this.trigger(document, events.search.highlightResults, {where: '#mail-list'});
- this.trigger(document, events.search.highlightResults, {where: '.bodyArea'});
this.trigger(document, events.search.highlightResults, {where: '.subjectArea'});
this.trigger(document, events.search.highlightResults, {where: '.msg-header .recipients'});
};
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 dfc57585..d952fed7 100644
--- a/web-ui/app/js/mail_view/ui/mail_view.js
+++ b/web-ui/app/js/mail_view/ui/mail_view.js
@@ -71,9 +71,52 @@ define(
attachments: attachments
}));
- this.$node.find('.bodyArea').html(viewHelpers.formatMailBody(data.mail));
+ var $iframe = $("#read-sandbox");
+ var iframe = $iframe[0];
+
+ var content = viewHelpers.formatMailBody(data.mail);
+
+ iframe.onload = function() {
+ // use iframe-resizer to dynamically adapt iframe size to its content
+ var config = {
+ resizedCallback: scaleToFit,
+ checkOrigin: false
+ };
+ $iframe.iFrameResize(config);
+
+ // transform scale iframe to fit container width
+ // necessary if iframe is wider than container
+ function scaleToFit() {
+ var parentWidth = $iframe.parent().width();
+ var w = $iframe.width();
+ var scale = 'none';
+
+ // only scale html mails
+ var mail = data.mail;
+ if (mail && mail.htmlBody && (w > parentWidth)) {
+ scale = parentWidth / w;
+ scale = 'scale(' + scale + ',' + scale + ')';
+ }
+
+ $iframe.css({
+ '-webkit-transform-origin': '0 0',
+ '-moz-transform-origin': '0 0',
+ '-ms-transform-origin': '0 0',
+ 'transform-origin': '0 0',
+ '-webkit-transform': scale,
+ '-moz-transform': scale,
+ '-ms-transform': scale,
+ 'transform': scale
+ });
+ }
+
+ iframe.contentWindow.postMessage({
+ html: content
+ }, '*');
+ };
+
+
- this.trigger(document, events.search.highlightResults, {where: '.bodyArea'});
this.trigger(document, events.search.highlightResults, {where: '.subjectArea'});
this.trigger(document, events.search.highlightResults, {where: '.msg-header .recipients'});
this.trigger(document, events.ui.replyBox.showReplyContainer);
@@ -214,9 +257,17 @@ define(
this.trigger(events.mail.want, {mail: this.attr.ident, caller: this});
};
+ this.highlightMailContent = function (event, data) {
+ // we can't directly manipulate the iFrame to highlight the content
+ // so we need to take an indirection where we directly manipulate
+ // the mail content to accomodate the highlighting
+ this.trigger(document, events.mail.highlightMailContent, data);
+ };
+
this.after('initialize', function () {
- this.on(this, events.mail.here, this.displayMail);
this.on(this, events.mail.notFound, this.openNoMessageSelectedPane);
+ this.on(this, events.mail.here, this.highlightMailContent);
+ this.on(document, events.mail.display, this.displayMail);
this.on(document, events.dispatchers.rightPane.clear, this.teardown);
this.on(document, events.mail.tags.updated, this.tagsUpdated);
this.on(document, events.mail.deleted, this.mailDeleted);
diff --git a/web-ui/app/js/page/events.js b/web-ui/app/js/page/events.js
index 7a0dbf9d..ad15e76e 100644
--- a/web-ui/app/js/page/events.js
+++ b/web-ui/app/js/page/events.js
@@ -121,6 +121,8 @@ define(function () {
mail: {
here: 'mail:here',
want: 'mail:want',
+ display: 'mail:display',
+ highlightMailContent: 'mail:highlightMailContent',
send: 'mail:send',
send_failed: 'mail:send_failed',
sent: 'mail:sent',
diff --git a/web-ui/app/js/sandbox.js b/web-ui/app/js/sandbox.js
new file mode 100644
index 00000000..f9e708d6
--- /dev/null
+++ b/web-ui/app/js/sandbox.js
@@ -0,0 +1,9 @@
+(function () {
+ 'use strict';
+
+ window.onmessage = function (e) {
+ if (e.data.html) {
+ document.body.innerHTML = e.data.html;
+ }
+ };
+})();
diff --git a/web-ui/app/js/search/results_highlighter.js b/web-ui/app/js/search/results_highlighter.js
index 9e3ba167..831be0cd 100644
--- a/web-ui/app/js/search/results_highlighter.js
+++ b/web-ui/app/js/search/results_highlighter.js
@@ -40,6 +40,7 @@ define(
var domIdent = data.where;
if(this.attr.keywords) {
_.each(this.attr.keywords, function (keyword) {
+ keyword = escapeRegExp(keyword);
$(domIdent).highlightRegex(new RegExp(keyword, 'i'), {
tagType: 'em',
className: 'search-highlight'
@@ -57,12 +58,40 @@ define(
});
};
+ this.highlightString = function (string) {
+ _.each(this.attr.keywords, function (keyword) {
+ keyword = escapeRegExp(keyword);
+ var regex = new RegExp('(' + keyword + ')', 'ig');
+ string = string.replace(regex, '<em class="search-highlight">$1</em>');
+ });
+ return string;
+ };
+
+ /*
+ * Alter data.mail.textPlainBody to highlight each of this.attr.keywords
+ * and pass it back to the mail_view when done
+ */
+ this.highlightMailContent = function(ev, data){
+ var mail = data.mail;
+ mail.textPlainBody = this.highlightString(mail.textPlainBody);
+ this.trigger(document, events.mail.display, data);
+ };
+
+ /*
+ * Escapes the special charaters used regular expressions that
+ * would cause problems with strings in the RegExp constructor
+ */
+ function escapeRegExp(string){
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ }
+
this.after('initialize', function () {
this.on(document, events.search.perform, this.getKeywordsSearch);
this.on(document, events.ui.tag.select, this.clearHighlights);
this.on(document, events.search.resetHighlight, this.clearHighlights);
this.on(document, events.search.highlightResults, this.highlightResults);
+ this.on(document, events.mail.highlightMailContent, this.highlightMailContent);
});
}
});
diff --git a/web-ui/app/js/user_alerts/ui/user_alerts.js b/web-ui/app/js/user_alerts/ui/user_alerts.js
index b02762aa..e944a7a5 100644
--- a/web-ui/app/js/user_alerts/ui/user_alerts.js
+++ b/web-ui/app/js/user_alerts/ui/user_alerts.js
@@ -32,20 +32,26 @@ define(
dismissTimeout: 3000
});
- this.render = function (message) {
+ this.render = function(message) {
this.$node.html(templates.userAlerts.message(message));
this.show();
setTimeout(this.hide.bind(this), this.attr.dismissTimeout);
};
- this.displayMessage = function (ev, data) {
- this.render({ message: {content: data.message, class: (data.class || 'success')}});
+ this.displayMessage = function(ev, data) {
+ this.render({
+ message: {
+ content: data.message,
+ class: 'message-panel__growl--' + (data.class || 'success')
+ }
+ });
};
- this.after('initialize', function () {
+ this.after('initialize', function() {
this.on(document, events.ui.userAlerts.displayMessage, this.displayMessage);
});
}
}
);
+
diff --git a/web-ui/app/sandbox.html b/web-ui/app/sandbox.html
new file mode 100644
index 00000000..13a86f25
--- /dev/null
+++ b/web-ui/app/sandbox.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <link href="css/opensans.css" rel="stylesheet" type="text/css">
+ <link href="css/sandbox.css" rel="stylesheet" type="text/css">
+
+ <!--usemin_start-->
+ <script src="js/sandbox.js"></script>
+ <script src="bower_components/iframe-resizer/js/iframeResizer.contentWindow.min.js"></script>
+ <!--usemin_end-->
+</head>
+
+<body></body>
+
+</html>
diff --git a/web-ui/app/scss/_alerts.scss b/web-ui/app/scss/_alerts.scss
deleted file mode 100644
index cfb31cbe..00000000
--- a/web-ui/app/scss/_alerts.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-.message-panel {
- width: 100%;
- margin: 10px auto;
- position: fixed;
- z-index: 10000;
- text-align: center;
- span{
- padding: 5px 60px;
- &.success {
- background: $warning;
- color: darken($warning, 50%);
- border: 1px solid darken($warning, 10%);
- @include box-shadow(1px 1px 3px darken($warning, 60%));
- }
- &.error {
- font-weight: bold;
- color: white;
- background: $error;
- border: 1px solid darken($error, 10%);
- @include box-shadow(1px 1px 3px darken($error, 60%));
- }
- }
-}
diff --git a/web-ui/app/scss/_mascot.scss b/web-ui/app/scss/_mascot.scss
deleted file mode 100644
index 74279063..00000000
--- a/web-ui/app/scss/_mascot.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-#no-message-selected-pane {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100vh;
-
- z-index: -100;
- background: $contrast;
- padding: 30px;
- vertical-align:middle;
- text-align:center;
- -webkit-transform: translate3d(0, 0, 0);
- &:before{
- content: '';
- display: inline-block;
- height: 100%;
- vertical-align: middle;
- margin-right: -0.25em;
- }
- .scene{
- display:inline-block;
- vertical-align:middle;
- }
-
- .text{
- color:$medium_dark_grey;
- margin-bottom: 40px;
- }
-}
-
-#no-mails-available-pane {
- text-align: center;
- line-height: 100vh;
- margin-top: -130px;
-
- .scene{
- display:inline-block;
- vertical-align:middle;
- }
-
- .text{
- color:$medium_dark_grey;
- margin-bottom: 40px;
- }
-
-}
diff --git a/web-ui/app/scss/_mixins.scss b/web-ui/app/scss/_mixins.scss
index 5bb84105..4583c55d 100644
--- a/web-ui/app/scss/_mixins.scss
+++ b/web-ui/app/scss/_mixins.scss
@@ -1,4 +1,4 @@
-@import 'colors';
+@import 'base/colors';
// SHARED MIXINS
@mixin btn-transition {
diff --git a/web-ui/app/scss/_others.scss b/web-ui/app/scss/_others.scss
new file mode 100644
index 00000000..e73ed33d
--- /dev/null
+++ b/web-ui/app/scss/_others.scss
@@ -0,0 +1,7 @@
+.no-padding {
+ padding: 0;
+}
+
+.text-right {
+ text-align: right;
+}
diff --git a/web-ui/app/scss/_read.scss b/web-ui/app/scss/_read.scss
index 2c079408..236a7c7c 100644
--- a/web-ui/app/scss/_read.scss
+++ b/web-ui/app/scss/_read.scss
@@ -66,6 +66,13 @@
.bodyArea {
padding: 10px 30px 0 30px;
+ box-sizing: border-box;
+
+ iframe {
+ box-sizing: inherit;
+ border: none;
+ width: 100%;
+ }
}
.attachmentsAreaWrap {
diff --git a/web-ui/app/scss/_colors.scss b/web-ui/app/scss/base/_colors.scss
index de5f76b3..de5f76b3 100644
--- a/web-ui/app/scss/_colors.scss
+++ b/web-ui/app/scss/base/_colors.scss
diff --git a/web-ui/app/scss/opensans.scss b/web-ui/app/scss/base/_fonts.scss
index ada6a025..ada6a025 100644
--- a/web-ui/app/scss/opensans.scss
+++ b/web-ui/app/scss/base/_fonts.scss
diff --git a/web-ui/app/scss/base/_scaffolding.scss b/web-ui/app/scss/base/_scaffolding.scss
new file mode 100644
index 00000000..b8b5fa3b
--- /dev/null
+++ b/web-ui/app/scss/base/_scaffolding.scss
@@ -0,0 +1,10 @@
+html {
+ height: 100% ;
+}
+
+body {
+ min-height: 100% ;
+ overflow: hidden;
+ background: $white;
+}
+
diff --git a/web-ui/app/scss/layout/_message-panel.scss b/web-ui/app/scss/layout/_message-panel.scss
new file mode 100644
index 00000000..e311a9bf
--- /dev/null
+++ b/web-ui/app/scss/layout/_message-panel.scss
@@ -0,0 +1,9 @@
+message-panel-container {
+ overflow: hidden;
+ position: fixed;
+ top: 0;
+ width: 100% ;
+ position: relative;
+ margin-bottom: 0;
+}
+
diff --git a/web-ui/app/scss/main.scss b/web-ui/app/scss/main.scss
deleted file mode 100644
index b582a5d5..00000000
--- a/web-ui/app/scss/main.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-@import "compass/css3";
-@import "reset";
-@import "foundation";
-@import "colors";
-@import "mixins";
-@import "alerts";
-@import "read";
-@import "reply";
-@import "compose";
-@import "security";
-@import "mascot";
-@import "styles";
-
-html {
- height:100%;
-}
-body {
- min-height:100%;
- overflow: hidden;
- background: $white;
-}
-header#main {
- overflow: hidden;
- position: fixed;
- top: 0;
- width: 100%;
- position: relative;
- margin-bottom: 0;
-}
-
-.no-padding {
- padding: 0;
-}
-
-.text-right {
- text-align: right;
-}
diff --git a/web-ui/app/scss/mixins/_position-helpers.scss b/web-ui/app/scss/mixins/_position-helpers.scss
new file mode 100644
index 00000000..254bfc6c
--- /dev/null
+++ b/web-ui/app/scss/mixins/_position-helpers.scss
@@ -0,0 +1,9 @@
+@mixin absolute-center-unknown-height-width() {
+ margin: auto;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ -ms-transform: translate(-50%, -50%);
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
diff --git a/web-ui/app/scss/sandbox.scss b/web-ui/app/scss/sandbox.scss
new file mode 100644
index 00000000..3cb4c441
--- /dev/null
+++ b/web-ui/app/scss/sandbox.scss
@@ -0,0 +1,20 @@
+$search-highlight: #FFEF29;
+
+body {
+ font-family: "Open Sans", "Microsoft YaHei", "Hiragino Sans GB", "Hiragino Sans GB W3", "微软雅黑", "Helvetica Neue", Arial, sans-serif;
+ font-size: 13px;
+ line-height: 1.2em;
+ background: white;
+ color: #333;
+ padding: 0;
+ margin: 0;
+ font-weight: normal;
+ -webkit-font-smoothing: antialiased;
+ font-style: normal;
+ box-sizing: border-box;
+ word-wrap: break-word;
+}
+
+.search-highlight {
+ background-color: $search-highlight;
+}
diff --git a/web-ui/app/scss/style.scss b/web-ui/app/scss/style.scss
new file mode 100644
index 00000000..d27b3031
--- /dev/null
+++ b/web-ui/app/scss/style.scss
@@ -0,0 +1,35 @@
+// vendor stylesheets and resets
+@import "vendor/reset";
+@import "compass/css3";
+@import "vendor/foundation";
+
+// basic configuration
+@import "base/fonts";
+@import "base/colors";
+@import "base/scaffolding";
+
+// mixins
+@import "mixins/position-helpers";
+@import "mixins";
+
+// templates
+@import "templates/no-content-placeholder";
+
+// layout
+@import "layout/message-panel";
+
+// views
+@import "views/message-panel";
+@import "views/no-message-selected";
+@import "views/no-mails-available";
+
+// misc stuff
+@import "others";
+
+// TODO
+@import "compose";
+@import "read";
+@import "reply";
+@import "security";
+@import "styles";
+
diff --git a/web-ui/app/scss/templates/_no-content-placeholder.scss b/web-ui/app/scss/templates/_no-content-placeholder.scss
new file mode 100644
index 00000000..c6807011
--- /dev/null
+++ b/web-ui/app/scss/templates/_no-content-placeholder.scss
@@ -0,0 +1,5 @@
+.no-content-placeholder {
+ @include absolute-center-unknown-height-width;
+
+ color: $medium_dark_grey;
+}
diff --git a/web-ui/app/scss/foundation.scss b/web-ui/app/scss/vendor/_foundation.scss
index 7918cf26..7918cf26 100644
--- a/web-ui/app/scss/foundation.scss
+++ b/web-ui/app/scss/vendor/_foundation.scss
diff --git a/web-ui/app/scss/reset.scss b/web-ui/app/scss/vendor/_reset.scss
index 55f8d054..55f8d054 100644
--- a/web-ui/app/scss/reset.scss
+++ b/web-ui/app/scss/vendor/_reset.scss
diff --git a/web-ui/app/scss/views/_message-panel.scss b/web-ui/app/scss/views/_message-panel.scss
new file mode 100644
index 00000000..4a0a7a6b
--- /dev/null
+++ b/web-ui/app/scss/views/_message-panel.scss
@@ -0,0 +1,26 @@
+.message-panel {
+ width: 100%;
+ margin: 10px auto;
+ position: fixed;
+ z-index: 10000;
+ text-align: center;
+
+ &__growl {
+ padding: 5px 60px;
+
+ &--success {
+ background: $warning;
+ color: darken($warning, 50%);
+ border: 1px solid darken($warning, 10%);
+ @include box-shadow(1px 1px 3px darken($warning, 60%));
+ }
+
+ &--error {
+ font-weight: bold;
+ color: white;
+ background: $error;
+ border: 1px solid darken($error, 10%);
+ @include box-shadow(1px 1px 3px darken($error, 60%));
+ }
+ }
+}
diff --git a/web-ui/app/scss/views/_no-mails-available.scss b/web-ui/app/scss/views/_no-mails-available.scss
new file mode 100644
index 00000000..bf5d256a
--- /dev/null
+++ b/web-ui/app/scss/views/_no-mails-available.scss
@@ -0,0 +1,3 @@
+.no-mails-available-pane {
+ @extend .no-content-placeholder;
+}
diff --git a/web-ui/app/scss/views/_no-message-selected.scss b/web-ui/app/scss/views/_no-message-selected.scss
new file mode 100644
index 00000000..0e367bf2
--- /dev/null
+++ b/web-ui/app/scss/views/_no-message-selected.scss
@@ -0,0 +1,14 @@
+.no-message-selected-pane {
+ background: $contrast;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+
+ &__text {
+ @extend .no-content-placeholder;
+
+ margin-bottom: 40px; // aligns label with "no results for XYZ"
+ }
+}
diff --git a/web-ui/app/templates/compose/no_mails_available.hbs b/web-ui/app/templates/compose/no_mails_available.hbs
index 6388d7db..147f3533 100644
--- a/web-ui/app/templates/compose/no_mails_available.hbs
+++ b/web-ui/app/templates/compose/no_mails_available.hbs
@@ -1,7 +1,7 @@
-<div class="scene">
+<div class="no-mails-available-pane">
{{#if forSearch }}
- <div class="text">{{t 'NO RESULTS FOR'}}: '{{ forSearch }}'.</div>
+ {{t 'NO RESULTS FOR'}}: '{{ forSearch }}'.
{{else}}
- <div class="text">{{t 'NO EMAILS IN'}} '{{ tag }}'.</div>
+ {{t 'NO EMAILS IN'}} '{{ tag }}'.
{{/if}}
</div>
diff --git a/web-ui/app/templates/compose/no_message_selected.hbs b/web-ui/app/templates/compose/no_message_selected.hbs
index 0442192d..71aa6267 100644
--- a/web-ui/app/templates/compose/no_message_selected.hbs
+++ b/web-ui/app/templates/compose/no_message_selected.hbs
@@ -1,3 +1,3 @@
-<div class="scene">
- <div class="text">{{t 'NOTHING SELECTED'}}.</div>
+<div class="no-message-selected-pane">
+ <div class="no-message-selected-pane__text">{{t 'NOTHING SELECTED'}}.</div>
</div>
diff --git a/web-ui/app/templates/mails/full_view.hbs b/web-ui/app/templates/mails/full_view.hbs
index f9ec084a..3ff109e9 100644
--- a/web-ui/app/templates/mails/full_view.hbs
+++ b/web-ui/app/templates/mails/full_view.hbs
@@ -64,6 +64,7 @@
</header>
<div class="bodyArea column large-12">
+ <iframe id="read-sandbox" sandbox="allow-popups allow-scripts" src="sandbox/sandbox.html" scrolling="no"></iframe>
</div>
{{#if attachments}}
diff --git a/web-ui/app/templates/search/search_trigger.hbs b/web-ui/app/templates/search/search_trigger.hbs
index 2261d154..f2c410a4 100644
--- a/web-ui/app/templates/search/search_trigger.hbs
+++ b/web-ui/app/templates/search/search_trigger.hbs
@@ -1,3 +1,3 @@
<form>
- <input type="search" placeholder="{{t 'search-placeholder'}}"></input>
+ <input type="search" pattern="[a-zA-Z0-9\s]{3,}" placeholder="{{t 'search-placeholder'}}"></input>
</form>
diff --git a/web-ui/app/templates/user_alerts/message.hbs b/web-ui/app/templates/user_alerts/message.hbs
index 3a0055eb..abba1f91 100644
--- a/web-ui/app/templates/user_alerts/message.hbs
+++ b/web-ui/app/templates/user_alerts/message.hbs
@@ -1 +1 @@
-<span class="{{ message.class }}">{{ message.content }}</span>
+<span class="message-panel__growl {{ message.class }}">{{ message.content }}</span>
diff --git a/web-ui/bower.json b/web-ui/bower.json
index 263ac2e4..018a57c4 100644
--- a/web-ui/bower.json
+++ b/web-ui/bower.json
@@ -17,7 +17,8 @@
"jquery-file-upload": "~9.11.2",
"jquery-ui": "~1.11.4",
"DOMPurify": "~0.7.4",
- "he": "~0.5.0"
+ "he": "~0.5.0",
+ "iframe-resizer": "~3.5.3"
},
"devDependencies": {
"handlebars": "2.0.0",
diff --git a/web-ui/config/package.sh b/web-ui/config/package.sh
index 9b6bc66c..0bd82367 100644
--- a/web-ui/config/package.sh
+++ b/web-ui/config/package.sh
@@ -28,16 +28,24 @@ mkdir -p dist
./go handlebars
./go imagemin
./go minify_html
+./go minify_sandbox
./go add_git_version
./go buildmain
# copy files
cd app
-cp --parents 404.html fonts/* locales/**/* bower_components/font-awesome/css/font-awesome.min.css bower_components/jquery-file-upload/css/jquery.fileupload.css bower_components/font-awesome/fonts/* ../dist
+cp --parents \
+404.html \
+fonts/* \
+locales/**/* \
+bower_components/font-awesome/css/font-awesome.min.css \
+bower_components/jquery-file-upload/css/jquery.fileupload.css \
+bower_components/font-awesome/fonts/* \
+../dist
cd -
-# concat js files and minify
+# concat js files and minify for app.min.js
cat \
app/bower_components/modernizr/modernizr.js \
app/bower_components/lodash/dist/lodash.js \
@@ -51,6 +59,14 @@ app/bower_components/foundation/js/foundation.js \
app/bower_components/foundation/js/foundation/foundation.reveal.js \
app/bower_components/foundation/js/foundation/foundation.offcanvas.js \
app/js/foundation/initialize_foundation.js \
+app/bower_components/iframe-resizer/js/iframeResizer.min.js \
.tmp/app.concatenated.js > dist/app.js
node_modules/.bin/minify dist/app.js > dist/app.min.js
rm dist/app.js
+
+# concat js files and minify for sandbox.min.js
+cat \
+app/js/sandbox.js \
+app/bower_components/iframe-resizer/js/iframeResizer.contentWindow.min.js > dist/sandbox.js
+node_modules/.bin/minify dist/sandbox.js > dist/sandbox.min.js
+rm dist/sandbox.js
diff --git a/web-ui/package.json b/web-ui/package.json
index a49e32d1..0e8b9262 100644
--- a/web-ui/package.json
+++ b/web-ui/package.json
@@ -38,6 +38,7 @@
"package": "/bin/bash config/package.sh",
"imagemin": "node config/imagemin.js",
"minify_html": "node_modules/.bin/html-minifier app/index.html --collapse-whitespace | sed 's|<!--usemin_start-->.*<!--usemin_end-->|<script src=\"assets/app.min.js\" type=\"text/javascript\"></script>|' > dist/index.html",
+ "minify_sandbox": "node_modules/.bin/html-minifier app/sandbox.html --collapse-whitespace | sed 's|<!--usemin_start-->.*<!--usemin_end-->|<script src=\"sandbox.min.js\" type=\"text/javascript\"></script>|' > dist/sandbox.html",
"add_git_version": "/bin/bash config/add_git_version.sh"
}
}
diff --git a/web-ui/test/spec/helpers/sanitizer.spec.js b/web-ui/test/spec/helpers/sanitizer.spec.js
index acd4b2b2..b553583e 100644
--- a/web-ui/test/spec/helpers/sanitizer.spec.js
+++ b/web-ui/test/spec/helpers/sanitizer.spec.js
@@ -25,6 +25,12 @@ define(['helpers/sanitizer'], function (sanitizer) {
var output = sanitizer.purifyText('123<a>asd</a>');
expect(output).toEqual(expectedOutput);
});
+
+ it('should leave highlighted text untouched', function () {
+ var expectedOutput = '<em class="search-highlight">&#x31;&#x32;&#x33;&#x3C;&#x61;&#x3E;&#x61;&#x73;&#x64;&#x3C;&#x2F;&#x61;&#x3E;</em>';
+ var output = sanitizer.purifyText('<em class="search-highlight">123<a>asd</a></em>');
+ expect(output).toEqual(expectedOutput);
+ });
});
describe('sanitizer.sanitize', function () {
diff --git a/web-ui/test/spec/mail_view/ui/mail_view.spec.js b/web-ui/test/spec/mail_view/ui/mail_view.spec.js
index 9ed56023..9f1114a7 100644
--- a/web-ui/test/spec/mail_view/ui/mail_view.spec.js
+++ b/web-ui/test/spec/mail_view/ui/mail_view.spec.js
@@ -21,6 +21,12 @@ describeComponent('mail_view/ui/mail_view', function () {
expect(spyEvent.mostRecentCall.data.mail).toEqual(1);
});
+ it('triggers mail.highlightMailContent when receiving mail.here', function () {
+ var hightlightEvent = spyOnEvent(document,Pixelated.events.mail.highlightMailContent);
+ this.component.trigger(this.component, Pixelated.events.mail.here);
+ expect(hightlightEvent).toHaveBeenTriggeredOn(document);
+ });
+
it('triggers dispatchers.rightPane.openNoMessageSelected when getting mail.notFound', function () {
var openNoMessageSelectedEvent = spyOnEvent(document, Pixelated.events.dispatchers.rightPane.openNoMessageSelected);
diff --git a/web-ui/test/spec/mail_view/ui/no_mails_available_pane.spec.js b/web-ui/test/spec/mail_view/ui/no_mails_available_pane.spec.js
index 157e6c47..1bf79b96 100644
--- a/web-ui/test/spec/mail_view/ui/no_mails_available_pane.spec.js
+++ b/web-ui/test/spec/mail_view/ui/no_mails_available_pane.spec.js
@@ -4,17 +4,17 @@ describeComponent('mail_view/ui/no_mails_available_pane', function () {
describe('after initialization', function () {
it('renders template', function () {
this.setupComponent({tag: 'inbox'});
- expect(this.$node.html()).toMatch('<div class="text">NO EMAILS IN \'INBOX\'.</div>');
+ expect(this.$node.html()).toMatch('<div class="no-mails-available-pane">\n NO EMAILS IN \'INBOX\'.\n</div>');
});
it('show different message for search with no results', function () {
this.setupComponent({tag: 'all', forSearch: 'search'});
- expect(this.$node.html()).toMatch('<div class="text">NO RESULTS FOR: \'SEARCH\'.</div>');
+ expect(this.$node.html()).toMatch('<div class="no-mails-available-pane">\n NO RESULTS FOR: \'SEARCH\'.\n</div>');
});
it('show only tag information when listing all mails', function () {
this.setupComponent({tag: 'all', forSearch: 'in:all'});
- expect(this.$node.html()).toMatch('<div class="text">NO EMAILS IN \'ALL\'.</div>');
+ expect(this.$node.html()).toMatch('<div class="no-mails-available-pane">\n NO EMAILS IN \'ALL\'.\n</div>');
});
});
});
diff --git a/web-ui/test/spec/search/results_highlighter.spec.js b/web-ui/test/spec/search/results_highlighter.spec.js
index cfb61e9c..13131a8e 100644
--- a/web-ui/test/spec/search/results_highlighter.spec.js
+++ b/web-ui/test/spec/search/results_highlighter.spec.js
@@ -1,9 +1,11 @@
describeComponent('search/results_highlighter', function () {
'use strict';
- it('highlights words or parts of words that match with the keywords given', function () {
+ beforeEach(function () {
this.setupComponent('<div id="text">Any one seeing too many open bugs</div>');
+ });
+ it('highlights words or parts of words that match with the keywords given', function () {
this.component.attr = {keywords: ['any']};
this.component.highlightResults(event, {where: '#text'});
@@ -12,9 +14,15 @@ describeComponent('search/results_highlighter', function () {
expect(highlightedWords).toEqual(2);
});
- it('resets highlights when a new search is performed', function() {
- this.setupComponent('<div id="text">Any one seeing too many open bugs</div>');
+ it('highlights a string with the keywords given', function () {
+ this.component.attr = {keywords: ['foo']};
+ var expectedString = 'the <em class="search-highlight">foo</em> bar';
+ var string = this.component.highlightString('the foo bar');
+
+ expect(string).toEqual(expectedString);
+ });
+ it('resets highlights when a new search is performed', function() {
this.component.attr = {keywords: ['any']};
this.component.highlightResults(event, {where: '#text'});
$(document).trigger(Pixelated.events.search.resetHighlight);
diff --git a/web-ui/test/spec/user_alerts/ui/user_alerts.spec.js b/web-ui/test/spec/user_alerts/ui/user_alerts.spec.js
index bde3b7fa..f15cda81 100644
--- a/web-ui/test/spec/user_alerts/ui/user_alerts.spec.js
+++ b/web-ui/test/spec/user_alerts/ui/user_alerts.spec.js
@@ -8,13 +8,13 @@ describeComponent('user_alerts/ui/user_alerts', function () {
it('should render message when ui:user_alerts:displayMessage is triggered', function () {
this.component.trigger(Pixelated.events.ui.userAlerts.displayMessage, { message: 'a message' });
- expect(this.component.$node.html()).toEqual('<span class="success">a message</span>\n');
+ expect(this.component.$node.html()).toEqual('<span class="message-panel__growl message-panel__growl--success">a message</span>\n');
});
it('should render error message', function () {
this.component.trigger(Pixelated.events.ui.userAlerts.displayMessage, { message: 'send failed', class: 'error' });
- expect(this.component.$node.html()).toEqual('<span class="error">send failed</span>\n');
+ expect(this.component.$node.html()).toEqual('<span class="message-panel__growl message-panel__growl--error">send failed</span>\n');
});
it('should be emptied and hidden when hide is called', function() {
@@ -24,6 +24,4 @@ describeComponent('user_alerts/ui/user_alerts', function () {
expect(this.$node.html()).toEqual('');
});
-
-
});