diff --git a/app/__init__.py b/app/__init__.py index e2852b6b3..d013be090 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -303,8 +303,13 @@ def useful_headers_after_request(response): response.headers.add('X-Frame-Options', 'deny') response.headers.add('X-Content-Type-Options', 'nosniff') response.headers.add('X-XSS-Protection', '1; mode=block') - response.headers.add('Content-Security-Policy', - "default-src 'self' 'unsafe-inline'; script-src 'self' *.google-analytics.com 'unsafe-inline' data:; object-src 'self'; font-src 'self' data:; img-src 'self' *.google-analytics.com data:;") # noqa + response.headers.add('Content-Security-Policy', ( + "default-src 'self' 'unsafe-inline';" + "script-src 'self' *.google-analytics.com 'unsafe-inline' data:;" + "object-src 'self';" + "font-src 'self' data:;" + "img-src 'self' *.google-analytics.com *.notifications.service.gov.uk data:;" + )) if 'Cache-Control' in response.headers: del response.headers['Cache-Control'] response.headers.add( diff --git a/app/assets/javascripts/highlightTags.js b/app/assets/javascripts/highlightTags.js index 97ab56bfb..896c83358 100644 --- a/app/assets/javascripts/highlightTags.js +++ b/app/assets/javascripts/highlightTags.js @@ -5,7 +5,7 @@ !('oninput' in document.createElement('input')) ) return; - const tagPattern = /\(\([^\)\(]+\)\)/g; + const tagPattern = /\(\(([^\)\((\?)]+)(\?\?)?([^\)\(]*)\)\)/g; Modules.HighlightTags = function() { @@ -43,7 +43,9 @@ this.replacePlaceholders = () => this.$background.html( this.escapedMessage().replace( - tagPattern, match => `${match}` + tagPattern, (match, name, separator, value) => value && separator ? + `((${name}??${value}))` : + `((${name}${value}))` ) ); diff --git a/app/assets/stylesheets/components/placeholder.scss b/app/assets/stylesheets/components/placeholder.scss index e4b6b9fb9..2d76d8ff0 100644 --- a/app/assets/stylesheets/components/placeholder.scss +++ b/app/assets/stylesheets/components/placeholder.scss @@ -22,3 +22,19 @@ border-radius: 1px; box-shadow: inset 0 -2px 0 0 $white, inset 0 2px 0 0 $white; } + +.placeholder-conditional { + + @extend %placeholder; + border-radius: 0; + border-top-left-radius: 20px; + border-bottom-left-radius: 20px; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + box-shadow: inset 9px 0 0 0 $white, inset -17px 0 0 0 $white, inset 0 -3px 0 0 $white, inset 0 3px 0 0 $white; + + .sms-message-wrapper & { + box-shadow: inset 9px 0 0 0 $panel-colour, inset -17px 0 0 0 $panel-colour, inset 0 -3.5px 0 0 $panel-colour, inset 0 3.5px 0 0 $panel-colour; + } + +} diff --git a/app/main/views/index.py b/app/main/views/index.py index d3b9fae03..569d716c8 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -1,12 +1,15 @@ import markdown import os -from flask import (render_template, url_for, redirect, Markup) +from flask import (render_template, url_for, redirect, Markup, request) from app.main import main +from app import convert_to_boolean from flask_login import login_required from flask.ext.login import current_user from mdx_gfm import GithubFlavoredMarkdownExtension +from notifications_utils.renderers import HTMLEmail + @main.route('/') def index(): @@ -46,6 +49,42 @@ def delivery_and_failure(): return render_template('views/delivery-and-failure.html') +@main.route('/_email') +def email_template(): + return HTMLEmail( + govuk_banner=convert_to_boolean(request.args.get('govuk_banner', True)) + )( + 'Lorem Ipsum is simply dummy text of the printing and typesetting ' + 'industry.\n\nLorem Ipsum has been the industry’s standard dummy ' + 'text ever since the 1500s, when an unknown printer took a galley ' + 'of type and scrambled it to make a type specimen book. ' + '\n\n' + '# History' + '\n\n' + 'It has ' + 'survived not only' + '\n' + '*five centuries' + '\n' + '* but also the leap into electronic typesetting' + '\n\n' + 'It was ' + 'popularised in the 1960s with the release of Letraset sheets ' + 'containing Lorem Ipsum passages, and more recently with desktop ' + 'publishing software like Aldus PageMaker including versions of ' + 'Lorem Ipsum.' + '\n\n' + '^ It is a long established fact that a reader will be distracted ' + 'by the readable content of a page when looking at its layout.' + '\n\n' + 'The point of using Lorem Ipsum is that it has a more-or-less ' + 'normal distribution of letters, as opposed to using ‘Content ' + 'here, content here’, making it look like readable English.' + '\n\n\n' + 'This is an example of an email sent using GOV.UK Notify.' + ) + + @main.route('/documentation') def documentation(): curr_dir = os.path.dirname(os.path.realpath(__file__)) diff --git a/app/templates/components/email-message.html b/app/templates/components/email-message.html index 8543fae86..6099d4341 100644 --- a/app/templates/components/email-message.html +++ b/app/templates/components/email-message.html @@ -6,7 +6,8 @@ recipient=None, id=None, show_placeholder_for_recipient=False, - show_id=False + show_id=False, + expanded=False ) %}
{% if from_name or subject %} @@ -45,11 +46,15 @@ {% endif %} -
+
{% endmacro %} diff --git a/app/templates/views/check.html b/app/templates/views/check.html index ab638153b..5c5c4db5b 100644 --- a/app/templates/views/check.html +++ b/app/templates/views/check.html @@ -144,7 +144,7 @@ {% if 'email' == template.template_type %} {{ email_message( template.formatted_subject_as_markup if errors else template.replaced_subject, - template.formatted_as_markup if errors else template.replaced, + template.formatted_as_markup if errors else template.replaced|safe, from_address='{}@notifications.service.gov.uk'.format(current_service.email_from), from_name=current_service.name, recipient=first_recipient, @@ -154,7 +154,7 @@
{{ sms_message( - template.formatted_as_markup if errors else template.replaced, + template.formatted_as_markup if errors else template.replaced|safe, recipient=first_recipient, show_placeholder_for_recipient=errors )}} diff --git a/app/templates/views/templates/_template.html b/app/templates/views/templates/_template.html index 50cf966f9..50c01ef71 100644 --- a/app/templates/views/templates/_template.html +++ b/app/templates/views/templates/_template.html @@ -6,7 +6,8 @@ {{ email_message( template.formatted_subject_as_markup, template.formatted_as_markup, - id=template.id + id=template.id, + expanded=expanded ) }} {% elif 'sms' == template.template_type %} {{ sms_message( diff --git a/app/templates/views/templates/template.html b/app/templates/views/templates/template.html index 4257761f6..3f73a33ae 100644 --- a/app/templates/views/templates/template.html +++ b/app/templates/views/templates/template.html @@ -14,7 +14,7 @@

{{ template.name }}

- {% with show_title=False %} + {% with show_title=False, expanded=True %} {% include 'views/templates/_template.html' %} {% endwith %}
diff --git a/requirements.txt b/requirements.txt index a633bd6b0..20ab05704 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,4 +18,4 @@ pytz==2016.4 git+https://github.com/alphagov/notifications-python-client.git@1.0.0#egg=notifications-python-client==1.0.0 -git+https://github.com/alphagov/notifications-utils.git@6.2.0#egg=notifications-utils==6.2.0 +git+https://github.com/alphagov/notifications-utils.git@8.4.1#egg=notifications-utils==8.4.1 diff --git a/tests/app/main/views/test_email_preview.py b/tests/app/main/views/test_email_preview.py new file mode 100644 index 000000000..b19d5e985 --- /dev/null +++ b/tests/app/main/views/test_email_preview.py @@ -0,0 +1,23 @@ +import pytest +from flask import url_for + + +@pytest.mark.parametrize( + "query_args, params", [ + ({}, {'govuk_banner': True}), + ({'govuk_banner': 'false'}, {'govuk_banner': False}) + ] +) +def test_renders(app_, mocker, query_args, params): + with app_.test_request_context(), app_.test_client() as client: + + mock_html_email = mocker.patch( + 'app.main.views.index.HTMLEmail', + return_value=lambda x: 'rendered' + ) + + response = client.get(url_for('main.email_template', **query_args)) + + assert response.status_code == 200 + assert response.get_data(as_text=True) == 'rendered' + mock_html_email.assert_called_once_with(**params) diff --git a/tests/app/main/views/test_headers.py b/tests/app/main/views/test_headers.py index 006855439..53b1f2b32 100644 --- a/tests/app/main/views/test_headers.py +++ b/tests/app/main/views/test_headers.py @@ -6,4 +6,10 @@ def test_owasp_useful_headers_set(app_): assert response.headers['X-Frame-Options'] == 'deny' assert response.headers['X-Content-Type-Options'] == 'nosniff' assert response.headers['X-XSS-Protection'] == '1; mode=block' - assert response.headers['Content-Security-Policy'] == "default-src 'self' 'unsafe-inline'; script-src 'self' *.google-analytics.com 'unsafe-inline' data:; object-src 'self'; font-src 'self' data:; img-src 'self' *.google-analytics.com data:;" # noqa + assert response.headers['Content-Security-Policy'] == ( + "default-src 'self' 'unsafe-inline';" + "script-src 'self' *.google-analytics.com 'unsafe-inline' data:;" + "object-src 'self';" + "font-src 'self' data:;" + "img-src 'self' *.google-analytics.com *.notifications.service.gov.uk data:;" + )